diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dfa8c72d36645..44a3241045cc4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -32810,7 +32810,7 @@ namespace ts { case SyntaxKind.MinusToken: case SyntaxKind.TildeToken: checkNonNullType(operandType, node.operand); - if (maybeTypeOfKind(operandType, TypeFlags.ESSymbolLike)) { + if (maybeTypeOfKindConsideringBaseConstraint(operandType, TypeFlags.ESSymbolLike)) { error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator)); } if (node.operator === SyntaxKind.PlusToken) { @@ -32871,6 +32871,15 @@ namespace ts { return numberType; } + function maybeTypeOfKindConsideringBaseConstraint(type: Type, kind: TypeFlags): boolean { + if (maybeTypeOfKind(type, kind)) { + return true; + } + + const baseConstraint = getBaseConstraintOrType(type); + return !!baseConstraint && maybeTypeOfKind(baseConstraint, kind); + } + // Return true if type might be of the given kind. A union or intersection type might be of a given // kind if at least one constituent type is of the given kind. function maybeTypeOfKind(type: Type, kind: TypeFlags): boolean { @@ -33654,8 +33663,8 @@ namespace ts { // Return true if there was no error, false if there was an error. function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean { const offendingSymbolOperand = - maybeTypeOfKind(leftType, TypeFlags.ESSymbolLike) ? left : - maybeTypeOfKind(rightType, TypeFlags.ESSymbolLike) ? right : + maybeTypeOfKindConsideringBaseConstraint(leftType, TypeFlags.ESSymbolLike) ? left : + maybeTypeOfKindConsideringBaseConstraint(rightType, TypeFlags.ESSymbolLike) ? right : undefined; if (offendingSymbolOperand) { @@ -33893,7 +33902,7 @@ namespace ts { const types = []; for (const span of node.templateSpans) { const type = checkExpression(span.expression); - if (maybeTypeOfKind(type, TypeFlags.ESSymbolLike)) { + if (maybeTypeOfKindConsideringBaseConstraint(type, TypeFlags.ESSymbolLike)) { error(span.expression, Diagnostics.Implicit_conversion_of_a_symbol_to_a_string_will_fail_at_runtime_Consider_wrapping_this_expression_in_String); } texts.push(span.literal.text); diff --git a/tests/baselines/reference/noImplicitSymbolToString.errors.txt b/tests/baselines/reference/noImplicitSymbolToString.errors.txt index cb664d9a8b93e..b90cf982dd398 100644 --- a/tests/baselines/reference/noImplicitSymbolToString.errors.txt +++ b/tests/baselines/reference/noImplicitSymbolToString.errors.txt @@ -3,9 +3,19 @@ tests/cases/compiler/noImplicitSymbolToString.ts(7,30): error TS2469: The '+' op tests/cases/compiler/noImplicitSymbolToString.ts(8,8): error TS2469: The '+=' operator cannot be applied to type 'symbol'. tests/cases/compiler/noImplicitSymbolToString.ts(13,47): error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. tests/cases/compiler/noImplicitSymbolToString.ts(13,90): error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. +tests/cases/compiler/noImplicitSymbolToString.ts(21,15): error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. +tests/cases/compiler/noImplicitSymbolToString.ts(26,8): error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. +tests/cases/compiler/noImplicitSymbolToString.ts(27,5): error TS2469: The '+' operator cannot be applied to type 'symbol'. +tests/cases/compiler/noImplicitSymbolToString.ts(28,6): error TS2469: The '+' operator cannot be applied to type 'symbol'. +tests/cases/compiler/noImplicitSymbolToString.ts(31,8): error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. +tests/cases/compiler/noImplicitSymbolToString.ts(32,5): error TS2469: The '+' operator cannot be applied to type 'symbol'. +tests/cases/compiler/noImplicitSymbolToString.ts(33,6): error TS2469: The '+' operator cannot be applied to type 'symbol'. +tests/cases/compiler/noImplicitSymbolToString.ts(43,8): error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. +tests/cases/compiler/noImplicitSymbolToString.ts(44,5): error TS2469: The '+' operator cannot be applied to type 'symbol'. +tests/cases/compiler/noImplicitSymbolToString.ts(45,6): error TS2469: The '+' operator cannot be applied to type 'symbol'. -==== tests/cases/compiler/noImplicitSymbolToString.ts (5 errors) ==== +==== tests/cases/compiler/noImplicitSymbolToString.ts (15 errors) ==== // Fix #19666 let symbol!: symbol; @@ -29,4 +39,57 @@ tests/cases/compiler/noImplicitSymbolToString.ts(13,90): error TS2731: Implicit !!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. ~~~~~~~~~~~~~~~~~ !!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. + + + // Fix #44462 + + type StringOrSymbol = string | symbol; + + function getKey(key: S) { + return `${key} is the key`; + ~~~ +!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. + } + + function getKey1(key: S) { + let s1!: S; + `${s1}`; + ~~ +!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. + s1 + ''; + ~~ +!!! error TS2469: The '+' operator cannot be applied to type 'symbol'. + +s1; + ~~ +!!! error TS2469: The '+' operator cannot be applied to type 'symbol'. + + let s2!: S | string; + `${s2}`; + ~~ +!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. + s2 + ''; + ~~ +!!! error TS2469: The '+' operator cannot be applied to type 'symbol'. + +s2; + ~~ +!!! error TS2469: The '+' operator cannot be applied to type 'symbol'. + } + + function getKey2(key: S) { + let s1!: S; + `${s1}`; + s1 + ''; + +s1; + + let s2!: S | symbol; + `${s2}`; + ~~ +!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. + s2 + ''; + ~~ +!!! error TS2469: The '+' operator cannot be applied to type 'symbol'. + +s2; + ~~ +!!! error TS2469: The '+' operator cannot be applied to type 'symbol'. + } \ No newline at end of file diff --git a/tests/baselines/reference/noImplicitSymbolToString.js b/tests/baselines/reference/noImplicitSymbolToString.js index fed276b8a731e..68f581faa3924 100644 --- a/tests/baselines/reference/noImplicitSymbolToString.js +++ b/tests/baselines/reference/noImplicitSymbolToString.js @@ -12,6 +12,39 @@ let symbolUnionNumber!: symbol | number; let symbolUnionString!: symbol | string; const templateStrUnion = `union with number ${symbolUnionNumber} and union with string ${symbolUnionString}`; + + +// Fix #44462 + +type StringOrSymbol = string | symbol; + +function getKey(key: S) { + return `${key} is the key`; +} + +function getKey1(key: S) { + let s1!: S; + `${s1}`; + s1 + ''; + +s1; + + let s2!: S | string; + `${s2}`; + s2 + ''; + +s2; +} + +function getKey2(key: S) { + let s1!: S; + `${s1}`; + s1 + ''; + +s1; + + let s2!: S | symbol; + `${s2}`; + s2 + ''; + +s2; +} //// [noImplicitSymbolToString.js] @@ -24,3 +57,26 @@ str += symbol; var symbolUnionNumber; var symbolUnionString; var templateStrUnion = "union with number ".concat(symbolUnionNumber, " and union with string ").concat(symbolUnionString); +function getKey(key) { + return "".concat(key, " is the key"); +} +function getKey1(key) { + var s1; + "".concat(s1); + s1 + ''; + +s1; + var s2; + "".concat(s2); + s2 + ''; + +s2; +} +function getKey2(key) { + var s1; + "".concat(s1); + s1 + ''; + +s1; + var s2; + "".concat(s2); + s2 + ''; + +s2; +} diff --git a/tests/baselines/reference/noImplicitSymbolToString.symbols b/tests/baselines/reference/noImplicitSymbolToString.symbols index b2a08e151ab64..4922e1a112c99 100644 --- a/tests/baselines/reference/noImplicitSymbolToString.symbols +++ b/tests/baselines/reference/noImplicitSymbolToString.symbols @@ -30,3 +30,86 @@ const templateStrUnion = `union with number ${symbolUnionNumber} and union with >symbolUnionNumber : Symbol(symbolUnionNumber, Decl(noImplicitSymbolToString.ts, 9, 3)) >symbolUnionString : Symbol(symbolUnionString, Decl(noImplicitSymbolToString.ts, 10, 3)) + +// Fix #44462 + +type StringOrSymbol = string | symbol; +>StringOrSymbol : Symbol(StringOrSymbol, Decl(noImplicitSymbolToString.ts, 12, 109)) + +function getKey(key: S) { +>getKey : Symbol(getKey, Decl(noImplicitSymbolToString.ts, 17, 38)) +>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 19, 16)) +>StringOrSymbol : Symbol(StringOrSymbol, Decl(noImplicitSymbolToString.ts, 12, 109)) +>key : Symbol(key, Decl(noImplicitSymbolToString.ts, 19, 42)) +>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 19, 16)) + + return `${key} is the key`; +>key : Symbol(key, Decl(noImplicitSymbolToString.ts, 19, 42)) +} + +function getKey1(key: S) { +>getKey1 : Symbol(getKey1, Decl(noImplicitSymbolToString.ts, 21, 1)) +>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 23, 17)) +>key : Symbol(key, Decl(noImplicitSymbolToString.ts, 23, 35)) +>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 23, 17)) + + let s1!: S; +>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 24, 7)) +>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 23, 17)) + + `${s1}`; +>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 24, 7)) + + s1 + ''; +>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 24, 7)) + + +s1; +>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 24, 7)) + + let s2!: S | string; +>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 29, 7)) +>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 23, 17)) + + `${s2}`; +>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 29, 7)) + + s2 + ''; +>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 29, 7)) + + +s2; +>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 29, 7)) +} + +function getKey2(key: S) { +>getKey2 : Symbol(getKey2, Decl(noImplicitSymbolToString.ts, 33, 1)) +>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 35, 17)) +>key : Symbol(key, Decl(noImplicitSymbolToString.ts, 35, 35)) +>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 35, 17)) + + let s1!: S; +>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 36, 7)) +>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 35, 17)) + + `${s1}`; +>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 36, 7)) + + s1 + ''; +>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 36, 7)) + + +s1; +>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 36, 7)) + + let s2!: S | symbol; +>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 41, 7)) +>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 35, 17)) + + `${s2}`; +>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 41, 7)) + + s2 + ''; +>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 41, 7)) + + +s2; +>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 41, 7)) +} + diff --git a/tests/baselines/reference/noImplicitSymbolToString.types b/tests/baselines/reference/noImplicitSymbolToString.types index c21c0e07bfded..7111542c4a318 100644 --- a/tests/baselines/reference/noImplicitSymbolToString.types +++ b/tests/baselines/reference/noImplicitSymbolToString.types @@ -36,3 +36,92 @@ const templateStrUnion = `union with number ${symbolUnionNumber} and union with >symbolUnionNumber : number | symbol >symbolUnionString : string | symbol + +// Fix #44462 + +type StringOrSymbol = string | symbol; +>StringOrSymbol : StringOrSymbol + +function getKey(key: S) { +>getKey : (key: S) => string +>key : S + + return `${key} is the key`; +>`${key} is the key` : string +>key : S +} + +function getKey1(key: S) { +>getKey1 : (key: S) => void +>key : S + + let s1!: S; +>s1 : S + + `${s1}`; +>`${s1}` : string +>s1 : S + + s1 + ''; +>s1 + '' : string +>s1 : S +>'' : "" + + +s1; +>+s1 : number +>s1 : S + + let s2!: S | string; +>s2 : string | S + + `${s2}`; +>`${s2}` : string +>s2 : string | S + + s2 + ''; +>s2 + '' : string +>s2 : string | S +>'' : "" + + +s2; +>+s2 : number +>s2 : string | S +} + +function getKey2(key: S) { +>getKey2 : (key: S) => void +>key : S + + let s1!: S; +>s1 : S + + `${s1}`; +>`${s1}` : string +>s1 : S + + s1 + ''; +>s1 + '' : string +>s1 : S +>'' : "" + + +s1; +>+s1 : number +>s1 : S + + let s2!: S | symbol; +>s2 : symbol | S + + `${s2}`; +>`${s2}` : string +>s2 : symbol | S + + s2 + ''; +>s2 + '' : string +>s2 : symbol | S +>'' : "" + + +s2; +>+s2 : number +>s2 : symbol | S +} + diff --git a/tests/cases/compiler/noImplicitSymbolToString.ts b/tests/cases/compiler/noImplicitSymbolToString.ts index 16690d6d845cb..a3a797c5f67a8 100644 --- a/tests/cases/compiler/noImplicitSymbolToString.ts +++ b/tests/cases/compiler/noImplicitSymbolToString.ts @@ -11,3 +11,36 @@ let symbolUnionNumber!: symbol | number; let symbolUnionString!: symbol | string; const templateStrUnion = `union with number ${symbolUnionNumber} and union with string ${symbolUnionString}`; + + +// Fix #44462 + +type StringOrSymbol = string | symbol; + +function getKey(key: S) { + return `${key} is the key`; +} + +function getKey1(key: S) { + let s1!: S; + `${s1}`; + s1 + ''; + +s1; + + let s2!: S | string; + `${s2}`; + s2 + ''; + +s2; +} + +function getKey2(key: S) { + let s1!: S; + `${s1}`; + s1 + ''; + +s1; + + let s2!: S | symbol; + `${s2}`; + s2 + ''; + +s2; +}