-
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
Computed property union lifting #21070
Conversation
Still uses getSpreadType (with modifications) for combining multiple unioned computed properties. This likely has problems that will be exposed by more tests after I write them.
Also improve some names
@DanielRosenwasser you sounded interested in this too, although I don't know if you have time for a review right now. |
src/compiler/checker.ts
Outdated
if (!computedType) { | ||
return anyType; | ||
} | ||
else if (computedType.flags & TypeFlags.Literal) { |
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.
Both of the following two branches should just be the union and non-union cases under an isLiteralType(computedType)
check, no? It already handles unions/enums correctly. Unless you're explicitly excluding literal type unions which include null
/undefined
(which, IMO, should be handled with getNonNullableType
, if that's the case?)
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 didn't think about null/undefined, but it seems like they should be handled the same as other literals. isLiteralType
does this correctly.
hasComputedStringProperty = false; | ||
hasComputedNumberProperty = false; | ||
typeFlags = 0; | ||
updateIntermediateType(getSpreadType(intermediate, createObjectLiteralType(), node.symbol, propagatedFlags, /*fromComputedProperty*/ false)); |
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 location no longer does
hasComputedStringProperty = false;
hasComputedNumberProperty = false;
typeFlags = 0;
Is that intentional?
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.
Yes, this was a mistake because it reset these properties between spreads. I thought one of the baselines improved as a result, but I don't see it now. It was something like { [s1]: 1, ...o, [s2]: 2, ...o }
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.
computedPropertyNames5_ES5/ES6
src/compiler/checker.ts
Outdated
else if (computedType.flags & TypeFlags.Union && (computedType as UnionType).types.every(t => !!(t.flags & TypeFlags.Literal))) { | ||
type = isTypeAny(objectLiteralType) | ||
? objectLiteralType | ||
: (computedType as UnionType).types.every(t => !!(t.flags & TypeFlags.NumberLiteral)) && getIndexTypeOfType(objectLiteralType, IndexKind.Number) || |
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 code looks suspiciously similar to the above code in getTypeForBindingElement
maybe a helper for getTypeOfComputedDestructuredProperty
(akin to getTypeOfDestructuredProperty
) or something is in order?
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.
Good idea. The resulting function issues errors, so I called it checkComputedDestructuredProperty
} | ||
else { | ||
const text = getTextOfPropertyName(name); | ||
type = isTypeAny(objectLiteralType) |
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 know you just moved this from below, but I think this could probably be
type = isTypeAny(objectLiteralType) ? objectLiteralType : getTypeOfDestructuredProperty(objectLiteralType, name);
if (type === unknownType) { type = undefined; } // Not sure if this is necessary, but is the only difference in behavior between what was here and `getTypeOfDestructuredProperty`
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.
During checking, I'd rather check type flags than parse+serialise the name like isNumericLiteralName does.
@@ -0,0 +1,62 @@ | |||
tests/cases/conformance/es6/computedProperties/computedPropertyUnionLiftsToUnionType.ts(32,4): error TS2459: Type '{ a: string; } | { b: string; }' has no property '[ab]' and no string index signature. |
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.
has no property '[ab]' and no string index signature
- this error seems somewhat lacking. I'd expect either has no property 'a' and no string index signature
or has no property 'b' and no string index signature
, given the other changes you've made.
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 improved the error reporting and put it behind the noImplicitAny flag where it belongs.
@@ -144,7 +144,7 @@ export default { | |||
|
|||
=== tests/cases/compiler/comma.ts === | |||
export default { | |||
>{ ['foo']: 42} : { ['foo']: number; } | |||
>{ ['foo']: 42} : { ["foo"]: number; } |
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 see this is no longer preserving quotemarks. Does this affect declaration emit? For example, in
const ab: 'a' | 'b' = 'a';
export obj = { [ab]: "quas" };
what is the declaration emit? Is it export obj: { ['a']: string } | { ['b']: string }
?
Do we use single quotes on 'a'
and 'b'
, like the code does when the type is made, or no? I know it's best-effort, so I can understand if we don't.
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.
Actually, it seems to be using a completely different code path, because the output is { a: string } | { b: string }
. I guess this is OK, but I didn't expect it. See the latest commits for the declaration baseline.
src/compiler/checker.ts
Outdated
!((symbol as TransientSymbol).checkFlags & CheckFlags.Late) && | ||
!isWellKnownSymbolSyntactically((name as ComputedPropertyName).expression) && | ||
symbol.escapedName) { | ||
return '["' + unescapeLeadingUnderscores(symbol.escapedName) + '"]'; |
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.
Previously this would print [0]
if the computed property originally was a numeric literal. I know this is more correct with how we view the type (since keys are always strings), but it does feel unfortunate to be always quoting everything in the result. Maybe not quoting numeric names here would make me feel better? It would probably also reduce baseline noise.
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.
True on all points. It's easy enough to change, so I did. But as you noticed below, we aren't really trying to preserve what the user typed with this change.
src/compiler/checker.ts
Outdated
symbol.flags & SymbolFlags.Transient && | ||
!((symbol as TransientSymbol).checkFlags & CheckFlags.Late) && | ||
!isWellKnownSymbolSyntactically((name as ComputedPropertyName).expression) && | ||
symbol.escapedName) { |
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.
Can the escapedName
be the empty string? That would also be falsey 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.
Yes, { ['']: 1 }
is perfectly legal. I added !== undefined
here.
EDIT: hmm, almost. That bug uses a non-literal type so it may not be covered 🤔 |
Also add declaration baselines for computedPropertyUnionLiftsToUnionType
@Kovensky The new destructuring code was almost enough, I just needed to add a couple of cases. With the latest commit, #16789 is also fixed. |
Let callers rely on closure instead of passing in the source type unchanged to the lambda.
@weswigham I think this is ready for another look. |
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.
Minor style nit, but otherwise looks good. I'm not the biggest fan of a boolean parameter on getSpreadType
, but it'll do.
@@ -4391,22 +4400,28 @@ namespace ts { | |||
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) | |||
const name = declaration.propertyName || <Identifier>declaration.name; | |||
if (isComputedNonLiteralName(name)) { | |||
// computed properties with non-literal names are treated as 'any' | |||
return anyType; | |||
type = checkComputedDestructuredProperty(parentType, name, text => { |
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'd write a named function in scope for this lambda and then reuse it below in the else
branch.
The scope of this change is bigger than i previously anticipated. We need to discuss this change in the design meeting. |
- Issues with dynamic keys, microsoft/TypeScript#21070 - Issues with React.SFC array return type, microsoft/TypeScript#21699
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. |
Is this ever going to be implemented? |
I may actually be able to integrate fixes for the same issues into #26797. I'll look into it. |
It may also be possible to excise a less-ambitious chunk of this PR. It's on my list of bugs to fix for 3.1 or 3.2, but near the bottom. I'll talk with @weswigham to decide on an approach. |
Fixes #13948
Fixes #16789
Fixes #20725
Computed properties of object literals with a union type distribute across the type to create a union type. In the following example,
o
has the type{ a: string } | { b: string }
.Previously, computed properties caused the object literal type to have a index signature instead. This now happens only if the type of the computed property is not a literal type or union of literal types.
This PR also improves two other aspects of computed properties:
d1
,d1s
andd12
have the typeboolean
now, instead ofany
.Currently the quickinfo for
o1
is{ ["x"]: string }
. Previously it was{ [a]: string }
. Similarly, the quickinfo foro2
is{ ["a"]: string } | { ["b"]: string }
. Without improved printing it is{ [ab]: string } | { [ab]: string }
.