-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Introduce literal freshness for literal enum member types #26556
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
Introduce literal freshness for literal enum member types #26556
Conversation
…rences in ambient const initializers
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.
Seems like a good change. It would be nice to know what it does, if anything, to the user tests (and maybe RWC tests).
src/compiler/checker.ts
Outdated
@@ -29247,7 +29254,7 @@ namespace ts { | |||
return grammarErrorAtPos(node, node.initializer.pos - equalsTokenLength, equalsTokenLength, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); | |||
} | |||
} | |||
if (node.initializer && !(isVarConst(node) && isStringOrNumberLiteralExpression(node.initializer))) { | |||
if (node.initializer && !(isVarConst(node) && (isStringOrNumberLiteralExpression(node.initializer) || isSimpleLiteralEnumReference(node.initializer)))) { | |||
// Error on equals token which immediate precedes the initializer |
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.
typo: immediately (and a couple lines below)
How were the RWC results? |
@typescript-bot test this |
Heya @weswigham, I've started to run the extended test suite on this PR at b5251b0. You can monitor the build here. It should now contribute to this PR's status checks. |
@RyanCavanaugh They look pretty good to me. Just some more refined enum types in some error messages and a declaration file. |
|
@@ -5864,9 +5864,9 @@ namespace ts { | |||
for (const declaration of symbol.declarations) { | |||
if (declaration.kind === SyntaxKind.EnumDeclaration) { | |||
for (const member of (<EnumDeclaration>declaration).members) { | |||
const memberType = getLiteralType(getEnumMemberValue(member)!, enumCount, getSymbolOfNode(member)); // TODO: GH#18217 | |||
const memberType = getFreshTypeOfLiteralType(getLiteralType(getEnumMemberValue(member)!, enumCount, getSymbolOfNode(member))); // TODO: GH#18217 |
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'm sort of surprised you're storing the fresh type in the symbol instead of the regular type. I would expect to see the regular type in the symbol and then freshness added by getTypeOfSymbol
.
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 is actually to mimic old behavior. Before, since we widened everywhere, it was always like the declared type was the fresh one, since it was only direct reference to the declaration that could be literal. And this makes perfect sense - the declaration is where the type flows into expressions from; to become a type it must go through a reference, so all that changed was where and when it widened.
@@ -8193,7 +8193,7 @@ namespace ts { | |||
const res = tryGetDeclaredTypeOfSymbol(symbol); | |||
if (res) { | |||
return checkNoTypeArguments(node, symbol) ? | |||
res.flags & TypeFlags.TypeParameter ? getConstrainedTypeVariable(<TypeParameter>res, node) : res : | |||
res.flags & TypeFlags.TypeParameter ? getConstrainedTypeVariable(<TypeParameter>res, node) : getRegularTypeOfLiteralType(res) : |
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.
Again, here I would just expect to find the regular type always.
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.
Again, as I said above, this makes more sense. Just like how string and number literals always start fresh, the enum declaration itself is the literal for an enum. We have the ability to widen and strip freshness expressed well in most places in typespace for literals, we don't exactly have a good idea of places where we could unwiden-when-entering-an-expression-context, by comparison. Here, specifically, we needed to strip freshness anew because when only expression literals were fresh, typeof and a literal in a literal type was the only way they could enter typespace (and we strip there). Enum members, however, do not use typeof (they effectively direct straight to the expression type - both typespace and expression enum ref types resolve from the same underlying cache field), so they need the freshness stripped whenever we reference them in typespace, which must happen via a reference, so here.
src/compiler/checker.ts
Outdated
function isSimpleLiteralEnumReference(expr: Expression) { | ||
if (!(isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression)))) return false; | ||
if (!isEntityNameExpression(expr.expression)) return false; | ||
return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLiteral); |
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 three return statements when one will do?
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.
Eh. Sometimes my preference is to break a long condition up using statements instead of ternaries/and+or just to make it a bit easier to read/edit (since it gets optimized to the same thing anyway, this just lets you place breakpoints between more things when debugging it). In fact, it's my normal - usually I only go back in and compact a set of statements once I'm confident I no longer need to adjust them. I can combine them, if you'd prefer.
@ahejlsberg @DanielRosenwasser let's discuss on Friday |
Update: Now that the other PR is in, this does fix #25990, which I've added a test for. |
@RyanCavanaugh I spoke offline, @ahejlsberg is on board with this (though he remarked on how we still don't have freshness for booleans, not that we have a great ask for it right now); what did you want to discuss on Friday? |
@weswigham good to merge 👍 |
To be more specific, we mark literal enum members as "fresh" at their declaration sites, and strip it in expression space when we usually strip freshness, and at any point when we would perform a lookup of the type via type-space.
Additionally, we allow enum references in ambient const initializers so that freshness can be preserved through declaration emit.
Fixes #22093.
Fixes #25990.