-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
More prolific literal types #19587
More prolific literal types #19587
Conversation
…across intersections or unions
Apart form the test change, do you see any breaks in the RWC baselines? |
@mhegazy Two fewer errors (one each in two projects) due to the more permissive comparable relationship (desirable - the casts should likely have been allowed, but were prevented due to incompatibilities on inner types, which are now allowed as the inner types are compared bidirectionally just like the top level), and a bunch of altered error messages which now contain literals, but no new breaks. |
@ahejlsberg I've incorporated the changes you suggested in person. |
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.
Two questions and one naming suggestion.
@@ -9491,7 +9497,7 @@ namespace ts { | |||
} | |||
// Report constraint errors only if the constraint is not the empty object type | |||
const reportConstraintErrors = reportErrors && constraint !== emptyObjectType; | |||
if (result = isRelatedTo(constraint, target, reportConstraintErrors)) { | |||
if (result = isRelatedToMonodirectional(constraint, target, reportConstraintErrors)) { |
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.
why only here and in the union/intersection type handling? why does it become bidirectional everywhere else?
} | ||
|
||
function isLiteralContextualType(contextualType: Type) { | ||
function isLiteralContextualType(contextualType: Type, candidateLiteral: Type): boolean { |
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.
Consider flipping the parameters and changing the name to canBeContextuallyTypedAsLiteral (or something similar; probably the parameter names would need to change too.)
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.
@rbuckton 's dynamic names PR already includes both of those things, actually.
Type '() => string | number' is not assignable to type '() => number'. | ||
Type 'string | number' is not assignable to type 'number'. | ||
Type 'string' is not assignable to type 'number'. | ||
tests/cases/conformance/expressions/contextualTyping/arrayLiteralExpressionContextualTyping.ts(8,5): error TS2322: Type '[1, 2, 3, string]' is not assignable to type '[number, number, 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.
hm, it would be nicer if this change didn't happen.
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.
Why? 1
, 2
, and 3
are verbatim what was written and are all contextually typed by number
. It's pretty reasonable, IMO.
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.
People expect to see types in a type error, and don't think of 1
as a type. It's not the end of the world; it remains pretty clear because this only happens with contextual typing when the value is sitting right there in plain sight.
I think there is some more fundamental thinking we need to do around the comparable relationship before this is ready to go. The comparable relationship should hold if some value exists that belongs to both types. For object types I think this means we should consider them comparable unless we can prove they're disjoint. I'm not sure that's what we currently do, even with this PR. For example, when a member is present in one object type but not the other, we should consider it a match (because we can't prove that it isn't). Also, the relationship should be commutative (bidirectional), which it isn't, even with the changes in this PR (e.g. |
Any given type variables Maybe its better to think of this in terms of troubleshooting this specific issue with the comparable relationship: declare enum Strings {
A = "a",
B = "b"
}
declare let a: { y: 1 | 2, z: "b" | "c" };
declare let b: { y: number, z: Strings.A | Strings.B };
a = b as typeof a;
b = a as typeof b; Those casts fail - even though declare let az: "b" | "c";
declare let bz: Strings.A | Strings.B;
az = bz as typeof az; // succeeds on Strings.A | Strings.B -> "b | "c", fails on "b" | "c" -> Strings.A | Strings.B overall success
bz = az as typeof bz;
declare let ay: 1 | 2;
declare let by: number;
ay = by as typeof ay; // fails number -> 1 | 2, succeeds on 1 | 2 -> number, overall success
by = ay as typeof by; (all of those casts work).
Ergo, Right now what we really have is a |
Superseded by #19966 |
This is an iteration on #19322.
Building on what @ahejlsberg suggested in reviewing #19322, this fixes #16457 by, rather than doing any special-casing of contextual types, causing us to infer literals so long as they are domain-appropriate for a given position. This means that if a number type is contextually typed by a number type, then we retain a literal, and the same each for string and boolean literals.
This necessitated fixing the
comparable
relationship to be deeply bidirectional (instead of just bidirectional at the top level); as casts between potentially compatible object types would fail thanks to the extra literal types without the deep bidirectionality (this was visible within our own codebase).Most of the baseline diffs are just new literal types materializing before they are assigned and disappear, however as mentioned in this comment, there are some ramifications where what was previously OK is now an error due to the stricter inference. I think could revert to the old behavior (or similar to it) for type parameters; however this could be an improvement (and is certainly more consistent). Perf-wise, I actually don't see any difference; these types were always there before, we just widened them, whereas now we widen them a step later under most circumstances.
By request, fixes #19632. Also fixes #19837.