Skip to content

Enforce identical enum values in compatibility checks #55924

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 44 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20601,19 +20601,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 @@ -4132,6 +4132,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 @@ -896,6 +896,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.
}
})()
Loading