Skip to content

isolatedModules errors for non-literal enum initializers - #56736

Merged
Andrew Branch (andrewbranch) merged 11 commits into
microsoft:mainfrom
frigus02:enum-initializers
Mar 20, 2024
Merged

isolatedModules errors for non-literal enum initializers#56736
Andrew Branch (andrewbranch) merged 11 commits into
microsoft:mainfrom
frigus02:enum-initializers

Conversation

@frigus02

@frigus02Jan Kühle (frigus02) commented Dec 11, 2023

Copy link
Copy Markdown
Contributor

Fixes#56153

Adds 2 new errors under isolatedModules only:

  • (1) When an enum initializer expression that isn't a string literal has a string type
  • (2) When an enum member without an initialization expression follows an enum member with an initialization expression that isn't a numeric literal

Ran the new checks across our code base. I wanted to check if we need to support computed values based on local identifiers. But we have so few errors, I don't think this is necessary:

ErrorPercent of TS projects failing with the new error
(1) identifier imported from another file0.0059%
(1) identifier local to the same file0.0025%
(2)0.0002%
sum0.0085%

@typescript-botTypeScript Bot (typescript-bot) added the For Backlog Bug PRs that fix a backlog bug label Dec 11, 2023
@frigus02
Jan Kühle (frigus02) marked this pull request as ready for review December 11, 2023 18:50
@jakebailey

Copy link
Copy Markdown
Member

@typescript-bot

TypeScript Bot (typescript-bot) commented Dec 11, 2023

Copy link
Copy Markdown
Contributor

Heya Jake Bailey (@jakebailey), I've started to run the regular perf test suite on this PR at c51645a. You can monitor the build here.

@typescript-bot

TypeScript Bot (typescript-bot) commented Dec 11, 2023

Copy link
Copy Markdown
Contributor

Heya Jake Bailey (@jakebailey), I've started to run the diff-based top-repos suite on this PR at c51645a. You can monitor the build here.

Update: The results are in!

@typescript-bot

TypeScript Bot (typescript-bot) commented Dec 11, 2023

Copy link
Copy Markdown
Contributor

Heya Jake Bailey (@jakebailey), I've started to run the parallelized Definitely Typed test suite on this PR at c51645a. You can monitor the build here.

Update: The results are in!

@typescript-bot

TypeScript Bot (typescript-bot) commented Dec 11, 2023

Copy link
Copy Markdown
Contributor

Heya Jake Bailey (@jakebailey), I've started to run the diff-based user code test suite on this PR at c51645a. You can monitor the build here.

Update: The results are in!

@typescript-bot

TypeScript Bot (typescript-bot) commented Dec 11, 2023

Copy link
Copy Markdown
Contributor

Heya Jake Bailey (@jakebailey), I've started to run the tarball bundle task on this PR at c51645a. You can monitor the build here.

@typescript-bot

TypeScript Bot (typescript-bot) commented Dec 11, 2023

Copy link
Copy Markdown
Contributor

Hey Jake Bailey (@jakebailey), I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
"devDependencies": {
"typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/159054/artifacts?artifactName=tgz&fileId=FA18007074EDA659A8573C547724ABF39560091EFF9B4B80CE963603F3AF048F02&fileName=/typescript-5.4.0-insiders.20231211.tgz"
}
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/pr-build@5.4.0-pr-56736-6".;

@typescript-bot

Copy link
Copy Markdown
Contributor

Jake Bailey (@jakebailey) Here are the results of running the user test suite comparing main and refs/pull/56736/merge:

There were infrastructure failures potentially unrelated to your change:

  • 1 instance of "Package install failed"

Otherwise...

Something interesting changed - please have a look.

Details

puppeteer

packages/browsers/test/src/tsconfig.json

@typescript-bot

Copy link
Copy Markdown
Contributor

Hey Jake Bailey (@jakebailey), the results of running the DT tests are ready.
Everything looks the same!
You can check the log here.

@typescript-bot

Copy link
Copy Markdown
Contributor

Jake Bailey (@jakebailey) Here are the results of running the top-repos suite comparing main and refs/pull/56736/merge:

Everything looks good!

@jakebailey

Copy link
Copy Markdown
Member

TypeScript Bot (@typescript-bot) perf test this

@typescript-bot

TypeScript Bot (typescript-bot) commented Dec 11, 2023

Copy link
Copy Markdown
Contributor

Heya Jake Bailey (@jakebailey), I've started to run the regular perf test suite on this PR at c51645a. You can monitor the build here.

Update: The results are in!

@typescript-bot

Copy link
Copy Markdown
Contributor

Jake Bailey (@jakebailey)
The results of the perf run you requested are in!

Here they are:

Compiler

Comparison Report - baseline..pr
MetricbaselineprDeltaBestWorstp-value
Angular - node (v18.15.0, x64)
Memory used295,432k (± 0.01%)295,389k (± 0.00%)-43k (- 0.01%)295,372k295,409kp=0.031 n=6
Parse Time2.64s (± 0.37%)2.65s (± 0.21%)~2.64s2.65sp=0.201 n=6
Bind Time0.82s (± 0.63%)0.82s (± 0.00%)~0.82s0.82sp=0.174 n=6
Check Time8.13s (± 0.20%)8.15s (± 0.41%)~8.11s8.20sp=0.292 n=6
Emit Time7.08s (± 0.34%)7.09s (± 0.32%)~7.05s7.11sp=0.288 n=6
Total Time18.67s (± 0.13%)18.71s (± 0.25%)~18.63s18.77sp=0.143 n=6
Compiler-Unions - node (v18.15.0, x64)
Memory used192,845k (± 1.24%)194,751k (± 1.49%)~191,354k197,322kp=0.688 n=6
Parse Time1.35s (± 1.11%)1.36s (± 1.11%)~1.34s1.38sp=0.737 n=6
Bind Time0.72s (± 0.00%)0.72s (± 0.00%)~0.72s0.72sp=1.000 n=6
Check Time9.26s (± 0.30%)9.27s (± 0.37%)~9.21s9.30sp=0.332 n=6
Emit Time2.63s (± 0.99%)2.62s (± 0.20%)~2.61s2.62sp=0.368 n=6
Total Time13.96s (± 0.36%)13.97s (± 0.27%)~13.90s14.00sp=0.747 n=6
Monaco - node (v18.15.0, x64)
Memory used347,374k (± 0.00%)347,382k (± 0.00%)~347,366k347,393kp=0.335 n=6
Parse Time2.46s (± 0.76%)2.46s (± 0.48%)~2.44s2.47sp=1.000 n=6
Bind Time0.92s (± 0.56%)0.93s (± 0.88%)~0.92s0.94sp=0.523 n=6
Check Time6.91s (± 0.53%)6.90s (± 0.24%)~6.87s6.91sp=1.000 n=6
Emit Time4.05s (± 0.46%)4.05s (± 0.56%)~4.03s4.09sp=0.459 n=6
Total Time14.34s (± 0.43%)14.34s (± 0.14%)~14.31s14.36sp=0.462 n=6
TFS - node (v18.15.0, x64)
Memory used302,636k (± 0.00%)302,647k (± 0.00%)~302,627k302,666kp=0.230 n=6
Parse Time1.99s (± 1.02%)2.00s (± 1.84%)~1.94s2.03sp=0.625 n=6
Bind Time1.01s (± 1.25%)1.01s (± 1.53%)~1.00s1.04sp=0.741 n=6
Check Time6.27s (± 0.22%)6.27s (± 0.55%)~6.24s6.33sp=0.292 n=6
Emit Time3.57s (± 0.42%)3.57s (± 0.51%)~3.55s3.60sp=0.805 n=6
Total Time12.84s (± 0.17%)12.85s (± 0.40%)~12.77s12.91sp=0.686 n=6
material-ui - node (v18.15.0, x64)
Memory used506,784k (± 0.01%)506,797k (± 0.01%)~506,753k506,839kp=0.471 n=6
Parse Time2.58s (± 0.68%)2.58s (± 0.38%)~2.57s2.59sp=0.548 n=6
Bind Time0.99s (± 1.18%)0.99s (± 0.76%)~0.98s1.00sp=0.734 n=6
Check Time16.97s (± 0.26%)16.89s (± 0.36%)-0.08s (- 0.45%)16.84s17.01sp=0.045 n=6
Emit Time0.00s (± 0.00%)0.00s (± 0.00%)~0.00s0.00sp=1.000 n=6
Total Time20.54s (± 0.24%)20.46s (± 0.30%)-0.08s (- 0.39%)20.40s20.57sp=0.045 n=6
xstate - node (v18.15.0, x64)
Memory used512,765k (± 0.01%)512,820k (± 0.01%)~512,740k512,914kp=0.230 n=6
Parse Time3.28s (± 0.32%)3.27s (± 0.17%)~3.27s3.28sp=1.000 n=6
Bind Time1.53s (± 0.27%)1.54s (± 0.41%)+0.01s (+ 0.54%)1.53s1.55sp=0.033 n=6
Check Time2.80s (± 0.18%)2.81s (± 0.30%)+0.01s (+ 0.42%)2.81s2.83sp=0.020 n=6
Emit Time0.07s (± 0.00%)0.07s (± 0.00%)~0.07s0.07sp=1.000 n=6
Total Time7.68s (± 0.23%)7.70s (± 0.14%)+0.02s (+ 0.28%)7.69s7.72sp=0.027 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Angular - node (v18.15.0, x64)
  • Compiler-Unions - node (v18.15.0, x64)
  • Monaco - node (v18.15.0, x64)
  • TFS - node (v18.15.0, x64)
  • material-ui - node (v18.15.0, x64)
  • xstate - node (v18.15.0, x64)
BenchmarkNameIterations
Currentpr6
Baselinebaseline6

tsserver

Comparison Report - baseline..pr
MetricbaselineprDeltaBestWorstp-value
Compiler-UnionsTSServer - node (v18.15.0, x64)
Req 1 - updateOpen2,340ms (± 0.91%)2,343ms (± 0.78%)~2,319ms2,363msp=0.810 n=6
Req 2 - geterr5,407ms (± 1.63%)5,416ms (± 1.53%)~5,328ms5,530msp=0.809 n=6
Req 3 - references326ms (± 1.61%)328ms (± 1.35%)~320ms333msp=0.809 n=6
Req 4 - navto277ms (± 1.19%)277ms (± 1.20%)~273ms280msp=1.000 n=6
Req 5 - completionInfo count1,356 (± 0.00%)1,356 (± 0.00%)~1,3561,356p=1.000 n=6
Req 5 - completionInfo87ms (± 6.13%)87ms (± 6.29%)~82ms93msp=0.934 n=6
CompilerTSServer - node (v18.15.0, x64)
Req 1 - updateOpen2,476ms (± 0.98%)2,468ms (± 0.66%)~2,442ms2,490msp=0.369 n=6
Req 2 - geterr4,133ms (± 1.99%)4,161ms (± 1.80%)~4,059ms4,219msp=0.575 n=6
Req 3 - references340ms (± 1.65%)339ms (± 1.64%)~334ms348msp=0.624 n=6
Req 4 - navto286ms (± 1.16%)286ms (± 1.32%)~283ms293msp=0.803 n=6
Req 5 - completionInfo count1,518 (± 0.00%)1,518 (± 0.00%)~1,5181,518p=1.000 n=6
Req 5 - completionInfo83ms (± 7.26%)81ms (± 7.31%)~77ms89msp=0.676 n=6
xstateTSServer - node (v18.15.0, x64)
Req 1 - updateOpen2,597ms (± 0.52%)2,600ms (± 0.58%)~2,575ms2,616msp=0.575 n=6
Req 2 - geterr1,707ms (± 1.93%)1,672ms (± 2.35%)-35ms (- 2.04%)1,639ms1,723msp=0.045 n=6
Req 3 - references103ms (± 0.87%)119ms (± 6.32%)🔻+16ms (+15.86%)104ms123msp=0.007 n=6
Req 4 - navto365ms (± 0.21%)364ms (± 0.22%)~363ms365msp=0.383 n=6
Req 5 - completionInfo count2,073 (± 0.00%)2,073 (± 0.00%)~2,0732,073p=1.000 n=6
Req 5 - completionInfo307ms (± 1.39%)305ms (± 2.10%)~295ms313msp=0.688 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • CompilerTSServer - node (v18.15.0, x64)
  • Compiler-UnionsTSServer - node (v18.15.0, x64)
  • xstateTSServer - node (v18.15.0, x64)
BenchmarkNameIterations
Currentpr6
Baselinebaseline6

Startup

Comparison Report - baseline..pr
MetricbaselineprDeltaBestWorstp-value
tsc-startup - node (v18.15.0, x64)
Execution time152.96ms (± 0.22%)153.06ms (± 0.20%)+0.10ms (+ 0.06%)151.94ms157.10msp=0.001 n=600
tsserver-startup - node (v18.15.0, x64)
Execution time228.19ms (± 0.17%)227.90ms (± 0.14%)-0.29ms (- 0.13%)226.63ms231.83msp=0.000 n=600
tsserverlibrary-startup - node (v18.15.0, x64)
Execution time229.68ms (± 0.18%)229.68ms (± 0.27%)~228.11ms249.88msp=0.242 n=600
typescript-startup - node (v18.15.0, x64)
Execution time229.71ms (± 0.17%)229.67ms (± 0.18%)~227.99ms234.66msp=0.132 n=600
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • tsc-startup - node (v18.15.0, x64)
  • tsserver-startup - node (v18.15.0, x64)
  • tsserverlibrary-startup - node (v18.15.0, x64)
  • typescript-startup - node (v18.15.0, x64)
BenchmarkNameIterations
Currentpr6
Baselinebaseline6

Developer Information:

Download Benchmarks

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks Jan Kühle (@frigus02)!

Whatever we do in evaluatesToStringLiteral and evaluatesToNumericLiteral here will become the minimum requirement for all third-party compilers to implement to guide their own emit. I think what you have is pretty reasonable, but we should get the team, and other compiler maintainers, to weigh in. Nicolò Ribaudo (@nicolo-ribaudo)Evan Wallace (@evanw)Donny/강동윤 (@kdy1)

Comment threadsrc/compiler/checker.ts Outdated
Comment threadsrc/compiler/checker.ts Outdated
Comment threadsrc/compiler/checker.ts Outdated
Comment threadsrc/compiler/diagnosticMessages.json Outdated
@jakebailey

Copy link
Copy Markdown
Member

At least for esbuild, my understanding is that it doesn't really care about enum stuff that violates isolatedModules; for example, TS itself uses exported const enums and to do so disables isolatedModules and esbuild will happily inline them (which is good).

The new test cases don't fail currently in esbuild, so I'm not sure there's actually any work to be done outside of TS if this PR just intends to add more errors.

@frigus02Jan Kühle (frigus02) left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks for the review, Andrew and Jake. I addressed the comments.

This implementation is quite thorough. I wanted to avoid too many new errors in our code base when turning on isolatedModules, so I tried to support as much as possible. I'm happy to simplify it if you like, though.

Comment threadsrc/compiler/checker.ts Outdated
Comment threadsrc/compiler/diagnosticMessages.json Outdated
Comment threadsrc/compiler/checker.ts Outdated
Comment threadsrc/compiler/checker.ts Outdated
@frigus02

Copy link
Copy Markdown
ContributorAuthor

It's probably too late to get this into 5.4 now, is it? That's alright.

Rather than introducing 2 new functions, which duplicate some of the `evaluate` logic, we could also extend `evaluate` to return whether the evaluation was purely syntactic, e.g.:
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index b0ea7ea9ff..78cab2d428 100644
--- a/src/compiler/checker.ts+++ b/src/compiler/checker.ts@@ -38687,7 +38687,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (isConstContext(node) || isTemplateLiteralContext(node) || someType(getContextualType(node, /*contextFlags*/ undefined) || unknownType, isTemplateLiteralContextualType)) {
return getTemplateLiteralType(texts, types);
}
- const evaluated = node.parent.kind !== SyntaxKind.TaggedTemplateExpression && evaluateTemplateExpression(node);+ const evaluated = node.parent.kind !== SyntaxKind.TaggedTemplateExpression && evaluateTemplateExpression(node).value;
return evaluated ? getFreshTypeOfLiteralType(getStringLiteralType(evaluated)) : stringType;
}
@@ -45010,15 +45010,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) {
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
let autoValue: number | undefined = 0;
+ let previous: EnumMember | undefined;
for (const member of node.members) {
- const value = computeMemberValue(member, autoValue);+ const value = computeMemberValue(member, autoValue, previous);
getNodeLinks(member).enumMemberValue = value;
autoValue = typeof value === "number" ? value + 1 : undefined;
+ previous = member;
}
}
}
- function computeMemberValue(member: EnumMember, autoValue: number | undefined) {+ function computeMemberValue(member: EnumMember, autoValue: number | undefined, previous: EnumMember | undefined) {
if (isComputedNonLiteralName(member.name)) {
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
}
@@ -45040,26 +45042,41 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// If the member is the first member in the enum declaration, it is assigned the value zero.
// Otherwise, it is assigned the value of the immediately preceding member plus one, and an error
// occurs if the immediately preceding member is not a constant enum member.
- if (autoValue !== undefined) {- return autoValue;- }+ if (autoValue === undefined) {
error(member.name, Diagnostics.Enum_member_must_have_initializer);
return undefined;
}
+ if (getIsolatedModules(compilerOptions) && previous?.initializer) {+ const previousResult = evaluate(previous.initializer);+ if (typeof previousResult.value !== "number" || !previousResult.syntactic) {+ error(+ member.name,+ Diagnostics.Enum_member_following_a_non_literal_numeric_member_must_have_an_initializer_when_isolatedModules_is_enabled,+ );+ }+ }+ return autoValue;+ }
function computeConstantValue(member: EnumMember): string | number | undefined {
const isConstEnum = isEnumConst(member.parent);
const initializer = member.initializer!;
- const value = evaluate(initializer, member);- if (value !== undefined) {- if (isConstEnum && typeof value === "number" && !isFinite(value)) {+ const result = evaluate(initializer, member);+ if (result.value !== undefined) {+ if (isConstEnum && typeof result.value === "number" && !isFinite(result.value)) {
error(
initializer,
- isNaN(value) ?+ isNaN(result.value) ?
Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN :
Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value,
);
}
+ else if (getIsolatedModules(compilerOptions) && typeof result.value === "string" && !result.syntactic) {+ error(+ initializer,+ Diagnostics.A_string_member_initializer_in_a_enum_declaration_can_only_use_constant_expressions_when_isolatedModules_is_enabled,+ );+ }
}
else if (isConstEnum) {
error(initializer, Diagnostics.const_enum_member_initializers_must_be_constant_expressions);
@@ -45070,13 +45087,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
else {
checkTypeAssignableTo(checkExpression(initializer), numberType, initializer, Diagnostics.Type_0_is_not_assignable_to_type_1_as_required_for_computed_enum_member_values);
}
- return value;+ return result.value;+ }++ interface EvaluationResult<T> {+ value: T;+ syntactic: boolean;
}
- function evaluate(expr: Expression, location?: Declaration): string | number | undefined {+ function evaluate(expr: Expression, location?: Declaration): EvaluationResult<string | number | undefined> {+ let syntactic = true;+ let value = evaluateInternal(expr, location);+ return {value, syntactic};++ function evaluateInternal(expr: Expression, location?: Declaration): string | number | undefined {
switch (expr.kind) {
case SyntaxKind.PrefixUnaryExpression:
- const value = evaluate((expr as PrefixUnaryExpression).operand, location);+ const value = evaluateInternal((expr as PrefixUnaryExpression).operand, location);
if (typeof value === "number") {
switch ((expr as PrefixUnaryExpression).operator) {
case SyntaxKind.PlusToken:
@@ -45089,8 +45116,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
break;
case SyntaxKind.BinaryExpression:
- const left = evaluate((expr as BinaryExpression).left, location);- const right = evaluate((expr as BinaryExpression).right, location);+ const left = evaluateInternal((expr as BinaryExpression).left, location);+ const right = evaluateInternal((expr as BinaryExpression).right, location);
if (typeof left === "number" && typeof right === "number") {
switch ((expr as BinaryExpression).operatorToken.kind) {
case SyntaxKind.BarToken:
@@ -45131,13 +45158,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.NoSubstitutionTemplateLiteral:
return (expr as StringLiteralLike).text;
case SyntaxKind.TemplateExpression:
- return evaluateTemplateExpression(expr as TemplateExpression, location);+ const templateResult = evaluateTemplateExpression(expr as TemplateExpression, location);+ syntactic &&= templateResult.syntactic;+ return templateResult.value;
case SyntaxKind.NumericLiteral:
checkGrammarNumericLiteral(expr as NumericLiteral);
return +(expr as NumericLiteral).text;
case SyntaxKind.ParenthesizedExpression:
- return evaluate((expr as ParenthesizedExpression).expression, location);+ return evaluateInternal((expr as ParenthesizedExpression).expression, location);
case SyntaxKind.Identifier: {
+ syntactic = false;
const identifier = expr as Identifier;
if (isInfinityOrNaNString(identifier.escapedText) && (resolveEntityName(identifier, SymbolFlags.Value, /*ignoreErrors*/ true) === getGlobalSymbol(identifier.escapedText, SymbolFlags.Value, /*diagnostic*/ undefined))) {
return +(identifier.escapedText);
@@ -45145,6 +45175,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// falls through
}
case SyntaxKind.PropertyAccessExpression:
+ syntactic = false;
if (isEntityNameExpression(expr)) {
const symbol = resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true);
if (symbol) {
@@ -45154,13 +45185,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (isConstantVariable(symbol)) {
const declaration = symbol.valueDeclaration;
if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && (!location || declaration !== location && isBlockScopedNameDeclaredBeforeUse(declaration, location))) {
- return evaluate(declaration.initializer, declaration);+ return evaluateInternal(declaration.initializer, declaration);
}
}
}
}
break;
case SyntaxKind.ElementAccessExpression:
+ syntactic = false;
const root = (expr as ElementAccessExpression).expression;
if (isEntityNameExpression(root) && isStringLiteralLike((expr as ElementAccessExpression).argumentExpression)) {
const rootSymbol = resolveEntityName(root, SymbolFlags.Value, /*ignoreErrors*/ true);
@@ -45176,6 +45208,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
return undefined;
}
+ }
function evaluateEnumMember(expr: Expression, symbol: Symbol, location: Declaration) {
const declaration = symbol.valueDeclaration;
@@ -45190,17 +45223,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return getEnumMemberValue(declaration as EnumMember);
}
- function evaluateTemplateExpression(expr: TemplateExpression, location?: Declaration) {- let result = expr.head.text;+ function evaluateTemplateExpression(expr: TemplateExpression, location?: Declaration): EvaluationResult<string | undefined> {+ let value = expr.head.text;+ let syntactic = true;
for (const span of expr.templateSpans) {
- const value = evaluate(span.expression, location);- if (value === undefined) {- return undefined;+ const evaluateResult = evaluate(span.expression, location);+ syntactic &&= evaluateResult.syntactic;+ if (evaluateResult.value === undefined) {+ return {value: undefined, syntactic};
}
- result += value;- result += span.literal.text;+ value += evaluateResult.value;+ value += span.literal.text;
}
- return result;+ return {value, syntactic};
}
function checkEnumDeclaration(node: EnumDeclaration) {

@andrewbranch

Copy link
Copy Markdown
Member

Yeah, sorry for the delay. I was holding off close to the 5.4 beta when I noticed that there would be significant merge conflicts with #53463, but it turned out that didn’t go into the beta either.

Comment threadtests/cases/compiler/enumWithNonLiteralStringInitializer.ts Outdated
Comment threadtests/cases/compiler/enumNoInitializerFollowsNonLiteralInitializer.ts Outdated
Comment threadsrc/compiler/checker.ts Outdated
@frigus02

Copy link
Copy Markdown
ContributorAuthor

Hi Andrew Branch (@andrewbranch). Congrats to the TS 5.4 release! Do you think we can merge some version of this PR in time for 5.5? 😊

@frigus02

Copy link
Copy Markdown
ContributorAuthor

Thank you for #57686, Andrew. I update the PR and it's much smaller now.

Comment threadsrc/compiler/utilities.ts Outdated
Comment threadsrc/compiler/diagnosticMessages.json Outdated
"category": "Error",
"code": 18054
},
"A string member initializer in a enum declaration can only use constant expressions when 'isolatedModules' is enabled.": {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Paraphrasing a suggestion from Daniel Rosenwasser (@DanielRosenwasser):

Suggested change
"A string member initializer in a enum declaration can only use constant expressions when 'isolatedModules' is enabled.": {
"'{0}' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled.": {

We could totally do a quick fix to wrap it in a template literal but I think this error is going to be so niche that I’m not sure it’s worth it.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I like the suggested error message. But what do I substitute {0} with? The initializer can be a relatively complex expression. Is there a utility function to stringify that? Or should I use the enum member name or even the computed value?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I forgot to include that; Daniel’s suggestion was 'EnumName.MemberName'

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Sounds good. Do we want to handle member names that are not valid identifier? E.g.:

import{bar}from'./bar';enumFoo{['not an identifier']=bar}// 'Foo.not an identifier' has a string type, ...// 'Foo["not an identifier"]' has a string type, ...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I updated the error message and ignored the non-identifier case for now, because it seems like an extremely rare case. That means it would print 'Foo.not an identifier' in the error message.

@andrewbranch

Copy link
Copy Markdown
Member

TypeScript Bot (@typescript-bot) test top200

@typescript-bot

TypeScript Bot (typescript-bot) commented Mar 19, 2024

Copy link
Copy Markdown
Contributor

Starting jobs; this comment will be updated as builds start and complete.

CommandStatusResults
test top200✅ Started✅ Results

@typescript-bot

Copy link
Copy Markdown
Contributor

Andrew Branch (@andrewbranch) Here are the results of running the top-repos suite comparing main and refs/pull/56736/merge:

Everything looks good!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for your patience Jan Kühle (@frigus02)!

@andrewbranch
Andrew Branch (andrewbranch) merged commit f70b068 into microsoft:mainMar 20, 2024
@microsoftMicrosoft (microsoft) locked as resolved and limited conversation to collaborators Oct 16, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

For Backlog BugPRs that fix a backlog bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Certain resolution-dependent enum emit isn't correctly flagged as an error under isolatedModules

5 participants

@frigus02@jakebailey@typescript-bot@andrewbranch@sandersn