-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Contextually type implemented properties #6118
Conversation
This also applies when the assigned value would be widened to a type other than the contextually inherited type. For example, an inherited property `p: number` now correctly maintains its type even when initialised with `p = undefined` in a subclass. Previously it just widened to `any`.
Previously, the implemented property `i` took the type of its initialiser (`i = "foo"`). Now it takes the type of the property it inherits (`base1.i : any`).
@@ -2895,6 +2902,34 @@ namespace ts { | |||
return unknownType; | |||
} | |||
|
|||
function getTypeOfBasePropertyDeclaration(declaration: VariableLikeDeclaration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change to declaration: PropertyDeclaration
.
Also: 1. Address review comments. 2. Add test cases to implementedPropertyContextualTyping.* 3. Improve some tests: bestCommonTypeOfTuple2 and destructuringParameterDeclaration2. 4. Make some tests worse due to exposing bug 6190: requiredInitializedParameter.*. I'll fix it separately.
if (declaration.kind === SyntaxKind.PropertyDeclaration) { | ||
const type = getTypeOfBasePropertyDeclaration(<PropertyDeclaration>declaration); | ||
if (type) { | ||
mapper = createTypeMapper([undefinedType, nullType], [type, type]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the first argument never changes, you could factor it out to a constant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not so sure this works - doesn't this map all undefined and null types in the expression? So if I have
class Base {
a: (x: string) => void;
}
class Derived extends Base {
a = (x = null) => {}
}
won't x
in Derived
get the type (x: string) => void
meaning a
will have the type (x: (x: string) => void) => void
.
Maybe I misunderstood.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
Also, I think the TypeMappers are supposed to be for generics.
Yes, Anders explained contextual typing in person and I'll push new changes with |
Inside other getContextual* functions. This requires checking that getting contextual type is not in an inferential context.
Here's why var a: any[];
var b = a = [undefined, null];
But this doesn't trigger the implicit any check in I'm not sure whether this needs to be fixed, or whether it's an obscure enough corner case to let the technicality slide. |
After discussing with Mohamed, I think dropping the implicit any error is the right thing in this case. |
Observable types from contextually-typed expressions have always been very weird, so I don't think it's much to worry about. I think that code has changed meaning more than once since 1.0. |
Remind me, does this also fix #1373? |
1. Contextual typing of inherited properties with string literal types. This required no changes. 2. With strict-null, null/undefined should only get contextual types that include null / undefined. 3. Conflicting inherited properties from interfaces should not contextually type the implementation. Note that for (3), properties inherited from classes still contextually type the implementation, even if there are later conflicts with properties inherited from interfaces.
Use the contextual type from conflicting properties inherited from interfaces only if the types are equal. Previously these properties were not contextually typed.
Running on real world codeWe discussed this PR in the design meeting last week and decided that it was too inconsistent and didn't capture intent well enough. Instead, I tried a simpler approach that gives inherited properties exactly their inherited type (unless there is a type annotation). The change is found in the branch contextually-type-inherited-properties-WIP. I ran this branch on our real world code base consisting of Microsoft internal code as well as projects like Angular and Grunt. This showed that the new change broke more than it helped. Since both approaches failed, I am going to close this PR -- we may revisit this problem in the future. The goodThe improved typing exposed one bug that happens twice in the same project: interface I {
method(o: SomeType): string;
}
class C extends I {
method(o) {
return o.doesNotExist;
}
} Before this change, Both methods would have caught this bug. So would The badConsider the following example: namespace Base {
export interface Item {
a;
}
export abstract class Base {
abstract item: Item;
}
}
namespace User {
export interface Subitem extends Base.Item {
sub;
}
export interface Contract {
item: Subitem;
}
export function createC(): Contract {
}
function createSubitem(): Subitem {
// factory code here ...
}
class C extends Base.Base implements Contract {
item = createSubitem();
}
} The intended usage is something like let c: User.Contract = User.createC();
console.log(c.item.a);
console.log(c.item.sub); Unfortunately, if The uglyIn several projects, properties of type KnockoutObservable changed to |
👍 Thanks for reporting on what sorts of problems/improvements this brought. |
if Wouldn't that remove the bad and ugly part? |
@tinganho I believe that in general there is not a lowest subtype for a set of types. The type system does not provide a total ordering. |
Keywords: base derived methods properties property contextual contextually type subclass superclass |
Fixes #3667. Fixes #1373.
Interesting baseline changes in the latest commit:
is now illegal. This is a breaking change.
This is now legal because null gets contextually typed as an array, although node says "Cannot read property 'Symbol(Symbol.iterator)' of null" when running the code.
There are also a couple of implicit any errors that went missing from wideningTuples3 and wideningTuples5. I still need to find out why the error no longer fires:
Should have an an error when b implicitly gets the type
[any, any]
.