Skip to content

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

Merged
merged 8 commits into from
Sep 7, 2018

Conversation

weswigham
Copy link
Member

@weswigham weswigham commented Aug 20, 2018

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.

@weswigham weswigham changed the title Introduce literal freshness for literal enum members Introduce literal freshness for literal enum member types Aug 20, 2018
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.

Seems like a good change. It would be nice to know what it does, if anything, to the user tests (and maybe RWC tests).

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

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)

@RyanCavanaugh
Copy link
Member

How were the RWC results?

@weswigham
Copy link
Member Author

@typescript-bot test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 30, 2018

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.

@weswigham
Copy link
Member Author

weswigham commented Aug 30, 2018

@RyanCavanaugh They look pretty good to me. Just some more refined enum types in some error messages and a declaration file.

@RyanCavanaugh
Copy link
Member

:shipit:

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

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.

Copy link
Member Author

@weswigham weswigham Aug 31, 2018

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

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.

Copy link
Member Author

@weswigham weswigham Aug 31, 2018

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.

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

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?

Copy link
Member Author

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.

@RyanCavanaugh
Copy link
Member

@ahejlsberg @DanielRosenwasser let's discuss on Friday

@weswigham
Copy link
Member Author

Update: Now that the other PR is in, this does fix #25990, which I've added a test for.

@weswigham
Copy link
Member Author

@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?

@RyanCavanaugh
Copy link
Member

@weswigham good to merge 👍

@weswigham weswigham merged commit f8b6a8f into microsoft:master Sep 7, 2018
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.

5 participants