-
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
Fix17667 #21425
Fix17667 #21425
Conversation
Some parts are pretty "fragile" still. However bugs have clearly been identified and prototype fix works. Test cases are missing.
Notes about the bug: There were in fact two bugs, as was initially suggested by @Mhehazy. The first lead to properties not being found inside optional object binding patterns. The second lead to a function not being assignable to another when it actually should have. Both bugs were fixed. Addes more testcases.
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 don't think the basic approach to add undefined
recursively to the type is correct. I'll take a look tomorrow to see what will be the right approach.
@@ -8832,7 +8833,7 @@ namespace ts { | |||
const targetParams = target.parameters; | |||
for (let i = 0; i < checkCount; i++) { | |||
const sourceType = i < sourceMax ? getTypeOfParameter(sourceParams[i]) : getRestTypeOfSignature(source); | |||
const targetType = i < targetMax ? getTypeOfParameter(targetParams[i]) : getRestTypeOfSignature(target); | |||
const targetType: Type = i < targetMax ? getTypeOfParameter(targetParams[i]) : getRestTypeOfSignature(target); |
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.
This annotation isn't required, so please remove it.
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.
Yeah....had that in for "debugging" proposes and it slipped through. :-(
@@ -3954,7 +3954,8 @@ namespace ts { | |||
if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) { | |||
parentType = getNonNullableType(parentType); | |||
} | |||
const propType = getTypeOfPropertyOfType(parentType, text); | |||
const parentTypeNEUndefined = getTypeWithFacts(parentType, TypeFacts.NEUndefined); |
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.
what happens if you just overwrite parentType as in the previous statement?
parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined);
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.
(Later) I tried it and it improved one error baseline: downlevelLetConst16.
|
||
if (type.flags & TypeFlags.Union || type.flags & TypeFlags.Intersection) { | ||
for (let i = 0; i < propCount; i++) { | ||
const prop = rType.properties[i] as SymbolLinks; |
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.
Please use 4-space tabs.
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.
OK...
} | ||
} | ||
|
||
if (type.flags & TypeFlags.Union || type.flags & TypeFlags.Intersection) { |
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.
TypeFlags.UnionOrIntersection
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.
Jup...
const propCount = rType.properties.length; | ||
|
||
if (rType.flags & TypeFlags.Object) { | ||
for (let i = 0; i < propCount; i++) { |
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.
for (const prop in getPropertiesOfType(rType))
This might also collapse the object vs union/intersection code paths into one.
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 will look into that.
innerType = prop.type; | ||
} | ||
prop.type = innerType; | ||
rType.properties[i] = prop as Symbol; |
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.
You need to make a new type instead of overwriting the properties of an existing type. Somebody else might expect the properties of that type to remain the same.
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.
Same with symbol; you can't just update the type of an existing symbol.
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.
That seems reasonable. I should wipe Haskell out of my head for good. :-D
|
||
if (type.flags & TypeFlags.Union || type.flags & TypeFlags.Intersection) { | ||
for (let i = 0; i < propCount; i++) { | ||
const prop = rType.properties[i] as SymbolLinks; |
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.
This only works if the union has already had its properties resolved. getPropertiesOfType
does the right thing.
@@ -3954,7 +3954,8 @@ namespace ts { | |||
if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) { | |||
parentType = getNonNullableType(parentType); | |||
} | |||
const propType = getTypeOfPropertyOfType(parentType, text); | |||
const parentTypeNEUndefined = getTypeWithFacts(parentType, TypeFacts.NEUndefined); |
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.
- It looks like undefined gets removed. whether or not there is an initialiser. I don't think this is correct.
- Shouldn't there be an equivalent line for arrays? You can do things like
([first = 1, second = 2]) => first + second
, right?
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.
Here undefined should not be removed, but getTypeOfPropertyOfType should "see" through the levels of Union types (with undefined) and get the correct type of the property. Maybe the best approach here is to fix getTypeOfPropertyOfType instead of patching it here.
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.
The problem originates in getPropertyOfUnionOrIntersectionType()
, where the return type is undefined
because the property is partial (only present in one of the constituents of the union).
This makes sense for some cases, but not in this (buggy) one. Probably the most "elegant" solution is to pass some sort of flag down the line to indicate if we want partials resolved or not.
return type; | ||
} | ||
} | ||
|
||
function getTypeOfParameter(symbol: Symbol) { | ||
const type = getTypeOfSymbol(symbol); |
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 think that you need to follow getTypeOfSymbol
down to where the type of binding elements is recursively obtained, and change the innermost case, instead of making a post-processing step that introduces undefined
to the type after the fact.
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 did exactly that in one of my fix prototypes. It broke a dozen or so of testcases. Furthermore, i don't think this approach is correct. Consider this: getTypeOfSymbol
is also used "inside" a function to determine what type a argument has, as well as the signature of the function from the "outside". In this case the two are different. If there is an initializer, the argument in the function should not include undefined, because it is garantueed to never assume this value, but the signature should, because you can safely pass an undefined
. We already did that actually, but just for the most "outer level" (see getTypeOfParameter
), which is not correct as it has been previously. I agree that this solution (apart from the obvious improvements mentioned) is not perfect, but without changing typechecking on a more fundamental level, i don't see any other way.
Thanks for your contribution. This PR has not been updated in a while and cannot be automatically merged at the time being. For housekeeping purposes we are closing stale PRs. If you'd still like to continue working on this PR, please leave a message and one of the maintainers can reopen it. |
Fixes #17667