From b90b2d8596d116f17bdb229a46b38b78e2d0867d Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 27 Mar 2024 10:36:38 -0700 Subject: [PATCH 1/4] Use evaluator for isolatedModules enum restrictions --- src/compiler/checker.ts | 193 ++++++++++-------- src/compiler/emitter.ts | 1 + src/compiler/transformers/ts.ts | 17 +- src/compiler/types.ts | 10 +- src/compiler/utilities.ts | 19 -- ...lyString(isolatedmodules=false).errors.txt | 18 +- ...tacticallyString(isolatedmodules=false).js | 12 ++ ...llyString(isolatedmodules=true).errors.txt | 18 +- ...ntacticallyString(isolatedmodules=true).js | 12 ++ ...acticallyString2(isolatedmodules=false).js | 26 ++- ...lyString2(isolatedmodules=true).errors.txt | 29 +++ ...tacticallyString2(isolatedmodules=true).js | 26 ++- ...zerFollowsNonLiteralInitializer.errors.txt | 10 + ...InitializerFollowsNonLiteralInitializer.js | 21 ++ .../computedEnumMemberSyntacticallyString.ts | 7 + .../computedEnumMemberSyntacticallyString2.ts | 17 +- ...InitializerFollowsNonLiteralInitializer.ts | 10 + 17 files changed, 332 insertions(+), 114 deletions(-) create mode 100644 tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7cff9af11039d..6bcd705f37283 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -166,6 +166,7 @@ import { equateValues, escapeLeadingUnderscores, escapeString, + type EvaluatorResult, every, EvolvingArrayType, ExclamationToken, @@ -719,7 +720,6 @@ import { isStringOrNumericLiteralLike, isSuperCall, isSuperProperty, - isSyntacticallyString, isTaggedTemplateExpression, isTemplateSpan, isThisContainerOrFunctionBlock, @@ -12745,7 +12745,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { for (const member of (declaration as EnumDeclaration).members) { if (hasBindableName(member)) { const memberSymbol = getSymbolOfDeclaration(member); - const value = getEnumMemberValue(member); + const value = getEnumMemberValue(member).value; const memberType = getFreshTypeOfLiteralType( value !== undefined ? getEnumLiteralType(value, getSymbolId(symbol), memberSymbol) : @@ -21144,8 +21144,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return false; } - const sourceValue = getEnumMemberValue(getDeclarationOfKind(sourceProperty, SyntaxKind.EnumMember)!); - const targetValue = getEnumMemberValue(getDeclarationOfKind(targetProperty, SyntaxKind.EnumMember)!); + const sourceValue = getEnumMemberValue(getDeclarationOfKind(sourceProperty, SyntaxKind.EnumMember)!).value; + const targetValue = getEnumMemberValue(getDeclarationOfKind(targetProperty, SyntaxKind.EnumMember)!).value; if (sourceValue !== targetValue) { const sourceIsString = typeof sourceValue === "string"; const targetIsString = typeof targetValue === "string"; @@ -39254,7 +39254,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; } @@ -45648,6 +45648,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + function evaluatorResult(value: T, isSyntacticallyString = false, resolvedOtherFiles = false): EvaluatorResult { + return { value, isSyntacticallyString, resolvedOtherFiles }; + } + function computeEnumMemberValues(node: EnumDeclaration) { const nodeLinks = getNodeLinks(node); if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) { @@ -45655,15 +45659,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let autoValue: number | undefined = 0; let previous: EnumMember | undefined; for (const member of node.members) { - const value = computeMemberValue(member, autoValue, previous); - getNodeLinks(member).enumMemberValue = value; - autoValue = typeof value === "number" ? value + 1 : undefined; + const result = computeEnumMemberValue(member, autoValue, previous); + getNodeLinks(member).enumMemberValue = result; + autoValue = typeof result.value === "number" ? result.value + 1 : undefined; previous = member; } } } - function computeMemberValue(member: EnumMember, autoValue: number | undefined, previous: EnumMember | undefined) { + function computeEnumMemberValue(member: EnumMember, autoValue: number | undefined, previous: EnumMember | undefined): EvaluatorResult { if (isComputedNonLiteralName(member.name)) { error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums); } @@ -45674,12 +45678,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } if (member.initializer) { - return computeConstantValue(member); + return computeConstantEnumMemberValue(member); } // In ambient non-const numeric enum declarations, enum members without initializers are // considered computed members (as opposed to having auto-incremented values). if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent)) { - return undefined; + return evaluatorResult(/*value*/ undefined); } // If the member declaration specifies no value, the member is considered a constant enum member. // If the member is the first member in the enum declaration, it is assigned the value zero. @@ -45687,31 +45691,34 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // occurs if the immediately preceding member is not a constant enum member. if (autoValue === undefined) { error(member.name, Diagnostics.Enum_member_must_have_initializer); - return undefined; + return evaluatorResult(/*value*/ undefined); } - if (getIsolatedModules(compilerOptions) && previous?.initializer && !isSyntacticallyNumericConstant(previous.initializer)) { - error( - member.name, - Diagnostics.Enum_member_following_a_non_literal_numeric_member_must_have_an_initializer_when_isolatedModules_is_enabled, - ); + if (getIsolatedModules(compilerOptions) && previous?.initializer) { + const prevValue = getEnumMemberValue(previous); + if (!(typeof prevValue.value === "number" && !prevValue.resolvedOtherFiles)) { + error( + member.name, + Diagnostics.Enum_member_following_a_non_literal_numeric_member_must_have_an_initializer_when_isolatedModules_is_enabled, + ); + } } - return autoValue; + return evaluatorResult(autoValue); } - function computeConstantValue(member: EnumMember): string | number | undefined { + function computeConstantEnumMemberValue(member: EnumMember): EvaluatorResult { 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 value === "string" && !isSyntacticallyString(initializer)) { + else if (getIsolatedModules(compilerOptions) && typeof result.value === "string" && !result.isSyntacticallyString) { error( initializer, Diagnostics._0_has_a_string_type_but_must_have_syntactically_recognizable_string_syntax_when_isolatedModules_is_enabled, @@ -45728,90 +45735,89 @@ 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; } - function isSyntacticallyNumericConstant(expr: Expression): boolean { + function evaluate(expr: Expression, location?: Declaration): EvaluatorResult { + let isSyntacticallyString = false; + let resolvedOtherFiles = false; expr = skipOuterExpressions(expr); switch (expr.kind) { case SyntaxKind.PrefixUnaryExpression: - return isSyntacticallyNumericConstant((expr as PrefixUnaryExpression).operand); - case SyntaxKind.BinaryExpression: - return isSyntacticallyNumericConstant((expr as BinaryExpression).left) && isSyntacticallyNumericConstant((expr as BinaryExpression).right); - case SyntaxKind.NumericLiteral: - return true; - } - return false; - } - - function evaluate(expr: Expression, location?: Declaration): string | number | undefined { - switch (expr.kind) { - case SyntaxKind.PrefixUnaryExpression: - const value = evaluate((expr as PrefixUnaryExpression).operand, location); - if (typeof value === "number") { + const result = evaluate((expr as PrefixUnaryExpression).operand, location); + resolvedOtherFiles = result.resolvedOtherFiles; + if (typeof result.value === "number") { switch ((expr as PrefixUnaryExpression).operator) { case SyntaxKind.PlusToken: - return value; + return evaluatorResult(result.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.MinusToken: - return -value; + return evaluatorResult(-result.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.TildeToken: - return ~value; + return evaluatorResult(~result.value, isSyntacticallyString, resolvedOtherFiles); } } break; - case SyntaxKind.BinaryExpression: + case SyntaxKind.BinaryExpression: { const left = evaluate((expr as BinaryExpression).left, location); const right = evaluate((expr as BinaryExpression).right, location); - if (typeof left === "number" && typeof right === "number") { + resolvedOtherFiles = left.resolvedOtherFiles || right.resolvedOtherFiles; + isSyntacticallyString = left.isSyntacticallyString || right.isSyntacticallyString; + if (typeof left.value === "number" && typeof right.value === "number") { switch ((expr as BinaryExpression).operatorToken.kind) { case SyntaxKind.BarToken: - return left | right; + return evaluatorResult(left.value | right.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.AmpersandToken: - return left & right; + return evaluatorResult(left.value & right.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.GreaterThanGreaterThanToken: - return left >> right; + return evaluatorResult(left.value >> right.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - return left >>> right; + return evaluatorResult(left.value >>> right.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.LessThanLessThanToken: - return left << right; + return evaluatorResult(left.value << right.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.CaretToken: - return left ^ right; + return evaluatorResult(left.value ^ right.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.AsteriskToken: - return left * right; + return evaluatorResult(left.value * right.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.SlashToken: - return left / right; + return evaluatorResult(left.value / right.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.PlusToken: - return left + right; + return evaluatorResult(left.value + right.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.MinusToken: - return left - right; + return evaluatorResult(left.value - right.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.PercentToken: - return left % right; + return evaluatorResult(left.value % right.value, isSyntacticallyString, resolvedOtherFiles); case SyntaxKind.AsteriskAsteriskToken: - return left ** right; + return evaluatorResult(left.value ** right.value, isSyntacticallyString, resolvedOtherFiles); } } else if ( - (typeof left === "string" || typeof left === "number") && - (typeof right === "string" || typeof right === "number") && + (typeof left.value === "string" || typeof left.value === "number") && + (typeof right.value === "string" || typeof right.value === "number") && (expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken ) { - return "" + left + right; + return evaluatorResult( + "" + left.value + right.value, + isSyntacticallyString, + resolvedOtherFiles, + ); } break; + } case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: - return (expr as StringLiteralLike).text; + return evaluatorResult((expr as StringLiteralLike).text, /*isSyntacticallyString*/ true); case SyntaxKind.TemplateExpression: return evaluateTemplateExpression(expr as TemplateExpression, location); case SyntaxKind.NumericLiteral: checkGrammarNumericLiteral(expr as NumericLiteral); - return +(expr as NumericLiteral).text; - case SyntaxKind.ParenthesizedExpression: - return evaluate((expr as ParenthesizedExpression).expression, location); + return evaluatorResult(+(expr as NumericLiteral).text); case SyntaxKind.Identifier: { 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); + // Technically we resolved a global lib file here, but the decision to treat this as numeric + // is more predicated on the fact that the single-file resolution *didn't* resolve to a + // different meaning of `Infinity` or `NaN`. Transpilers handle this no problem. + return evaluatorResult(+(identifier.escapedText), /*isSyntacticallyString*/ false); } // falls through } @@ -45825,7 +45831,15 @@ 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); + const result = evaluate(declaration.initializer, declaration); + if (location && getSourceFileOfNode(location) !== getSourceFileOfNode(declaration)) { + return evaluatorResult( + result.value, + /*isSyntacticallyString*/ false, + /*resolvedOtherFiles*/ true, + ); + } + return result; } } } @@ -45839,39 +45853,54 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const name = escapeLeadingUnderscores(((expr as ElementAccessExpression).argumentExpression as StringLiteralLike).text); const member = rootSymbol.exports!.get(name); if (member) { + Debug.assert(getSourceFileOfNode(member.valueDeclaration) === getSourceFileOfNode(rootSymbol.valueDeclaration)); return location ? evaluateEnumMember(expr, member, location) : getEnumMemberValue(member.valueDeclaration as EnumMember); } } } break; } - return undefined; + return evaluatorResult(/*value*/ undefined, isSyntacticallyString, resolvedOtherFiles); } function evaluateEnumMember(expr: Expression, symbol: Symbol, location: Declaration) { const declaration = symbol.valueDeclaration; if (!declaration || declaration === location) { error(expr, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(symbol)); - return undefined; + return evaluatorResult(/*value*/ undefined); } if (!isBlockScopedNameDeclaredBeforeUse(declaration, location)) { error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); - return 0; + return evaluatorResult(/*value*/ 0); + } + const result = getEnumMemberValue(declaration as EnumMember); + if (getSourceFileOfNode(location) !== getSourceFileOfNode(declaration) && !result.resolvedOtherFiles) { + return evaluatorResult( + result.value, + result.isSyntacticallyString, + /*resolvedOtherFiles*/ true, + ); } - return getEnumMemberValue(declaration as EnumMember); + return result; } - function evaluateTemplateExpression(expr: TemplateExpression, location?: Declaration) { + function evaluateTemplateExpression(expr: TemplateExpression, location?: Declaration): EvaluatorResult { let result = expr.head.text; + let resolvedOtherFiles = false; for (const span of expr.templateSpans) { - const value = evaluate(span.expression, location); - if (value === undefined) { - return undefined; + const spanResult = evaluate(span.expression, location); + if (spanResult.value === undefined) { + return evaluatorResult(/*value*/ undefined, /*isSyntacticallyString*/ true); } - result += value; + result += spanResult.value; result += span.literal.text; + resolvedOtherFiles ||= spanResult.resolvedOtherFiles; } - return result; + return evaluatorResult( + result, + /*isSyntacticallyString*/ true, + resolvedOtherFiles, + ); } function checkEnumDeclaration(node: EnumDeclaration) { @@ -48497,9 +48526,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return nodeLinks[nodeId]?.flags || 0; } - function getEnumMemberValue(node: EnumMember): string | number | undefined { + function getEnumMemberValue(node: EnumMember): EvaluatorResult { computeEnumMemberValues(node.parent); - return getNodeLinks(node).enumMemberValue; + return getNodeLinks(node).enumMemberValue ?? evaluatorResult(/*value*/ undefined); } function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression { @@ -48514,7 +48543,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { if (node.kind === SyntaxKind.EnumMember) { - return getEnumMemberValue(node); + return getEnumMemberValue(node).value; } const symbol = getNodeLinks(node).resolvedSymbol; @@ -48522,7 +48551,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // inline property\index accesses only for const enums const member = symbol.valueDeclaration as EnumMember; if (isEnumConst(member.parent)) { - return getEnumMemberValue(member); + return getEnumMemberValue(member).value; } } @@ -48883,6 +48912,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const node = getParseTreeNode(nodeIn, canHaveConstantValue); return node ? getConstantValue(node) : undefined; }, + getEnumMemberValue: nodeIn => { + const node = getParseTreeNode(nodeIn, isEnumMember); + return node ? getEnumMemberValue(node) : undefined; + }, collectLinkedAliases, getReferencedValueDeclaration, getReferencedValueDeclarations, diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 15e1847f72ac3..8036a76e16e2d 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1102,6 +1102,7 @@ export const notImplementedResolver: EmitResolver = { isEntityNameVisible: notImplemented, // Returns the constant value this property access resolves to: notImplemented, or 'undefined' for a non-constant getConstantValue: notImplemented, + getEnumMemberValue: notImplemented, getReferencedValueDeclaration: notImplemented, getReferencedValueDeclarations: notImplemented, getTypeReferenceSerializationKind: notImplemented, diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 7680adf8ada1e..b42a6b5451e05 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -122,7 +122,6 @@ import { isSimpleInlineableExpression, isSourceFile, isStatement, - isSyntacticallyString, isTemplateLiteral, isTryStatement, JsxOpeningElement, @@ -1915,7 +1914,8 @@ export function transformTypeScript(context: TransformationContext) { // we pass false as 'generateNameForComputedPropertyName' for a backward compatibility purposes // old emitter always generate 'expression' part of the name as-is. const name = getExpressionForPropertyName(member, /*generateNameForComputedPropertyName*/ false); - const valueExpression = transformEnumMemberDeclarationValue(member); + const evaluated = resolver.getEnumMemberValue(member); + const valueExpression = transformEnumMemberDeclarationValue(member, evaluated?.value); const innerAssignment = factory.createAssignment( factory.createElementAccessExpression( currentNamespaceContainerName, @@ -1923,7 +1923,7 @@ export function transformTypeScript(context: TransformationContext) { ), valueExpression, ); - const outerAssignment = isSyntacticallyString(valueExpression) ? + const outerAssignment = evaluated?.isSyntacticallyString ? innerAssignment : factory.createAssignment( factory.createElementAccessExpression( @@ -1948,12 +1948,11 @@ export function transformTypeScript(context: TransformationContext) { * * @param member The enum member node. */ - function transformEnumMemberDeclarationValue(member: EnumMember): Expression { - const value = resolver.getConstantValue(member); - if (value !== undefined) { - return typeof value === "string" ? factory.createStringLiteral(value) : - value < 0 ? factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-value)) : - factory.createNumericLiteral(value); + function transformEnumMemberDeclarationValue(member: EnumMember, constantValue: string | number | undefined): Expression { + if (constantValue !== undefined) { + return typeof constantValue === "string" ? factory.createStringLiteral(constantValue) : + constantValue < 0 ? factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-constantValue)) : + factory.createNumericLiteral(constantValue); } else { enableSubstitutionForNonQualifiedEnumMembers(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ce6064fbdc409..b10e1913c8e0f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5607,6 +5607,7 @@ export interface EmitResolver { isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult; // Returns the constant value this property access resolves to, or 'undefined' for a non-constant getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined; + getEnumMemberValue(node: EnumMember): EvaluatorResult | undefined; getReferencedValueDeclaration(reference: Identifier): Declaration | undefined; getReferencedValueDeclarations(reference: Identifier): Declaration[] | undefined; getTypeReferenceSerializationKind(typeName: EntityName, location?: Node): TypeReferenceSerializationKind; @@ -5939,6 +5940,13 @@ export const enum NodeCheckFlags { InCheckIdentifier = 1 << 22, } +/** @internal */ +export interface EvaluatorResult { + value: T; + isSyntacticallyString: boolean; + resolvedOtherFiles: boolean; +} + // dprint-ignore /** @internal */ export interface NodeLinks { @@ -5949,7 +5957,7 @@ export interface NodeLinks { resolvedSymbol?: Symbol; // Cached name resolution result resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result effectsSignature?: Signature; // Signature with possible control flow effects - enumMemberValue?: string | number; // Constant value of enum member + enumMemberValue?: EvaluatorResult; // Constant value of enum member isVisible?: boolean; // Is this node visible containsArgumentsReference?: boolean; // Whether a function-like declaration contains an 'arguments' reference hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 184f68b0c6221..5c1bd2ac053f2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -10641,22 +10641,3 @@ export function replaceFirstStar(s: string, replacement: string): string { export function getNameFromImportAttribute(node: ImportAttribute) { return isIdentifier(node.name) ? node.name.escapedText : escapeLeadingUnderscores(node.name.text); } - -/** @internal */ -export function isSyntacticallyString(expr: Expression): boolean { - expr = skipOuterExpressions(expr); - switch (expr.kind) { - case SyntaxKind.BinaryExpression: - const left = (expr as BinaryExpression).left; - const right = (expr as BinaryExpression).right; - return ( - (expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken && - (isSyntacticallyString(left) || isSyntacticallyString(right)) - ); - case SyntaxKind.TemplateExpression: - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return true; - } - return false; -} diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).errors.txt b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).errors.txt index 94d81a5a16ac2..6fa720f108ea1 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).errors.txt +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).errors.txt @@ -3,9 +3,12 @@ computedEnumMemberSyntacticallyString.ts(5,9): error TS18033: Type 'string' is n computedEnumMemberSyntacticallyString.ts(6,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. computedEnumMemberSyntacticallyString.ts(7,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. computedEnumMemberSyntacticallyString.ts(8,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +computedEnumMemberSyntacticallyString.ts(10,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +computedEnumMemberSyntacticallyString.ts(11,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +computedEnumMemberSyntacticallyString.ts(14,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. -==== computedEnumMemberSyntacticallyString.ts (5 errors) ==== +==== computedEnumMemberSyntacticallyString.ts (8 errors) ==== const BAR = 2..toFixed(0); enum Foo { @@ -24,5 +27,18 @@ computedEnumMemberSyntacticallyString.ts(8,9): error TS18033: Type 'string' is n E = `${BAR}`!, ~~~~~~~~~ !!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + + F = BAR, + ~~~ +!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + G = 2 + BAR, + ~~~~~~~ +!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + + H = A, + I = H + BAR, + ~~~~~~~ +!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + J = H } \ No newline at end of file diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).js b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).js index a8cc14cca3ad4..7f00c538ed2ff 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).js +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).js @@ -9,6 +9,13 @@ enum Foo { C = (`${BAR}`), D = (`${BAR}}`) as string, E = `${BAR}`!, + + F = BAR, + G = 2 + BAR, + + H = A, + I = H + BAR, + J = H } @@ -21,4 +28,9 @@ var Foo; Foo["C"] = (`${BAR}`); Foo["D"] = (`${BAR}}`); Foo["E"] = `${BAR}`; + Foo[Foo["F"] = BAR] = "F"; + Foo[Foo["G"] = 2 + BAR] = "G"; + Foo["H"] = Foo.A; + Foo["I"] = Foo.H + BAR; + Foo["J"] = Foo.H; })(Foo || (Foo = {})); diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).errors.txt b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).errors.txt index 94d81a5a16ac2..6fa720f108ea1 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).errors.txt +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).errors.txt @@ -3,9 +3,12 @@ computedEnumMemberSyntacticallyString.ts(5,9): error TS18033: Type 'string' is n computedEnumMemberSyntacticallyString.ts(6,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. computedEnumMemberSyntacticallyString.ts(7,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. computedEnumMemberSyntacticallyString.ts(8,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +computedEnumMemberSyntacticallyString.ts(10,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +computedEnumMemberSyntacticallyString.ts(11,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +computedEnumMemberSyntacticallyString.ts(14,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. -==== computedEnumMemberSyntacticallyString.ts (5 errors) ==== +==== computedEnumMemberSyntacticallyString.ts (8 errors) ==== const BAR = 2..toFixed(0); enum Foo { @@ -24,5 +27,18 @@ computedEnumMemberSyntacticallyString.ts(8,9): error TS18033: Type 'string' is n E = `${BAR}`!, ~~~~~~~~~ !!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + + F = BAR, + ~~~ +!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + G = 2 + BAR, + ~~~~~~~ +!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + + H = A, + I = H + BAR, + ~~~~~~~ +!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + J = H } \ No newline at end of file diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).js b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).js index a8cc14cca3ad4..7f00c538ed2ff 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).js +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).js @@ -9,6 +9,13 @@ enum Foo { C = (`${BAR}`), D = (`${BAR}}`) as string, E = `${BAR}`!, + + F = BAR, + G = 2 + BAR, + + H = A, + I = H + BAR, + J = H } @@ -21,4 +28,9 @@ var Foo; Foo["C"] = (`${BAR}`); Foo["D"] = (`${BAR}}`); Foo["E"] = `${BAR}`; + Foo[Foo["F"] = BAR] = "F"; + Foo[Foo["G"] = 2 + BAR] = "G"; + Foo["H"] = Foo.A; + Foo["I"] = Foo.H + BAR; + Foo["J"] = Foo.H; })(Foo || (Foo = {})); diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).js b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).js index 0a0bf9e0a8a24..34031b2346635 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).js +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).js @@ -2,7 +2,22 @@ //// [foo.ts] import { BAR } from './bar'; -enum Foo { A = `${BAR}` } +const LOCAL = 'LOCAL'; + +enum Foo { + A = `${BAR}`, + + B = LOCAL, + C = B, + D = C + 'BAR', + + F = BAR, + G = 2 + BAR, + + H = A, + I = H + BAR, + J = H +} //// [bar.ts] export const BAR = 'bar'; @@ -11,7 +26,16 @@ export const BAR = 'bar'; export const BAR = 'bar'; //// [foo.js] import { BAR } from './bar'; +const LOCAL = 'LOCAL'; var Foo; (function (Foo) { Foo["A"] = "bar"; + Foo["B"] = "LOCAL"; + Foo["C"] = "LOCAL"; + Foo["D"] = "LOCALBAR"; + Foo[Foo["F"] = "bar"] = "F"; + Foo[Foo["G"] = "2bar"] = "G"; + Foo["H"] = "bar"; + Foo["I"] = "barbar"; + Foo["J"] = "bar"; })(Foo || (Foo = {})); diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).errors.txt b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).errors.txt new file mode 100644 index 0000000000000..95137e70a9a01 --- /dev/null +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).errors.txt @@ -0,0 +1,29 @@ +foo.ts(11,7): error TS18055: 'Foo.F' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled. +foo.ts(12,7): error TS18055: 'Foo.G' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled. + + +==== ./foo.ts (2 errors) ==== + import { BAR } from './bar'; + const LOCAL = 'LOCAL'; + + enum Foo { + A = `${BAR}`, + + B = LOCAL, + C = B, + D = C + 'BAR', + + F = BAR, + ~~~ +!!! error TS18055: 'Foo.F' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled. + G = 2 + BAR, + ~~~~~~~ +!!! error TS18055: 'Foo.G' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled. + + H = A, + I = H + BAR, + J = H + } + +==== ./bar.ts (0 errors) ==== + export const BAR = 'bar'; \ No newline at end of file diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).js b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).js index 0a0bf9e0a8a24..34031b2346635 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).js +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).js @@ -2,7 +2,22 @@ //// [foo.ts] import { BAR } from './bar'; -enum Foo { A = `${BAR}` } +const LOCAL = 'LOCAL'; + +enum Foo { + A = `${BAR}`, + + B = LOCAL, + C = B, + D = C + 'BAR', + + F = BAR, + G = 2 + BAR, + + H = A, + I = H + BAR, + J = H +} //// [bar.ts] export const BAR = 'bar'; @@ -11,7 +26,16 @@ export const BAR = 'bar'; export const BAR = 'bar'; //// [foo.js] import { BAR } from './bar'; +const LOCAL = 'LOCAL'; var Foo; (function (Foo) { Foo["A"] = "bar"; + Foo["B"] = "LOCAL"; + Foo["C"] = "LOCAL"; + Foo["D"] = "LOCALBAR"; + Foo[Foo["F"] = "bar"] = "F"; + Foo[Foo["G"] = "2bar"] = "G"; + Foo["H"] = "bar"; + Foo["I"] = "barbar"; + Foo["J"] = "bar"; })(Foo || (Foo = {})); diff --git a/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.errors.txt b/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.errors.txt index 2999b769c9699..afe403e720923 100644 --- a/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.errors.txt +++ b/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.errors.txt @@ -31,4 +31,14 @@ bad.ts(4,5): error TS18056: Enum member following a non-literal numeric member m a = (2), b, } + enum E { + a, + b, + c = a, + d, + e = d | b, + f, + g = (f | a)! satisfies number as any, + h, + } \ No newline at end of file diff --git a/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.js b/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.js index 1b24257e04f7c..2c1d8ef4c414c 100644 --- a/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.js +++ b/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.js @@ -28,6 +28,16 @@ enum D { a = (2), b, } +enum E { + a, + b, + c = a, + d, + e = d | b, + f, + g = (f | a)! satisfies number as any, + h, +} //// [helpers.js] @@ -68,3 +78,14 @@ var D; D[D["a"] = 2] = "a"; D[D["b"] = 3] = "b"; })(D || (D = {})); +var E; +(function (E) { + E[E["a"] = 0] = "a"; + E[E["b"] = 1] = "b"; + E[E["c"] = 0] = "c"; + E[E["d"] = 1] = "d"; + E[E["e"] = 1] = "e"; + E[E["f"] = 2] = "f"; + E[E["g"] = 2] = "g"; + E[E["h"] = 3] = "h"; +})(E || (E = {})); diff --git a/tests/cases/compiler/computedEnumMemberSyntacticallyString.ts b/tests/cases/compiler/computedEnumMemberSyntacticallyString.ts index 15af31ebc6fb8..2f0c34624ba9b 100644 --- a/tests/cases/compiler/computedEnumMemberSyntacticallyString.ts +++ b/tests/cases/compiler/computedEnumMemberSyntacticallyString.ts @@ -10,4 +10,11 @@ enum Foo { C = (`${BAR}`), D = (`${BAR}}`) as string, E = `${BAR}`!, + + F = BAR, + G = 2 + BAR, + + H = A, + I = H + BAR, + J = H } diff --git a/tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts b/tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts index e163858b13c4a..1093d46c47e9e 100644 --- a/tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts +++ b/tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts @@ -4,7 +4,22 @@ // @filename: ./foo.ts import { BAR } from './bar'; -enum Foo { A = `${BAR}` } +const LOCAL = 'LOCAL'; + +enum Foo { + A = `${BAR}`, + + B = LOCAL, + C = B, + D = C + 'BAR', + + F = BAR, + G = 2 + BAR, + + H = A, + I = H + BAR, + J = H +} // @filename: ./bar.ts export const BAR = 'bar'; \ No newline at end of file diff --git a/tests/cases/compiler/enumNoInitializerFollowsNonLiteralInitializer.ts b/tests/cases/compiler/enumNoInitializerFollowsNonLiteralInitializer.ts index b4274dbd53385..1a8876bf5adab 100644 --- a/tests/cases/compiler/enumNoInitializerFollowsNonLiteralInitializer.ts +++ b/tests/cases/compiler/enumNoInitializerFollowsNonLiteralInitializer.ts @@ -29,3 +29,13 @@ enum D { a = (2), b, } +enum E { + a, + b, + c = a, + d, + e = d | b, + f, + g = (f | a)! satisfies number as any, + h, +} From 392976d5fb191a840b171265c680d65c906f5bf0 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 27 Mar 2024 15:46:43 -0700 Subject: [PATCH 2/4] Roll back outer expression skipping --- src/compiler/transformers/ts.ts | 2 +- src/compiler/utilities.ts | 17 ++++++++-- ...lyString(isolatedmodules=false).errors.txt | 14 ++------ ...tacticallyString(isolatedmodules=false).js | 4 --- ...llyString(isolatedmodules=true).errors.txt | 14 ++------ ...ntacticallyString(isolatedmodules=true).js | 4 --- ...yString2(isolatedmodules=false).errors.txt | 32 +++++++++++++++++++ ...acticallyString2(isolatedmodules=false).js | 9 ++++-- ...lyString2(isolatedmodules=true).errors.txt | 15 +++++++-- ...tacticallyString2(isolatedmodules=true).js | 9 ++++-- .../computedEnumMemberSyntacticallyString.ts | 2 -- .../computedEnumMemberSyntacticallyString2.ts | 3 ++ 12 files changed, 83 insertions(+), 42 deletions(-) create mode 100644 tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).errors.txt diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index b42a6b5451e05..e4ff2ac9abf59 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1923,7 +1923,7 @@ export function transformTypeScript(context: TransformationContext) { ), valueExpression, ); - const outerAssignment = evaluated?.isSyntacticallyString ? + const outerAssignment = typeof evaluated?.value === "string" || evaluated?.isSyntacticallyString ? innerAssignment : factory.createAssignment( factory.createElementAccessExpression( diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 1c70744e5f4e9..875c1fb8185c2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -10661,7 +10661,19 @@ export function createEvaluator({ evaluateElementAccessExpression, evaluateEntit function evaluate(expr: Expression, location?: Declaration): EvaluatorResult { let isSyntacticallyString = false; let resolvedOtherFiles = false; - expr = skipOuterExpressions(expr); + // It's unclear when/whether we should consider skipping other kinds of outer expressions. + // Type assertions intentionally break evaluation when evaluating literal types, such as: + // type T = `one ${"two" as any} three`; // string + // But it's less clear whether such an assertion should break enum member evaluation: + // enum E { + // A = "one" as any + // } + // SatisfiesExpressions and non-null assertions seem to have even less reason to break + // emitting enum members as literals. However, these expressions also break Babel's + // evaluation (but not esbuild's), and the isolatedModules errors we give depend on + // our evaluation results, so we're currently being conservative so as to issue errors + // on code that might break Babel. + expr = skipParentheses(expr); switch (expr.kind) { case SyntaxKind.PrefixUnaryExpression: const result = evaluate((expr as PrefixUnaryExpression).operand, location); @@ -10680,8 +10692,8 @@ export function createEvaluator({ evaluateElementAccessExpression, evaluateEntit case SyntaxKind.BinaryExpression: { const left = evaluate((expr as BinaryExpression).left, location); const right = evaluate((expr as BinaryExpression).right, location); + isSyntacticallyString = (left.isSyntacticallyString || right.isSyntacticallyString) && (expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken; resolvedOtherFiles = left.resolvedOtherFiles || right.resolvedOtherFiles; - isSyntacticallyString = left.isSyntacticallyString || right.isSyntacticallyString; if (typeof left.value === "number" && typeof right.value === "number") { switch ((expr as BinaryExpression).operatorToken.kind) { case SyntaxKind.BarToken: @@ -10721,6 +10733,7 @@ export function createEvaluator({ evaluateElementAccessExpression, evaluateEntit resolvedOtherFiles, ); } + break; } case SyntaxKind.StringLiteral: diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).errors.txt b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).errors.txt index 6fa720f108ea1..741f89a635f7c 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).errors.txt +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).errors.txt @@ -1,14 +1,12 @@ computedEnumMemberSyntacticallyString.ts(4,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. computedEnumMemberSyntacticallyString.ts(5,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. computedEnumMemberSyntacticallyString.ts(6,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. -computedEnumMemberSyntacticallyString.ts(7,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. computedEnumMemberSyntacticallyString.ts(8,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. -computedEnumMemberSyntacticallyString.ts(10,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. -computedEnumMemberSyntacticallyString.ts(11,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. -computedEnumMemberSyntacticallyString.ts(14,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +computedEnumMemberSyntacticallyString.ts(9,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +computedEnumMemberSyntacticallyString.ts(12,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. -==== computedEnumMemberSyntacticallyString.ts (8 errors) ==== +==== computedEnumMemberSyntacticallyString.ts (6 errors) ==== const BAR = 2..toFixed(0); enum Foo { @@ -21,12 +19,6 @@ computedEnumMemberSyntacticallyString.ts(14,9): error TS18033: Type 'string' is C = (`${BAR}`), ~~~~~~~~~~ !!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. - D = (`${BAR}}`) as string, - ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. - E = `${BAR}`!, - ~~~~~~~~~ -!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. F = BAR, ~~~ diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).js b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).js index 7f00c538ed2ff..38c360bdf0c83 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).js +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).js @@ -7,8 +7,6 @@ enum Foo { A = `${BAR}`, B = "2" + BAR, C = (`${BAR}`), - D = (`${BAR}}`) as string, - E = `${BAR}`!, F = BAR, G = 2 + BAR, @@ -26,8 +24,6 @@ var Foo; Foo["A"] = `${BAR}`; Foo["B"] = "2" + BAR; Foo["C"] = (`${BAR}`); - Foo["D"] = (`${BAR}}`); - Foo["E"] = `${BAR}`; Foo[Foo["F"] = BAR] = "F"; Foo[Foo["G"] = 2 + BAR] = "G"; Foo["H"] = Foo.A; diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).errors.txt b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).errors.txt index 6fa720f108ea1..741f89a635f7c 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).errors.txt +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).errors.txt @@ -1,14 +1,12 @@ computedEnumMemberSyntacticallyString.ts(4,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. computedEnumMemberSyntacticallyString.ts(5,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. computedEnumMemberSyntacticallyString.ts(6,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. -computedEnumMemberSyntacticallyString.ts(7,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. computedEnumMemberSyntacticallyString.ts(8,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. -computedEnumMemberSyntacticallyString.ts(10,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. -computedEnumMemberSyntacticallyString.ts(11,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. -computedEnumMemberSyntacticallyString.ts(14,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +computedEnumMemberSyntacticallyString.ts(9,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +computedEnumMemberSyntacticallyString.ts(12,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. -==== computedEnumMemberSyntacticallyString.ts (8 errors) ==== +==== computedEnumMemberSyntacticallyString.ts (6 errors) ==== const BAR = 2..toFixed(0); enum Foo { @@ -21,12 +19,6 @@ computedEnumMemberSyntacticallyString.ts(14,9): error TS18033: Type 'string' is C = (`${BAR}`), ~~~~~~~~~~ !!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. - D = (`${BAR}}`) as string, - ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. - E = `${BAR}`!, - ~~~~~~~~~ -!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. F = BAR, ~~~ diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).js b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).js index 7f00c538ed2ff..38c360bdf0c83 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).js +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).js @@ -7,8 +7,6 @@ enum Foo { A = `${BAR}`, B = "2" + BAR, C = (`${BAR}`), - D = (`${BAR}}`) as string, - E = `${BAR}`!, F = BAR, G = 2 + BAR, @@ -26,8 +24,6 @@ var Foo; Foo["A"] = `${BAR}`; Foo["B"] = "2" + BAR; Foo["C"] = (`${BAR}`); - Foo["D"] = (`${BAR}}`); - Foo["E"] = `${BAR}`; Foo[Foo["F"] = BAR] = "F"; Foo[Foo["G"] = 2 + BAR] = "G"; Foo["H"] = Foo.A; diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).errors.txt b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).errors.txt new file mode 100644 index 0000000000000..2be83179ada19 --- /dev/null +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).errors.txt @@ -0,0 +1,32 @@ +foo.ts(11,8): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +foo.ts(12,8): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + + +==== ./foo.ts (2 errors) ==== + import { BAR } from './bar'; + const LOCAL = 'LOCAL'; + + enum Foo { + A = `${BAR}`, + + B = LOCAL, + C = B, + D = C + 'BAR', + + E1 = (`${BAR}`) as string, // We could recognize these, + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + E2 = `${BAR}`!, // but Babel doesn't + ~~~~~~~~~ +!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + + F = BAR, + G = 2 + BAR, + + H = A, + I = H + BAR, + J = H + } + +==== ./bar.ts (0 errors) ==== + export const BAR = 'bar'; \ No newline at end of file diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).js b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).js index 34031b2346635..132fda679153f 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).js +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).js @@ -11,6 +11,9 @@ enum Foo { C = B, D = C + 'BAR', + E1 = (`${BAR}`) as string, // We could recognize these, + E2 = `${BAR}`!, // but Babel doesn't + F = BAR, G = 2 + BAR, @@ -33,8 +36,10 @@ var Foo; Foo["B"] = "LOCAL"; Foo["C"] = "LOCAL"; Foo["D"] = "LOCALBAR"; - Foo[Foo["F"] = "bar"] = "F"; - Foo[Foo["G"] = "2bar"] = "G"; + Foo[Foo["E1"] = (`${BAR}`)] = "E1"; + Foo[Foo["E2"] = `${BAR}`] = "E2"; + Foo["F"] = "bar"; + Foo["G"] = "2bar"; Foo["H"] = "bar"; Foo["I"] = "barbar"; Foo["J"] = "bar"; diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).errors.txt b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).errors.txt index 95137e70a9a01..66b0352943649 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).errors.txt +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).errors.txt @@ -1,8 +1,10 @@ -foo.ts(11,7): error TS18055: 'Foo.F' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled. -foo.ts(12,7): error TS18055: 'Foo.G' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled. +foo.ts(11,8): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +foo.ts(12,8): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. +foo.ts(14,7): error TS18055: 'Foo.F' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled. +foo.ts(15,7): error TS18055: 'Foo.G' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled. -==== ./foo.ts (2 errors) ==== +==== ./foo.ts (4 errors) ==== import { BAR } from './bar'; const LOCAL = 'LOCAL'; @@ -13,6 +15,13 @@ foo.ts(12,7): error TS18055: 'Foo.G' has a string type, but must have syntactica C = B, D = C + 'BAR', + E1 = (`${BAR}`) as string, // We could recognize these, + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + E2 = `${BAR}`!, // but Babel doesn't + ~~~~~~~~~ +!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values. + F = BAR, ~~~ !!! error TS18055: 'Foo.F' has a string type, but must have syntactically recognizable string syntax when 'isolatedModules' is enabled. diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).js b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).js index 34031b2346635..132fda679153f 100644 --- a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).js +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).js @@ -11,6 +11,9 @@ enum Foo { C = B, D = C + 'BAR', + E1 = (`${BAR}`) as string, // We could recognize these, + E2 = `${BAR}`!, // but Babel doesn't + F = BAR, G = 2 + BAR, @@ -33,8 +36,10 @@ var Foo; Foo["B"] = "LOCAL"; Foo["C"] = "LOCAL"; Foo["D"] = "LOCALBAR"; - Foo[Foo["F"] = "bar"] = "F"; - Foo[Foo["G"] = "2bar"] = "G"; + Foo[Foo["E1"] = (`${BAR}`)] = "E1"; + Foo[Foo["E2"] = `${BAR}`] = "E2"; + Foo["F"] = "bar"; + Foo["G"] = "2bar"; Foo["H"] = "bar"; Foo["I"] = "barbar"; Foo["J"] = "bar"; diff --git a/tests/cases/compiler/computedEnumMemberSyntacticallyString.ts b/tests/cases/compiler/computedEnumMemberSyntacticallyString.ts index 2f0c34624ba9b..ae16dbcc0a7df 100644 --- a/tests/cases/compiler/computedEnumMemberSyntacticallyString.ts +++ b/tests/cases/compiler/computedEnumMemberSyntacticallyString.ts @@ -8,8 +8,6 @@ enum Foo { A = `${BAR}`, B = "2" + BAR, C = (`${BAR}`), - D = (`${BAR}}`) as string, - E = `${BAR}`!, F = BAR, G = 2 + BAR, diff --git a/tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts b/tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts index 1093d46c47e9e..cd364ff4261a0 100644 --- a/tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts +++ b/tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts @@ -13,6 +13,9 @@ enum Foo { C = B, D = C + 'BAR', + E1 = (`${BAR}`) as string, // We could recognize these, + E2 = `${BAR}`!, // but Babel doesn't + F = BAR, G = 2 + BAR, From 2035fc64e0d6abbe8d7eb64823054f32178cced1 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 27 Mar 2024 15:54:33 -0700 Subject: [PATCH 3/4] Update test --- ...oInitializerFollowsNonLiteralInitializer.errors.txt | 10 +++++++--- .../enumNoInitializerFollowsNonLiteralInitializer.js | 10 ++++++---- .../enumNoInitializerFollowsNonLiteralInitializer.ts | 5 +++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.errors.txt b/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.errors.txt index afe403e720923..3b137187d1478 100644 --- a/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.errors.txt +++ b/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.errors.txt @@ -1,16 +1,22 @@ bad.ts(4,5): error TS18056: Enum member following a non-literal numeric member must have an initializer when 'isolatedModules' is enabled. +bad.ts(7,5): error TS1061: Enum member must have initializer. ==== ./helpers.ts (0 errors) ==== export const foo = 2; -==== ./bad.ts (1 errors) ==== +==== ./bad.ts (2 errors) ==== import { foo } from "./helpers"; enum A { a = foo, b, ~ !!! error TS18056: Enum member following a non-literal numeric member must have an initializer when 'isolatedModules' is enabled. + c = 10, + d = (c)! satisfies number as any, + e, + ~ +!!! error TS1061: Enum member must have initializer. } ==== ./good.ts (0 errors) ==== @@ -38,7 +44,5 @@ bad.ts(4,5): error TS18056: Enum member following a non-literal numeric member m d, e = d | b, f, - g = (f | a)! satisfies number as any, - h, } \ No newline at end of file diff --git a/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.js b/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.js index 2c1d8ef4c414c..bc21c531709a1 100644 --- a/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.js +++ b/tests/baselines/reference/enumNoInitializerFollowsNonLiteralInitializer.js @@ -8,6 +8,9 @@ import { foo } from "./helpers"; enum A { a = foo, b, + c = 10, + d = (c)! satisfies number as any, + e, } //// [good.ts] @@ -35,8 +38,6 @@ enum E { d, e = d | b, f, - g = (f | a)! satisfies number as any, - h, } @@ -53,6 +54,9 @@ var A; (function (A) { A[A["a"] = 2] = "a"; A[A["b"] = 3] = "b"; + A[A["c"] = 10] = "c"; + A[A["d"] = (A.c)] = "d"; + A[A["e"] = void 0] = "e"; })(A || (A = {})); //// [good.js] "use strict"; @@ -86,6 +90,4 @@ var E; E[E["d"] = 1] = "d"; E[E["e"] = 1] = "e"; E[E["f"] = 2] = "f"; - E[E["g"] = 2] = "g"; - E[E["h"] = 3] = "h"; })(E || (E = {})); diff --git a/tests/cases/compiler/enumNoInitializerFollowsNonLiteralInitializer.ts b/tests/cases/compiler/enumNoInitializerFollowsNonLiteralInitializer.ts index 1a8876bf5adab..72cd4af5e8ac5 100644 --- a/tests/cases/compiler/enumNoInitializerFollowsNonLiteralInitializer.ts +++ b/tests/cases/compiler/enumNoInitializerFollowsNonLiteralInitializer.ts @@ -9,6 +9,9 @@ import { foo } from "./helpers"; enum A { a = foo, b, + c = 10, + d = (c)! satisfies number as any, + e, } // @filename: ./good.ts @@ -36,6 +39,4 @@ enum E { d, e = d | b, f, - g = (f | a)! satisfies number as any, - h, } From 4f69ba76386822c5018b32c90b863f8dfd52fe6b Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 2 Apr 2024 10:50:23 -0700 Subject: [PATCH 4/4] Remove type-only ipmorts --- src/compiler/checker.ts | 2 +- src/compiler/utilities.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e0804443d179d..b8c290161142a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -167,7 +167,7 @@ import { equateValues, escapeLeadingUnderscores, escapeString, - type EvaluatorResult, + EvaluatorResult, evaluatorResult, every, EvolvingArrayType, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 875c1fb8185c2..719c7309c6eb8 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -120,7 +120,7 @@ import { equateValues, escapeLeadingUnderscores, EvaluationResolver, - type EvaluatorResult, + EvaluatorResult, every, ExportAssignment, ExportDeclaration,