From fcb566d07384915e866c2ae7611f70b2ad45656d Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sat, 4 Sep 2021 19:43:40 +0900 Subject: [PATCH 1/3] feat(45679): support 'did you mean' diagnostics for string literal union --- src/compiler/checker.ts | 17 +++++++++++++ src/compiler/diagnosticMessages.json | 4 ++++ .../didYouMeanStringLiteral.errors.txt | 20 ++++++++++++++++ .../reference/didYouMeanStringLiteral.js | 14 +++++++++++ .../reference/didYouMeanStringLiteral.symbols | 24 +++++++++++++++++++ .../reference/didYouMeanStringLiteral.types | 22 +++++++++++++++++ ...sForCallAndAssignmentAreSimilar.errors.txt | 8 +++---- .../cases/compiler/didYouMeanStringLiteral.ts | 7 ++++++ 8 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/didYouMeanStringLiteral.errors.txt create mode 100644 tests/baselines/reference/didYouMeanStringLiteral.js create mode 100644 tests/baselines/reference/didYouMeanStringLiteral.symbols create mode 100644 tests/baselines/reference/didYouMeanStringLiteral.types create mode 100644 tests/cases/compiler/didYouMeanStringLiteral.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b71cfe8f3e97a..508ec40b1591e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17740,6 +17740,13 @@ namespace ts { message = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties; } else { + if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.Union) { + const suggestion = getSuggestionForNonexistentStringLiteral(source as StringLiteralType, target as UnionType); + if (suggestion) { + reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, sourceType, targetType, suggestion); + return; + } + } message = Diagnostics.Type_0_is_not_assignable_to_type_1; } } @@ -28127,6 +28134,16 @@ namespace ts { return suggestion; } + function getSuggestionForNonexistentStringLiteral(source: StringLiteralType, target: UnionType): string | undefined { + const candidates: string[] = []; + for (const type of target.types) { + if (type.flags & TypeFlags.StringLiteral) { + candidates.push((type as StringLiteralType).value); + } + } + return getSpellingSuggestion(source.value, candidates, (name) => name); + } + /** * Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough. * Names less than length 3 only check for case-insensitive equality, not levenshtein distance. diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index d1a03c6a31e9f..eb320a157f9d9 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3284,6 +3284,10 @@ "category": "Error", "code": 2819 }, + "Type '{0}' is not assignable to type '{1}'. Did you mean '{2}'?": { + "category": "Error", + "code": 2820 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/didYouMeanStringLiteral.errors.txt b/tests/baselines/reference/didYouMeanStringLiteral.errors.txt new file mode 100644 index 0000000000000..7e8f99d70d320 --- /dev/null +++ b/tests/baselines/reference/didYouMeanStringLiteral.errors.txt @@ -0,0 +1,20 @@ +tests/cases/compiler/didYouMeanStringLiteral.ts(5,7): error TS2820: Type '"strong"' is not assignable to type 'T1'. Did you mean 'string'? +tests/cases/compiler/didYouMeanStringLiteral.ts(6,7): error TS2322: Type '"strong"' is not assignable to type '"number" | "boolean"'. +tests/cases/compiler/didYouMeanStringLiteral.ts(7,7): error TS2820: Type '"strong"' is not assignable to type '"string" | "boolean"'. Did you mean 'string'? + + +==== tests/cases/compiler/didYouMeanStringLiteral.ts (3 errors) ==== + type T1 = "string" | "number" | "boolean"; + type T2 = T1 & ("number" | "boolean"); // "number" | "boolean" + type T3 = T1 & ("string" | "boolean"); // "string" | "boolean" + + const t1: T1 = "strong"; + ~~ +!!! error TS2820: Type '"strong"' is not assignable to type 'T1'. Did you mean 'string'? + const t2: T2 = "strong"; + ~~ +!!! error TS2322: Type '"strong"' is not assignable to type '"number" | "boolean"'. + const t3: T3 = "strong"; + ~~ +!!! error TS2820: Type '"strong"' is not assignable to type '"string" | "boolean"'. Did you mean 'string'? + \ No newline at end of file diff --git a/tests/baselines/reference/didYouMeanStringLiteral.js b/tests/baselines/reference/didYouMeanStringLiteral.js new file mode 100644 index 0000000000000..b1b356c74a1e9 --- /dev/null +++ b/tests/baselines/reference/didYouMeanStringLiteral.js @@ -0,0 +1,14 @@ +//// [didYouMeanStringLiteral.ts] +type T1 = "string" | "number" | "boolean"; +type T2 = T1 & ("number" | "boolean"); // "number" | "boolean" +type T3 = T1 & ("string" | "boolean"); // "string" | "boolean" + +const t1: T1 = "strong"; +const t2: T2 = "strong"; +const t3: T3 = "strong"; + + +//// [didYouMeanStringLiteral.js] +var t1 = "strong"; +var t2 = "strong"; +var t3 = "strong"; diff --git a/tests/baselines/reference/didYouMeanStringLiteral.symbols b/tests/baselines/reference/didYouMeanStringLiteral.symbols new file mode 100644 index 0000000000000..e0ab68cfc6e6a --- /dev/null +++ b/tests/baselines/reference/didYouMeanStringLiteral.symbols @@ -0,0 +1,24 @@ +=== tests/cases/compiler/didYouMeanStringLiteral.ts === +type T1 = "string" | "number" | "boolean"; +>T1 : Symbol(T1, Decl(didYouMeanStringLiteral.ts, 0, 0)) + +type T2 = T1 & ("number" | "boolean"); // "number" | "boolean" +>T2 : Symbol(T2, Decl(didYouMeanStringLiteral.ts, 0, 42)) +>T1 : Symbol(T1, Decl(didYouMeanStringLiteral.ts, 0, 0)) + +type T3 = T1 & ("string" | "boolean"); // "string" | "boolean" +>T3 : Symbol(T3, Decl(didYouMeanStringLiteral.ts, 1, 38)) +>T1 : Symbol(T1, Decl(didYouMeanStringLiteral.ts, 0, 0)) + +const t1: T1 = "strong"; +>t1 : Symbol(t1, Decl(didYouMeanStringLiteral.ts, 4, 5)) +>T1 : Symbol(T1, Decl(didYouMeanStringLiteral.ts, 0, 0)) + +const t2: T2 = "strong"; +>t2 : Symbol(t2, Decl(didYouMeanStringLiteral.ts, 5, 5)) +>T2 : Symbol(T2, Decl(didYouMeanStringLiteral.ts, 0, 42)) + +const t3: T3 = "strong"; +>t3 : Symbol(t3, Decl(didYouMeanStringLiteral.ts, 6, 5)) +>T3 : Symbol(T3, Decl(didYouMeanStringLiteral.ts, 1, 38)) + diff --git a/tests/baselines/reference/didYouMeanStringLiteral.types b/tests/baselines/reference/didYouMeanStringLiteral.types new file mode 100644 index 0000000000000..c348534c65212 --- /dev/null +++ b/tests/baselines/reference/didYouMeanStringLiteral.types @@ -0,0 +1,22 @@ +=== tests/cases/compiler/didYouMeanStringLiteral.ts === +type T1 = "string" | "number" | "boolean"; +>T1 : T1 + +type T2 = T1 & ("number" | "boolean"); // "number" | "boolean" +>T2 : "number" | "boolean" + +type T3 = T1 & ("string" | "boolean"); // "string" | "boolean" +>T3 : "string" | "boolean" + +const t1: T1 = "strong"; +>t1 : T1 +>"strong" : "strong" + +const t2: T2 = "strong"; +>t2 : "number" | "boolean" +>"strong" : "strong" + +const t3: T3 = "strong"; +>t3 : "string" | "boolean" +>"strong" : "strong" + diff --git a/tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt b/tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt index 93996556e651b..cf1c7f0bb5b27 100644 --- a/tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt +++ b/tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt @@ -1,5 +1,5 @@ -tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(11,11): error TS2322: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. -tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS2322: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. +tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(11,11): error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean 'hddvd'? +tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean 'hddvd'? ==== tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts (2 errors) ==== @@ -15,7 +15,7 @@ tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS232 { kind: "bluray", }, { kind: "hdpvd", } ~~~~ -!!! error TS2322: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. +!!! error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean 'hddvd'? !!! related TS6500 tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts:3:13: The expected type comes from property 'kind' which is declared here on type 'Disc' ]); @@ -23,7 +23,7 @@ tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS232 { kind: "bluray", }, { kind: "hdpvd", } ~~~~ -!!! error TS2322: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. +!!! error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean 'hddvd'? !!! related TS6500 tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts:3:13: The expected type comes from property 'kind' which is declared here on type 'Disc' ]; } \ No newline at end of file diff --git a/tests/cases/compiler/didYouMeanStringLiteral.ts b/tests/cases/compiler/didYouMeanStringLiteral.ts new file mode 100644 index 0000000000000..7110507de8b59 --- /dev/null +++ b/tests/cases/compiler/didYouMeanStringLiteral.ts @@ -0,0 +1,7 @@ +type T1 = "string" | "number" | "boolean"; +type T2 = T1 & ("number" | "boolean"); // "number" | "boolean" +type T3 = T1 & ("string" | "boolean"); // "string" | "boolean" + +const t1: T1 = "strong"; +const t2: T2 = "strong"; +const t3: T3 = "strong"; From deff222138b4a22864b4bc3727146ec5635860dc Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 5 Sep 2021 11:49:39 +0900 Subject: [PATCH 2/3] Format suggested type with `typeToString` --- src/compiler/checker.ts | 17 ++++++----------- .../didYouMeanStringLiteral.errors.txt | 8 ++++---- ...orsForCallAndAssignmentAreSimilar.errors.txt | 8 ++++---- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 508ec40b1591e..5f6295075059e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17741,9 +17741,9 @@ namespace ts { } else { if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.Union) { - const suggestion = getSuggestionForNonexistentStringLiteral(source as StringLiteralType, target as UnionType); - if (suggestion) { - reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, sourceType, targetType, suggestion); + const suggestedType = getSuggestedTypeForNonexistentStringLiteralType(source as StringLiteralType, target as UnionType); + if (suggestedType) { + reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, sourceType, targetType, typeToString(suggestedType)); return; } } @@ -28134,14 +28134,9 @@ namespace ts { return suggestion; } - function getSuggestionForNonexistentStringLiteral(source: StringLiteralType, target: UnionType): string | undefined { - const candidates: string[] = []; - for (const type of target.types) { - if (type.flags & TypeFlags.StringLiteral) { - candidates.push((type as StringLiteralType).value); - } - } - return getSpellingSuggestion(source.value, candidates, (name) => name); + function getSuggestedTypeForNonexistentStringLiteralType(source: StringLiteralType, target: UnionType): StringLiteralType | undefined { + const candidates = target.types.filter((type): type is StringLiteralType => !!(type.flags & TypeFlags.StringLiteral)); + return getSpellingSuggestion(source.value, candidates, (type) => type.value); } /** diff --git a/tests/baselines/reference/didYouMeanStringLiteral.errors.txt b/tests/baselines/reference/didYouMeanStringLiteral.errors.txt index 7e8f99d70d320..1f96007277f6d 100644 --- a/tests/baselines/reference/didYouMeanStringLiteral.errors.txt +++ b/tests/baselines/reference/didYouMeanStringLiteral.errors.txt @@ -1,6 +1,6 @@ -tests/cases/compiler/didYouMeanStringLiteral.ts(5,7): error TS2820: Type '"strong"' is not assignable to type 'T1'. Did you mean 'string'? +tests/cases/compiler/didYouMeanStringLiteral.ts(5,7): error TS2820: Type '"strong"' is not assignable to type 'T1'. Did you mean '"string"'? tests/cases/compiler/didYouMeanStringLiteral.ts(6,7): error TS2322: Type '"strong"' is not assignable to type '"number" | "boolean"'. -tests/cases/compiler/didYouMeanStringLiteral.ts(7,7): error TS2820: Type '"strong"' is not assignable to type '"string" | "boolean"'. Did you mean 'string'? +tests/cases/compiler/didYouMeanStringLiteral.ts(7,7): error TS2820: Type '"strong"' is not assignable to type '"string" | "boolean"'. Did you mean '"string"'? ==== tests/cases/compiler/didYouMeanStringLiteral.ts (3 errors) ==== @@ -10,11 +10,11 @@ tests/cases/compiler/didYouMeanStringLiteral.ts(7,7): error TS2820: Type '"stron const t1: T1 = "strong"; ~~ -!!! error TS2820: Type '"strong"' is not assignable to type 'T1'. Did you mean 'string'? +!!! error TS2820: Type '"strong"' is not assignable to type 'T1'. Did you mean '"string"'? const t2: T2 = "strong"; ~~ !!! error TS2322: Type '"strong"' is not assignable to type '"number" | "boolean"'. const t3: T3 = "strong"; ~~ -!!! error TS2820: Type '"strong"' is not assignable to type '"string" | "boolean"'. Did you mean 'string'? +!!! error TS2820: Type '"strong"' is not assignable to type '"string" | "boolean"'. Did you mean '"string"'? \ No newline at end of file diff --git a/tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt b/tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt index cf1c7f0bb5b27..70498f76d6368 100644 --- a/tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt +++ b/tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt @@ -1,5 +1,5 @@ -tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(11,11): error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean 'hddvd'? -tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean 'hddvd'? +tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(11,11): error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean '"hddvd"'? +tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean '"hddvd"'? ==== tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts (2 errors) ==== @@ -15,7 +15,7 @@ tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS282 { kind: "bluray", }, { kind: "hdpvd", } ~~~~ -!!! error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean 'hddvd'? +!!! error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean '"hddvd"'? !!! related TS6500 tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts:3:13: The expected type comes from property 'kind' which is declared here on type 'Disc' ]); @@ -23,7 +23,7 @@ tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS282 { kind: "bluray", }, { kind: "hdpvd", } ~~~~ -!!! error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean 'hddvd'? +!!! error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean '"hddvd"'? !!! related TS6500 tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts:3:13: The expected type comes from property 'kind' which is declared here on type 'Disc' ]; } \ No newline at end of file From 3dba7349ce7397b1c2302bb828eb26025317eb0f Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 14 Sep 2021 20:49:21 +0900 Subject: [PATCH 3/3] Address feedback --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5f6295075059e..3fe6934882b6e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17743,7 +17743,7 @@ namespace ts { if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.Union) { const suggestedType = getSuggestedTypeForNonexistentStringLiteralType(source as StringLiteralType, target as UnionType); if (suggestedType) { - reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, sourceType, targetType, typeToString(suggestedType)); + reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, generalizedSourceType, targetType, typeToString(suggestedType)); return; } } @@ -28136,7 +28136,7 @@ namespace ts { function getSuggestedTypeForNonexistentStringLiteralType(source: StringLiteralType, target: UnionType): StringLiteralType | undefined { const candidates = target.types.filter((type): type is StringLiteralType => !!(type.flags & TypeFlags.StringLiteral)); - return getSpellingSuggestion(source.value, candidates, (type) => type.value); + return getSpellingSuggestion(source.value, candidates, type => type.value); } /**