Skip to content

Commit

Permalink
Enforce identical enum values in compatibility checks (#55924)
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielRosenwasser committed Dec 20, 2023
1 parent 63babdf commit 93e6b9d
Show file tree
Hide file tree
Showing 18 changed files with 1,108 additions and 27 deletions.
48 changes: 44 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20883,19 +20883,59 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return !!(entry & RelationComparisonResult.Succeeded);
}
const targetEnumType = getTypeOfSymbol(targetSymbol);
for (const property of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) {
if (property.flags & SymbolFlags.EnumMember) {
const targetProperty = getPropertyOfType(targetEnumType, property.escapedName);
for (const sourceProperty of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) {
if (sourceProperty.flags & SymbolFlags.EnumMember) {
const targetProperty = getPropertyOfType(targetEnumType, sourceProperty.escapedName);
if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) {
if (errorReporter) {
errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property), typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType));
errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(sourceProperty), typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType));
enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported);
}
else {
enumRelation.set(id, RelationComparisonResult.Failed);
}
return false;
}
const sourceValue = getEnumMemberValue(getDeclarationOfKind(sourceProperty, SyntaxKind.EnumMember)!);
const targetValue = getEnumMemberValue(getDeclarationOfKind(targetProperty, SyntaxKind.EnumMember)!);
if (sourceValue !== targetValue) {
const sourceIsString = typeof sourceValue === "string";
const targetIsString = typeof targetValue === "string";

// If we have 2 enums with *known* values that differ, they are incompatible.
if (sourceValue !== undefined && targetValue !== undefined) {
if (!errorReporter) {
enumRelation.set(id, RelationComparisonResult.Failed);
}
else {
const escapedSource = sourceIsString ? `"${escapeString(sourceValue)}"` : sourceValue;
const escapedTarget = targetIsString ? `"${escapeString(targetValue)}"` : targetValue;
errorReporter(Diagnostics.Each_declaration_of_0_1_differs_in_its_value_where_2_was_expected_but_3_was_given, symbolName(targetSymbol), symbolName(targetProperty), escapedTarget, escapedSource);
enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported);
}
return false;
}

// At this point we know that at least one of the values is 'undefined'.
// This may mean that we have an opaque member from an ambient enum declaration,
// or that we were not able to calculate it (which is basically an error).
//
// Either way, we can assume that it's numeric.
// If the other is a string, we have a mismatch in types.
if (sourceIsString || targetIsString) {
if (!errorReporter) {
enumRelation.set(id, RelationComparisonResult.Failed);
}
else {
const knownStringValue = sourceValue ?? targetValue;
Debug.assert(typeof knownStringValue === "string");
const escapedValue = `"${escapeString(knownStringValue)}"`;
errorReporter(Diagnostics.One_value_of_0_1_is_the_string_2_and_the_other_is_assumed_to_be_an_unknown_numeric_value, symbolName(targetSymbol), symbolName(targetProperty), escapedValue);
enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported);
}
return false;
}
}
}
}
enumRelation.set(id, RelationComparisonResult.Succeeded);
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4176,6 +4176,14 @@
"category": "Error",
"code": 4124
},
"Each declaration of '{0}.{1}' differs in its value, where '{2}' was expected but '{3}' was given.": {
"category": "Error",
"code": 4125
},
"One value of '{0}.{1}' is the string '{2}', and the other is assumed to be an unknown numeric value.": {
"category": "Error",
"code": 4126
},

"The current host does not support the '{0}' option.": {
"category": "Error",
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,7 @@ export const enum JsxFlags {
// dprint-ignore
/** @internal */
export const enum RelationComparisonResult {
None = 0,
Succeeded = 1 << 0, // Should be truthy
Failed = 1 << 1,
Reported = 1 << 2,
Expand Down
31 changes: 23 additions & 8 deletions tests/baselines/reference/enumAssignmentCompat3.errors.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
enumAssignmentCompat3.ts(68,1): error TS2322: Type 'Abcd.E' is not assignable to type 'First.E'.
Property 'd' is missing in type 'First.E'.
enumAssignmentCompat3.ts(70,1): error TS2322: Type 'Cd.E' is not assignable to type 'First.E'.
Property 'd' is missing in type 'First.E'.
Each declaration of 'E.c' differs in its value, where '2' was expected but '0' was given.
enumAssignmentCompat3.ts(71,1): error TS2322: Type 'Nope' is not assignable to type 'E'.
enumAssignmentCompat3.ts(72,1): error TS2322: Type 'Decl.E' is not assignable to type 'First.E'.
Each declaration of 'E.c' differs in its value, where '2' was expected but '3' was given.
enumAssignmentCompat3.ts(75,1): error TS2322: Type 'First.E' is not assignable to type 'Ab.E'.
Property 'c' is missing in type 'Ab.E'.
enumAssignmentCompat3.ts(76,1): error TS2322: Type 'First.E' is not assignable to type 'Cd.E'.
Property 'a' is missing in type 'Cd.E'.
enumAssignmentCompat3.ts(77,1): error TS2322: Type 'E' is not assignable to type 'Nope'.
enumAssignmentCompat3.ts(78,1): error TS2322: Type 'First.E' is not assignable to type 'Decl.E'.
Each declaration of 'E.c' differs in its value, where '3' was expected but '2' was given.
enumAssignmentCompat3.ts(82,1): error TS2322: Type 'Const.E' is not assignable to type 'First.E'.
enumAssignmentCompat3.ts(83,1): error TS2322: Type 'First.E' is not assignable to type 'Const.E'.
enumAssignmentCompat3.ts(86,1): error TS2322: Type 'Merged.E' is not assignable to type 'First.E'.
Property 'd' is missing in type 'First.E'.
Each declaration of 'E.c' differs in its value, where '2' was expected but '3' was given.
enumAssignmentCompat3.ts(87,1): error TS2322: Type 'First.E' is not assignable to type 'Merged.E'.
Each declaration of 'E.c' differs in its value, where '3' was expected but '2' was given.


==== enumAssignmentCompat3.ts (9 errors) ====
==== enumAssignmentCompat3.ts (12 errors) ====
namespace First {
export enum E {
a, b, c,
Expand Down Expand Up @@ -90,11 +96,14 @@ enumAssignmentCompat3.ts(86,1): error TS2322: Type 'Merged.E' is not assignable
abc = secondCd; // missing 'd'
~~~
!!! error TS2322: Type 'Cd.E' is not assignable to type 'First.E'.
!!! error TS2322: Property 'd' is missing in type 'First.E'.
!!! error TS2322: Each declaration of 'E.c' differs in its value, where '2' was expected but '0' was given.
abc = nope; // nope!
~~~
!!! error TS2322: Type 'Nope' is not assignable to type 'E'.
abc = decl; // ok
abc = decl; // bad - value of 'c' differs between these enums
~~~
!!! error TS2322: Type 'Decl.E' is not assignable to type 'First.E'.
!!! error TS2322: Each declaration of 'E.c' differs in its value, where '2' was expected but '3' was given.
secondAbc = abc; // ok
secondAbcd = abc; // ok
secondAb = abc; // missing 'c'
Expand All @@ -108,7 +117,10 @@ enumAssignmentCompat3.ts(86,1): error TS2322: Type 'Merged.E' is not assignable
nope = abc; // nope!
~~~~
!!! error TS2322: Type 'E' is not assignable to type 'Nope'.
decl = abc; // ok
decl = abc; // bad - value of 'c' differs between these enums
~~~~
!!! error TS2322: Type 'First.E' is not assignable to type 'Decl.E'.
!!! error TS2322: Each declaration of 'E.c' differs in its value, where '3' was expected but '2' was given.

// const is only assignable to itself
k = k;
Expand All @@ -123,7 +135,10 @@ enumAssignmentCompat3.ts(86,1): error TS2322: Type 'Merged.E' is not assignable
abc = merged; // missing 'd'
~~~
!!! error TS2322: Type 'Merged.E' is not assignable to type 'First.E'.
!!! error TS2322: Property 'd' is missing in type 'First.E'.
merged = abc; // ok
!!! error TS2322: Each declaration of 'E.c' differs in its value, where '2' was expected but '3' was given.
merged = abc; // bad - value of 'c' differs between these enums
~~~~~~
!!! error TS2322: Type 'First.E' is not assignable to type 'Merged.E'.
!!! error TS2322: Each declaration of 'E.c' differs in its value, where '3' was expected but '2' was given.
abc = merged2; // ok
merged2 = abc; // ok
12 changes: 6 additions & 6 deletions tests/baselines/reference/enumAssignmentCompat3.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,13 @@ abc = secondAbcd; // missing 'd'
abc = secondAb; // ok
abc = secondCd; // missing 'd'
abc = nope; // nope!
abc = decl; // ok
abc = decl; // bad - value of 'c' differs between these enums
secondAbc = abc; // ok
secondAbcd = abc; // ok
secondAb = abc; // missing 'c'
secondCd = abc; // missing 'a' and 'b'
nope = abc; // nope!
decl = abc; // ok
decl = abc; // bad - value of 'c' differs between these enums

// const is only assignable to itself
k = k;
Expand All @@ -87,7 +87,7 @@ k = abc;

// merged enums compare all their members
abc = merged; // missing 'd'
merged = abc; // ok
merged = abc; // bad - value of 'c' differs between these enums
abc = merged2; // ok
merged2 = abc; // ok

Expand Down Expand Up @@ -184,19 +184,19 @@ abc = secondAbcd; // missing 'd'
abc = secondAb; // ok
abc = secondCd; // missing 'd'
abc = nope; // nope!
abc = decl; // ok
abc = decl; // bad - value of 'c' differs between these enums
secondAbc = abc; // ok
secondAbcd = abc; // ok
secondAb = abc; // missing 'c'
secondCd = abc; // missing 'a' and 'b'
nope = abc; // nope!
decl = abc; // ok
decl = abc; // bad - value of 'c' differs between these enums
// const is only assignable to itself
k = k;
abc = k; // error
k = abc;
// merged enums compare all their members
abc = merged; // missing 'd'
merged = abc; // ok
merged = abc; // bad - value of 'c' differs between these enums
abc = merged2; // ok
merged2 = abc; // ok
6 changes: 3 additions & 3 deletions tests/baselines/reference/enumAssignmentCompat3.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ abc = nope; // nope!
>abc : Symbol(abc, Decl(enumAssignmentCompat3.ts, 56, 3))
>nope : Symbol(nope, Decl(enumAssignmentCompat3.ts, 61, 3))

abc = decl; // ok
abc = decl; // bad - value of 'c' differs between these enums
>abc : Symbol(abc, Decl(enumAssignmentCompat3.ts, 56, 3))
>decl : Symbol(decl, Decl(enumAssignmentCompat3.ts, 63, 3))

Expand All @@ -224,7 +224,7 @@ nope = abc; // nope!
>nope : Symbol(nope, Decl(enumAssignmentCompat3.ts, 61, 3))
>abc : Symbol(abc, Decl(enumAssignmentCompat3.ts, 56, 3))

decl = abc; // ok
decl = abc; // bad - value of 'c' differs between these enums
>decl : Symbol(decl, Decl(enumAssignmentCompat3.ts, 63, 3))
>abc : Symbol(abc, Decl(enumAssignmentCompat3.ts, 56, 3))

Expand All @@ -246,7 +246,7 @@ abc = merged; // missing 'd'
>abc : Symbol(abc, Decl(enumAssignmentCompat3.ts, 56, 3))
>merged : Symbol(merged, Decl(enumAssignmentCompat3.ts, 64, 3))

merged = abc; // ok
merged = abc; // bad - value of 'c' differs between these enums
>merged : Symbol(merged, Decl(enumAssignmentCompat3.ts, 64, 3))
>abc : Symbol(abc, Decl(enumAssignmentCompat3.ts, 56, 3))

Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/enumAssignmentCompat3.types
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ abc = nope; // nope!
>abc : First.E
>nope : Abc.Nope

abc = decl; // ok
abc = decl; // bad - value of 'c' differs between these enums
>abc = decl : Decl.E
>abc : First.E
>decl : Decl.E
Expand Down Expand Up @@ -226,7 +226,7 @@ nope = abc; // nope!
>nope : Abc.Nope
>abc : First.E

decl = abc; // ok
decl = abc; // bad - value of 'c' differs between these enums
>decl = abc : First.E
>decl : Decl.E
>abc : First.E
Expand All @@ -253,7 +253,7 @@ abc = merged; // missing 'd'
>abc : First.E
>merged : Merged.E

merged = abc; // ok
merged = abc; // bad - value of 'c' differs between these enums
>merged = abc : First.E
>merged : Merged.E
>abc : First.E
Expand Down
115 changes: 115 additions & 0 deletions tests/baselines/reference/enumAssignmentCompat6.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
f.ts(37,5): error TS2322: Type 'strings.DiagnosticCategory' is not assignable to type 'numerics.DiagnosticCategory'.
Each declaration of 'DiagnosticCategory.Warning' differs in its value, where '0' was expected but '"Warning"' was given.
f.ts(38,5): error TS2322: Type 'numerics.DiagnosticCategory' is not assignable to type 'strings.DiagnosticCategory'.
Each declaration of 'DiagnosticCategory.Warning' differs in its value, where '"Warning"' was expected but '0' was given.
f.ts(42,5): error TS2322: Type 'DiagnosticCategory' is not assignable to type 'DiagnosticCategory2'.
f.ts(43,5): error TS2322: Type 'DiagnosticCategory2' is not assignable to type 'DiagnosticCategory'.
f.ts(52,5): error TS2322: Type 'ambients.DiagnosticCategory' is not assignable to type 'strings.DiagnosticCategory'.
One value of 'DiagnosticCategory.Warning' is the string '"Warning"', and the other is assumed to be an unknown numeric value.
f.ts(53,5): error TS2322: Type 'strings.DiagnosticCategory' is not assignable to type 'ambients.DiagnosticCategory'.
One value of 'DiagnosticCategory.Warning' is the string '"Warning"', and the other is assumed to be an unknown numeric value.
f.ts(73,9): error TS2322: Type 'DiagnosticCategory' is not assignable to type 'import("f").DiagnosticCategory'.
Each declaration of 'DiagnosticCategory.Warning' differs in its value, where '0' was expected but '"Warning"' was given.
f.ts(74,9): error TS2322: Type 'import("f").DiagnosticCategory' is not assignable to type 'DiagnosticCategory'.
Each declaration of 'DiagnosticCategory.Warning' differs in its value, where '"Warning"' was expected but '0' was given.


==== f.ts (8 errors) ====
// @filename a.ts
namespace numerics {
export enum DiagnosticCategory {
Warning,
Error,
Suggestion,
Message,
}

export enum DiagnosticCategory2 {
Warning,
Error,
Suggestion,
Message,
}
}

namespace strings {
export enum DiagnosticCategory {
Warning = "Warning",
Error = "Error",
Suggestion = "Suggestion",
Message = "Message",
}
}

declare namespace ambients {
export enum DiagnosticCategory {
Warning,
Error,
Suggestion,
Message,
}
}

function f(x: numerics.DiagnosticCategory, y: strings.DiagnosticCategory) {
x = y;
~
!!! error TS2322: Type 'strings.DiagnosticCategory' is not assignable to type 'numerics.DiagnosticCategory'.
!!! error TS2322: Each declaration of 'DiagnosticCategory.Warning' differs in its value, where '0' was expected but '"Warning"' was given.
y = x;
~
!!! error TS2322: Type 'numerics.DiagnosticCategory' is not assignable to type 'strings.DiagnosticCategory'.
!!! error TS2322: Each declaration of 'DiagnosticCategory.Warning' differs in its value, where '"Warning"' was expected but '0' was given.
}

function g(x: numerics.DiagnosticCategory2, y: strings.DiagnosticCategory) {
x = y;
~
!!! error TS2322: Type 'DiagnosticCategory' is not assignable to type 'DiagnosticCategory2'.
y = x;
~
!!! error TS2322: Type 'DiagnosticCategory2' is not assignable to type 'DiagnosticCategory'.
}

function h(x: numerics.DiagnosticCategory, y: ambients.DiagnosticCategory) {
x = y;
y = x;
}

function i(x: strings.DiagnosticCategory, y: ambients.DiagnosticCategory) {
x = y;
~
!!! error TS2322: Type 'ambients.DiagnosticCategory' is not assignable to type 'strings.DiagnosticCategory'.
!!! error TS2322: One value of 'DiagnosticCategory.Warning' is the string '"Warning"', and the other is assumed to be an unknown numeric value.
y = x;
~
!!! error TS2322: Type 'strings.DiagnosticCategory' is not assignable to type 'ambients.DiagnosticCategory'.
!!! error TS2322: One value of 'DiagnosticCategory.Warning' is the string '"Warning"', and the other is assumed to be an unknown numeric value.
}

export enum DiagnosticCategory {
Warning,
Error,
Suggestion,
Message,
}

export let x: DiagnosticCategory;

(() => {
enum DiagnosticCategory {
Warning = "Warning",
Error = "Error",
Suggestion = "Suggestion",
Message = "Message",
}
function f(y: DiagnosticCategory) {
x = y;
~
!!! error TS2322: Type 'DiagnosticCategory' is not assignable to type 'import("f").DiagnosticCategory'.
!!! error TS2322: Each declaration of 'DiagnosticCategory.Warning' differs in its value, where '0' was expected but '"Warning"' was given.
y = x;
~
!!! error TS2322: Type 'import("f").DiagnosticCategory' is not assignable to type 'DiagnosticCategory'.
!!! error TS2322: Each declaration of 'DiagnosticCategory.Warning' differs in its value, where '"Warning"' was expected but '0' was given.
}
})()

0 comments on commit 93e6b9d

Please sign in to comment.