Skip to content

Commit

Permalink
Use evaluator for isolatedModules enum restrictions
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewbranch committed Mar 27, 2024
1 parent 316f180 commit b90b2d8
Show file tree
Hide file tree
Showing 17 changed files with 332 additions and 114 deletions.
193 changes: 113 additions & 80 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
17 changes: 8 additions & 9 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ import {
isSimpleInlineableExpression,
isSourceFile,
isStatement,
isSyntacticallyString,
isTemplateLiteral,
isTryStatement,
JsxOpeningElement,
Expand Down Expand Up @@ -1915,15 +1914,16 @@ 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,
name,
),
valueExpression,
);
const outerAssignment = isSyntacticallyString(valueExpression) ?
const outerAssignment = evaluated?.isSyntacticallyString ?
innerAssignment :
factory.createAssignment(
factory.createElementAccessExpression(
Expand All @@ -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();
Expand Down
10 changes: 9 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -5939,6 +5940,13 @@ export const enum NodeCheckFlags {
InCheckIdentifier = 1 << 22,
}

/** @internal */
export interface EvaluatorResult<T extends string | number | undefined = string | number | undefined> {
value: T;
isSyntacticallyString: boolean;
resolvedOtherFiles: boolean;
}

// dprint-ignore
/** @internal */
export interface NodeLinks {
Expand All @@ -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
Expand Down
19 changes: 0 additions & 19 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}

Original file line number Diff line number Diff line change
Expand Up @@ -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
}


Expand All @@ -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 = {}));
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}

Original file line number Diff line number Diff line change
Expand Up @@ -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
}


Expand All @@ -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 = {}));
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 = {}));
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 = {}));
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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 = {}));
7 changes: 7 additions & 0 deletions tests/cases/compiler/computedEnumMemberSyntacticallyString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading

0 comments on commit b90b2d8

Please sign in to comment.