Skip to content

Make property access type checking less restictive #39065

Closed
@PetaSoft

Description

@PetaSoft

Search Terms

TypeScript 3.9, property access, type checking, union types

Suggestion

I want to propose a less restictive way of type checking for property access. To illustrate the new way of type checking, I want to cite the example which is mentioned in the TypeScript Handbook (section "Advanced Types", subsection Type Guards and Differentiating Types):

interface Bird { 
  fly(): void; 
  layEggs(): void; 
} 
 
interface Fish { 
  swim(): void; 
  layEggs(): void; 
}
 
declare function getSmallPet(): Fish | Bird;

let pet = getSmallPet();

// Each of these property accesses will cause an error 
if (pet.swim) {
  pet.swim();
} else if (pet.fly) {
  pet.fly();
}

The property access pet.swim is not allowed because type Bird does not have a property swim. In Javascript these kinds of property access are allowed and if pet does not have that property the result value is undefined.
I think that a property access pet.swim should be allowed even if not all parts of the union type have that property. The type of the result would be () => void | undefined. The if(pet.swim) type guard would eleminte the undefined from the result type in the body of the if statement, and pet.swim() would be allowed, too. The same would be the case for if(pet.fly).
This less restictive type checking does not cause any runtime errors, and more Javascript code can be ported to Typescript without any change.
I want to mention that of course a statement pet.swim() without a type guard would still cause a compile time error as pet.swim is of type () => void | undefined and undefined is not callable.

Going one step further, it can also make sense to allow property access even if the type of the object does not have a member with that property name:

declare let fooBird: Bird;
let x = fooBird.swim;    // x is of type undefined

Of cource, once again x() is not allowed as undefined is not callable. These kinds of property access do not cause any runtime errors but the first design goal of TypeScript is probably too much affected if it would be allowed.
At least, a compiler option would be helpful which allows such less restictive property accesses. Additionally, the compiler could emit warnings for such property accesses instead of errors.

I want to mention that the solution presented in the TypeScript Handbook does no longer work because of stricter type checks in if statements:

let pet = getSmallPet();
if ((pet as Fish).swim) {     // error
  (pet as Fish).swim();
} else if ((pet as Bird).fly) {   // error
  (pet as Bird).fly();
}

One gets the error: "This condition will always return true since the function is always defined".
It can be fixed using the in operator:

if ('swim' in pet) {
  pet.swim();
} else if ('fly' in pet) { 
  pet.fly();
}

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DeclinedThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions