Skip to content
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

Closed
wants to merge 5 commits into from
Closed

Fix17667 #21425

wants to merge 5 commits into from

Conversation

Lazarus535
Copy link
Contributor

Fixes #17667

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.
@sandersn sandersn self-requested a review January 26, 2018 17:01
Copy link
Member

@sandersn sandersn left a 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);
Copy link
Member

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.

Copy link
Contributor Author

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);
Copy link
Member

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);

Copy link
Member

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;
Copy link
Member

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.

Copy link
Contributor Author

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeFlags.UnionOrIntersection

Copy link
Contributor Author

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++) {
Copy link
Member

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.

Copy link
Contributor Author

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;
Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor Author

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;
Copy link
Member

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. It looks like undefined gets removed. whether or not there is an initialiser. I don't think this is correct.
  2. Shouldn't there be an equivalent line for arrays? You can do things like ([first = 1, second = 2]) => first + second, right?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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);
Copy link
Member

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.

Copy link
Contributor Author

@Lazarus535 Lazarus535 Jan 30, 2018

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.

@typescript-bot
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants