diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index da1af15dfa458..0cb09dfeae41c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11985,9 +11985,36 @@ namespace ts { } return Ternary.False; } - const related = isRelatedTo(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp), reportErrors); + + const sourceType = getTypeOfSymbol(sourceProp); + const targetType = getTypeOfSymbol(targetProp); + const related = isRelatedTo(sourceType, targetType, reportErrors); if (!related) { if (reportErrors) { + if (sourceProp.valueDeclaration && isLiteralType(targetType) && isPropertyAssignment(sourceProp.valueDeclaration) && sourceProp.valueDeclaration.initializer) { + const variableLikeDeclaration = findAncestor(sourceProp.valueDeclaration.parent, element => { + if (isParenthesizedExpression(element) || isPropertyAssignment(element) || isObjectLiteralExpression(element)) return false; + if (isVariableDeclaration(element)) return true; + return "quit"; + }) as VariableDeclaration | undefined; + if (variableLikeDeclaration && !variableLikeDeclaration.type && variableLikeDeclaration.initializer) { + const initializer = sourceProp.valueDeclaration.initializer; + let value: string | undefined; + forEachType(targetType, t => { + if (isStringOrNumericLiteralLike(initializer) && t.flags & TypeFlags.StringOrNumberLiteral && (t).value === initializer.text) { + value = initializer.text; + return true; + } + else if (initializer.kind === SyntaxKind.TrueKeyword || initializer.kind === SyntaxKind.FalseKeyword && t.flags & TypeFlags.BooleanLiteral) { + return true; + } + return false; + }); + if(value) { + reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2_as_2, typeToString(sourceType), typeToString(targetType), value); + } + } + } reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); } return Ternary.False; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9ba3c4e8cf150..3992f9d2aaf7c 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2437,6 +2437,10 @@ "category": "Error", "code": 2732 }, + "Type '{0}' is not assignable to type '{1}'. Did you mean '{2} as {2}'?": { + "category": "Error", + "code": 2733 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/stringAssignToStringLiteralType.errors.txt b/tests/baselines/reference/stringAssignToStringLiteralType.errors.txt new file mode 100644 index 0000000000000..783fb68ef0c40 --- /dev/null +++ b/tests/baselines/reference/stringAssignToStringLiteralType.errors.txt @@ -0,0 +1,19 @@ +tests/cases/compiler/stringAssignToStringLiteralType.ts(6,10): error TS2345: Argument of type '{ cherry: string; }' is not assignable to parameter of type 'IceCreamOptions'. + Types of property 'cherry' are incompatible. + Type 'string' is not assignable to type '"yes" | "no"'. Did you mean 'yes as yes'? + Type 'string' is not assignable to type '"yes" | "no"'. + + +==== tests/cases/compiler/stringAssignToStringLiteralType.ts (1 errors) ==== + interface IceCreamOptions { + readonly cherry: "yes" | "no" + } + declare function iceCream(options: IceCreamOptions): void; + const options = { cherry: "yes" }; + iceCream(options); + ~~~~~~~ +!!! error TS2345: Argument of type '{ cherry: string; }' is not assignable to parameter of type 'IceCreamOptions'. +!!! error TS2345: Types of property 'cherry' are incompatible. +!!! error TS2345: Type 'string' is not assignable to type '"yes" | "no"'. Did you mean 'yes as yes'? +!!! error TS2345: Type 'string' is not assignable to type '"yes" | "no"'. + \ No newline at end of file diff --git a/tests/baselines/reference/stringAssignToStringLiteralType.js b/tests/baselines/reference/stringAssignToStringLiteralType.js new file mode 100644 index 0000000000000..aeeb405c0b559 --- /dev/null +++ b/tests/baselines/reference/stringAssignToStringLiteralType.js @@ -0,0 +1,12 @@ +//// [stringAssignToStringLiteralType.ts] +interface IceCreamOptions { + readonly cherry: "yes" | "no" +} +declare function iceCream(options: IceCreamOptions): void; +const options = { cherry: "yes" }; +iceCream(options); + + +//// [stringAssignToStringLiteralType.js] +var options = { cherry: "yes" }; +iceCream(options); diff --git a/tests/baselines/reference/stringAssignToStringLiteralType.symbols b/tests/baselines/reference/stringAssignToStringLiteralType.symbols new file mode 100644 index 0000000000000..e6a8da3013f76 --- /dev/null +++ b/tests/baselines/reference/stringAssignToStringLiteralType.symbols @@ -0,0 +1,20 @@ +=== tests/cases/compiler/stringAssignToStringLiteralType.ts === +interface IceCreamOptions { +>IceCreamOptions : Symbol(IceCreamOptions, Decl(stringAssignToStringLiteralType.ts, 0, 0)) + + readonly cherry: "yes" | "no" +>cherry : Symbol(IceCreamOptions.cherry, Decl(stringAssignToStringLiteralType.ts, 0, 27)) +} +declare function iceCream(options: IceCreamOptions): void; +>iceCream : Symbol(iceCream, Decl(stringAssignToStringLiteralType.ts, 2, 1)) +>options : Symbol(options, Decl(stringAssignToStringLiteralType.ts, 3, 26)) +>IceCreamOptions : Symbol(IceCreamOptions, Decl(stringAssignToStringLiteralType.ts, 0, 0)) + +const options = { cherry: "yes" }; +>options : Symbol(options, Decl(stringAssignToStringLiteralType.ts, 4, 5)) +>cherry : Symbol(cherry, Decl(stringAssignToStringLiteralType.ts, 4, 17)) + +iceCream(options); +>iceCream : Symbol(iceCream, Decl(stringAssignToStringLiteralType.ts, 2, 1)) +>options : Symbol(options, Decl(stringAssignToStringLiteralType.ts, 4, 5)) + diff --git a/tests/baselines/reference/stringAssignToStringLiteralType.types b/tests/baselines/reference/stringAssignToStringLiteralType.types new file mode 100644 index 0000000000000..f896808e0a116 --- /dev/null +++ b/tests/baselines/reference/stringAssignToStringLiteralType.types @@ -0,0 +1,20 @@ +=== tests/cases/compiler/stringAssignToStringLiteralType.ts === +interface IceCreamOptions { + readonly cherry: "yes" | "no" +>cherry : "yes" | "no" +} +declare function iceCream(options: IceCreamOptions): void; +>iceCream : (options: IceCreamOptions) => void +>options : IceCreamOptions + +const options = { cherry: "yes" }; +>options : { cherry: string; } +>{ cherry: "yes" } : { cherry: string; } +>cherry : string +>"yes" : "yes" + +iceCream(options); +>iceCream(options) : void +>iceCream : (options: IceCreamOptions) => void +>options : { cherry: string; } + diff --git a/tests/cases/compiler/stringAssignToStringLiteralType.ts b/tests/cases/compiler/stringAssignToStringLiteralType.ts new file mode 100644 index 0000000000000..899a24c124f69 --- /dev/null +++ b/tests/cases/compiler/stringAssignToStringLiteralType.ts @@ -0,0 +1,6 @@ +interface IceCreamOptions { + readonly cherry: "yes" | "no" +} +declare function iceCream(options: IceCreamOptions): void; +const options = { cherry: "yes" }; +iceCream(options);