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

Distribute indexed accesses when simplifying them #26281

Merged
merged 14 commits into from
Aug 27, 2018

Conversation

weswigham
Copy link
Member

@weswigham weswigham commented Aug 7, 2018

And use the unknownType instead of the errorType when checking for property types if there is no error node, so the types combine appropriately under intersection and union operations.

Sketch of a fix for #26274 - I mostly want to investigate this change's impact on RWC, since the changes to, eg, index signature handling in intersections (it's more correct!) could be breaky. It's also likely correct to simply distribute on initial construction, rather than waiting for simplification - but I haven't looked much into that approach yet.

Fixes #26274
Fixes #25181
Fixes #26608

@microsoft microsoft deleted a comment from typescript-bot Aug 7, 2018
@weswigham
Copy link
Member Author

@typescript-bot test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 7, 2018

Heya @weswigham, I've started to run the extended test suite on this PR at c881d97. You can monitor the build here. It should now contribute to this PR's status checks.

@weswigham
Copy link
Member Author

Saw a pair of RWC breaks due to any as an index type not propagating (silently return the errorType had the effect of propagating it). Now T[any] is very explicitly also any. I think this is probably wrong, btw, but it's what we were doing before. It should probably also be unknown, despite how that may break some people.

@typescript-bot test this again btw

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 8, 2018

Heya @weswigham, I've started to run the extended test suite on this PR at 518046c. You can monitor the build here. It should now contribute to this PR's status checks.

};

function f1(s: Source, t: Target) {
t = s;
Copy link
Member Author

@weswigham weswigham Aug 8, 2018

Choose a reason for hiding this comment

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

I'm pretty sure this assignment is actually unsafe. Like, callback variance checks should prevent this from succeeding because Target's callbacks don't handle a wide enough range of things, but because we overwrite K with any for an erased comparison and WindowEventMap[any] is any it succeeds.

@weswigham
Copy link
Member Author

Last RWC break I saw had to do with flow control - we use getConstraintAtLocation to generate the initial type, but when we went to go look it up again to see if it differs, we weren't getting the locally constrained version anymore. This meant that types which simplified (which there are now more of, thanks to these identities) were narrowed against themselves (or rather: their simplified versions), resulting in somewhat incoherent results. Now we get the constraint at the referencing location whenever we lookup the initial or declared type again.

@typescript-bot test this again, since now there should be no new breaks 🤞

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 8, 2018

Heya @weswigham, I've started to run the extended test suite on this PR at 17d37ec. You can monitor the build here. It should now contribute to this PR's status checks.

@weswigham
Copy link
Member Author

@typescript-bot test this now that we've accepted RWC updates so hopefully they are passing now

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 8, 2018

Heya @weswigham, I've started to run the extended test suite on this PR at 17d37ec. You can monitor the build here. It should now contribute to this PR's status checks.

@weswigham
Copy link
Member Author

So, I've checked: This also fixes #25181, which was also due to an issue with reasoning over an index on an intersection! This seems way cleaner than my other candidate to fix that.

@weswigham
Copy link
Member Author

weswigham commented Aug 18, 2018

We talked about this today a bit - we think this is good, but we wanna fix the generic assignability issue one of the tests shows (T[K] being constrained to undefined to anything is assignable to it) and possibly sketch a patch to cover up the change in behavior for index signatures. (And run on DT to get a larger picture for how breaky it is)

@weswigham weswigham force-pushed the unknownify-accesses branch from ef9f6ee to 5bf01b3 Compare August 20, 2018 21:32
@weswigham
Copy link
Member Author

@typescript-bot test this again since I've fixed the constraint confusion issue

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 20, 2018

Heya @weswigham, I've started to run the extended test suite on this PR at c68d37a. You can monitor the build here. It should now contribute to this PR's status checks.

@weswigham
Copy link
Member Author

So I'm in the middle of running this on DT, and I've found a somewhat legitimate bug in a dt package activex-libreoffice in the same vein as #26274. They have a test of a generic createStruct function which takes a K extends keyof LibreOffice.StructNameMap. They then create a reflector over that K and attempt to call a createObject(obj: [(LibreOffice.ServicesNameMap & LibreOffice.StructNameMap)[K]]): void; using a [LibreOffice.StructNameMap[K]] for the argument type. Today this doesn't error, but on inspection you can tell that should ServicesNameMap and StructNameMap happen to overlap for some value of K, StructNameMap[K] alone does not satisfy the constraint (since it will be missing the ServicesNameMap bit).

I'm only 1/5th of the way done, so I can't say for certain just yet, but the DT changes have started off looking pretty good (given this is the only one thus far).

@RyanCavanaugh
Copy link
Member

Please add repro from #26608 if it's not already covered by something representative

@weswigham
Copy link
Member Author

@RyanCavanaugh added and confirmed for fixing #26608, too.

@weswigham
Copy link
Member Author

@typescript-bot test this once more for a good ol' checkmark

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 22, 2018

Heya @weswigham, I've started to run the extended test suite on this PR at 82b8a4f. You can monitor the build here. It should now contribute to this PR's status checks.

…redTypeRelatedTo to match the non-identity relations
@weswigham
Copy link
Member Author

@ahejlsberg I know in person I spoke about how the one crash I found in DT while working on this was due to the recursion guard not being triggered due to simplified indexed accesses bypassing the types we guard against - rather than modify simplification further, I realized the issue was really that isIdenticalTo didn't shuffle nearly as many types through recursiveTypeRelatedTo as the paths for other relationships did, which is why the issue only occurred when identity got checked. So rather than doing the large improvement of merging simplification and constraint fetching, I've left that for a later date and simply fixed isIdenticalTo by moving most of it into an identity-only branch of structuredTypeRelatedTo.

@typescript-bot test this again now that I've fixed the redux crash from DT (there's a repro in the tests now), just in case.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 23, 2018

Heya @weswigham, I've started to run the extended test suite on this PR at 98e9b3e. You can monitor the build here. It should now contribute to this PR's status checks.

function getConstraintForLocation(type: Type, node: Node): Type | undefined {
function getConstraintForLocation(type: Type, node?: Node): Type;
function getConstraintForLocation(type: Type | undefined, node?: Node): Type | undefined;
function getConstraintForLocation(type: Type, node?: Node): Type | undefined {
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be node: Node | undefined, i.e. not an optional parameter? It seems pointless to make it optional since the function will never do anything if you don't pass an argument for it.

Also, when might node be undefined? Not clear to me what those changes are accomplishing.

Copy link
Member Author

Choose a reason for hiding this comment

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

Didn't need to be undefine'd-able at all. I probably factored the place where it may have been undefinable so it wouldn't be while I was working on it - fix't.

function elaborateError(node: Expression | undefined, source: Type, target: Type): boolean {
if (!node) return false;
if (isOrHasGenericConditional(target)) return false;
Copy link
Member

Choose a reason for hiding this comment

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

Combine this with line above and have a single if statement.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

return type.simplified = mapType(objectType, t => getSimplifiedType(getIndexedAccessType(t, indexType)));
}
if (objectType.flags & TypeFlags.Intersection) {
return type.simplified = getIntersectionType(map((objectType as IntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType))));
Copy link
Member

Choose a reason for hiding this comment

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

I love how much simpler this is!

getInitialType(<VariableDeclaration | BindingElement>node) :
getAssignedType(node);
getAssignedType(node), reference);
Copy link
Member

Choose a reason for hiding this comment

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

reference is always defined, isn't it? Then why does getConstraintForLocation change to node?: Node ?

Copy link
Member Author

Choose a reason for hiding this comment

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

It doesn't now. ;) I think a prior iteration needed it. It's unchanged now.

function getInitialOrAssignedType(node: VariableDeclaration | BindingElement | Expression) {
return node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ?
function getInitialOrAssignedType(node: VariableDeclaration | BindingElement | Expression, reference: Node) {
return getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ?
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 this is the right thing, having poked at this during other bug fixes, but doesn't it cause a lot of type changes? Is that reflected in the baseline updates?

Copy link
Member Author

Choose a reason for hiding this comment

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

It is in the baselines - or rather, not doing this made the better distribution change some baselines, and then doing this changed them to the expected result.

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