Property with '= {Return}()' or '{Return}'

Why does the in operator return true for a property that is undefined?

Note: While researching the question I found the answer, since I have already spent the time, I decided to post what I found.


The reason why they return a different value is that [[GetProperty]] calls [[GetOwnProperty]], which does does not return the property value. [[GetOwnProperty]] returns a Property Descriptor which describes things like if the property is enumerable, writable and it's value.

When you set a property to undefined as with bar in the question, a Property Descriptor is created* with a [[Value]] of undefined. When [[GetOwnProperty]] is called for bar the Property Descriptor is returned, while with baz, undefined is returned. This means that [[GetProperty]] return a defined value for bar, which then makes [[HasProperty]] return true, instead of false. Which in turn makes the in operator return true as well.

* this is a lie, but for simplicity sake.

Specification Trace

From Annotated ECMAScript 5.1.

Note: Numbers below link to Spec reference where possible

For: 'bar' in foo

  • 1. Evaluating 'bar' gives a string 'bar'
  • 2. GetValue of 'bar' will return the value 'bar'
  • 3. Evaluating foo gives a reference foo
  • 4. GetValue of foo will return the value of 'foo', our object
  • 5. Type of value of foo is an object, no exception thrown

    1. toString of value of 'bar' returns the string 'bar, call [[HasProperty]]
      • 1. [[GetProperty]] returns Property Descriptor (not undefined)
      • 1. [[GetOwnProperty]] return Property Descriptor (not undefined)
        1. foo does have own property called bar, do not return undefined

        1. Create a Property Descriptor with no fields

        1. Create X from foo.bar

        1. X is a data property, so set [[Value]] to undefined and set [[Writable]]

        1. Else branch skipped

        1. Set [[Enumerable]]

        1. Set [[Configurable]]

        1. Return Property Descriptor

        1. Property Descriptor is not undefined, return the Property Descriptor.

        1. Property Descriptor is not undefined, so don't return false

        1. else, return true

Is it possible to have class.property = x return something other than x?

One downside is that you would break the chained assignment semantics:

$ irb 
irb(main):001:0> x = y = 3
=> 3
irb(main):002:0> p x
3
=> nil
irb(main):003:0> p y
3
=> nil
irb(main):004:0>

Consider:

x = MyClass.property = 3

Then x would take true if this worked as you had expected (right-associativity). That could be a surprise for people using your interface and used to the typical semantics.

You also got me thinking about parallel assignment, eg:

x, y = 1, 2

Apparently the return value from that expression is implementation specific... I guess I won't be chaining parallel assignments :)

Nice question!

TypeScript function that returns arg with extra property (TS2322)

Forging object literals with spread syntax and getting the types in line can be tricky in TypeScript. Assuming x does not change, you can simplify the problem a bit by not extracting x and adding it back to the result, but just destructure props separately instead:

function addY<T extends { x: number }>(props: T): Omit<T, 'y'> & { y: number } {
const {x} = props
return {
...props,
y: x + 1,
};
}

This type checks, and if you define a function Normalize to get rid of type aliases

type Normalize<T> = {[K in keyof T]: T[K]}

you can check that the return types are as expected:

const o1 = addY({x:10, z: 1})
type TestO1 = Normalize<typeof o1> // {x: number, z: number, y: number}

const o2 = addY({x:10, z: 1, y: 'str'})
type TestO2 = Normalize<typeof o2> // {x: number, z: number, y: number}

Of course, you could also use props.x instead of destructuring.

TypeScript playground

Checking if object has property with given name returns false

You have three objects and the methods you are using to test are meant for a single object.

  • obj - object containing a games property
  • obj.games - array with one element
  • obj.games[0] - object with id property

Consider these which all are true:

let obj = {
games: [
{id: 'test'}
]
}

console.log(`obj.hasOwnProperty('games')`, obj.hasOwnProperty('games'))
console.log(`'games' in obj`, 'games' in obj)

console.log(`obj.games.hasOwnProperty('0')`, obj.games.hasOwnProperty('0'))
console.log(`'0' in obj.games`, '0' in obj.games)

console.log(`obj.games[0].hasOwnProperty('id')`, obj.games[0].hasOwnProperty('id'))
console.log(`'id' in obj.games[0]`, 'id' in obj.games[0])

if (obj && obj.games && obj.games.length > 0 && obj.games[0].id) {
console.log('obj.games has at least one element, '
+ 'and the first element has an id')
}

let id = obj && obj.games && obj.games.length > 0 && obj.games[0].id;
console.log('id:', id);

Property does not exist on type void

You don't return anything from your catch block, so the return type of your function is Promise<WhateverTheTypeOfResponseIs | void> (N.B. async functions implicitly return a Promise, and if your postWithToken function doesn't return anything then it's just Promise<void>), depending on which code path happens.

In the future you can avoid unpleasant and slightly problematic to debug issues like this by giving your functions an explicit return type and in this case the compiler will let you know that your expectation was violated:

const postWithToken = async (route: string, token: string, obj: any): Promise<boolean> => {
try {
const resp = await fetch(
route,
{
method: 'POST',
body: JSON.stringify(Object.assign(obj, { token }))
},
)
return Boolean(resp.status < 400 && resp.status >= 200)
} catch (err) {
console.error(err)
return false
}
}

const API_ROUTES = {
PAYMENT_SESSION: 'whatever'
}

const testObject = {
token: ''
}

const token = ''

const createOrder = async (): Promise<boolean> => { // <-- squiggles
try {
const response = await postWithToken(API_ROUTES.PAYMENT_SESSION, token || '',
testObject)
console.log("FROM HOOK", response)
return response
} catch (err: any) {
console.log(err)
}
}

Playground

The types in the example I created may be different (you'll need to sub with the actual types from your code) but you should get the idea. You can fix this by any of the following:

  1. Explicitly return something of the correct type from the catch block.
  2. Change your return type to Promise<CorrectType | undefined>.
  3. Move the error handling to the caller as suggested by goto1 in the comments.

Also note that as goto1 points out in the comments on the question, your hook isn't actually a hook (which is fine, but be careful of terminology).

Interface is allowing extra property when it is used as return type of a function

An inteface seems to accept extra property(ies) when it is assigned as a return of a function via a type.

In general, TypeScript uses structural typing, so it is perfectly fine to assign an object with additional properties to a Human interface.

const lui = {
name: "Lui",
age: 40
}

const human: Human = lui // works,

You can assign lui to variable human with type Human, because typeof lui is a sub-type and therefore has the same/compatible property members.

An exception to this rule are excess property checks for "freshly created object literals", that are intended as developer aid and prohibit extra properties to be added. The general thought here is, that you exactly know what properties you want, when you define a direct object literal with no other indirection (access of variables etc. to get that object value).

Excess property checks require an immediately following explicit type annotation at the variable, property or function dealing with the fresh object literal in order to work. Otherwise the object literal type doesn't count as "fresh" anymore (its type is widened). Let's check your examples to illustrate the concept.

const humanCreator: HumanCreator = (name, age) => ({
name,
age // No error. Why?
});

You can see this as an assignment of a type compatible function to the variable with type HumanCreator. The compiler looks at the function expression (name, age) => ({ name, age }), infers parameter types and makes sure that its return type is compatible to the variable type. And indeed - {name: string; age:number} return type is assignable to Human (structural typing).

const humanCreatorr: HumanCreator = (name, age): Human => ({
name,
age // Error
});

const humanCreatorrr = (): Human => ({
name: '',
age: 0 // Error
});

These cases are different, as you immediately annotate the functions with Human return type. There is no type inference for the return type necessary for the compiler. tl;dr To enable excess property checks, annotate an explicit type for your object literal as close as possible.

Further links

  • Excess property checks in TS specifications
  • Widened types
  • Good introduction article about excess property checks

How to type functions that return properties of objects based on argument provided?

You want to make propname's type a generic type parameter like K extends keyof OBJ (well, T, not OBJ, because OBJ is not a conventional type parameter name), and then use indexed access types to represent the property type: T[K].

This looks very close to the getProperty() function shown in the (now deprecated) documentation for indexed access types:

function getProperty<T, K extends keyof T>(o: T, propertyName: K): T[K] {
return o[propertyName]; // o[propertyName] is of type T[K]
}

And you can verify that it works:

const name = getProperty(obj, 'name');
// const name: string
console.log(name.toUpperCase()); // "HELLO"

Playground link to code



Related Topics



Leave a reply



Submit