Skip to content

Commit f9ae305

Browse files
Zzzenweswigham
andauthored
support generic type when checking implicit conversion of symbol to string (#44578)
Co-authored-by: Wesley Wigham <wewigham@microsoft.com>
1 parent 774899f commit f9ae305

6 files changed

+338
-5
lines changed

src/compiler/checker.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -32810,7 +32810,7 @@ namespace ts {
3281032810
case SyntaxKind.MinusToken:
3281132811
case SyntaxKind.TildeToken:
3281232812
checkNonNullType(operandType, node.operand);
32813-
if (maybeTypeOfKind(operandType, TypeFlags.ESSymbolLike)) {
32813+
if (maybeTypeOfKindConsideringBaseConstraint(operandType, TypeFlags.ESSymbolLike)) {
3281432814
error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator));
3281532815
}
3281632816
if (node.operator === SyntaxKind.PlusToken) {
@@ -32871,6 +32871,15 @@ namespace ts {
3287132871
return numberType;
3287232872
}
3287332873

32874+
function maybeTypeOfKindConsideringBaseConstraint(type: Type, kind: TypeFlags): boolean {
32875+
if (maybeTypeOfKind(type, kind)) {
32876+
return true;
32877+
}
32878+
32879+
const baseConstraint = getBaseConstraintOrType(type);
32880+
return !!baseConstraint && maybeTypeOfKind(baseConstraint, kind);
32881+
}
32882+
3287432883
// Return true if type might be of the given kind. A union or intersection type might be of a given
3287532884
// kind if at least one constituent type is of the given kind.
3287632885
function maybeTypeOfKind(type: Type, kind: TypeFlags): boolean {
@@ -33654,8 +33663,8 @@ namespace ts {
3365433663
// Return true if there was no error, false if there was an error.
3365533664
function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean {
3365633665
const offendingSymbolOperand =
33657-
maybeTypeOfKind(leftType, TypeFlags.ESSymbolLike) ? left :
33658-
maybeTypeOfKind(rightType, TypeFlags.ESSymbolLike) ? right :
33666+
maybeTypeOfKindConsideringBaseConstraint(leftType, TypeFlags.ESSymbolLike) ? left :
33667+
maybeTypeOfKindConsideringBaseConstraint(rightType, TypeFlags.ESSymbolLike) ? right :
3365933668
undefined;
3366033669

3366133670
if (offendingSymbolOperand) {
@@ -33893,7 +33902,7 @@ namespace ts {
3389333902
const types = [];
3389433903
for (const span of node.templateSpans) {
3389533904
const type = checkExpression(span.expression);
33896-
if (maybeTypeOfKind(type, TypeFlags.ESSymbolLike)) {
33905+
if (maybeTypeOfKindConsideringBaseConstraint(type, TypeFlags.ESSymbolLike)) {
3389733906
error(span.expression, Diagnostics.Implicit_conversion_of_a_symbol_to_a_string_will_fail_at_runtime_Consider_wrapping_this_expression_in_String);
3389833907
}
3389933908
texts.push(span.literal.text);

tests/baselines/reference/noImplicitSymbolToString.errors.txt

+64-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,19 @@ tests/cases/compiler/noImplicitSymbolToString.ts(7,30): error TS2469: The '+' op
33
tests/cases/compiler/noImplicitSymbolToString.ts(8,8): error TS2469: The '+=' operator cannot be applied to type 'symbol'.
44
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(...)'.
55
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(...)'.
6+
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(...)'.
7+
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(...)'.
8+
tests/cases/compiler/noImplicitSymbolToString.ts(27,5): error TS2469: The '+' operator cannot be applied to type 'symbol'.
9+
tests/cases/compiler/noImplicitSymbolToString.ts(28,6): error TS2469: The '+' operator cannot be applied to type 'symbol'.
10+
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(...)'.
11+
tests/cases/compiler/noImplicitSymbolToString.ts(32,5): error TS2469: The '+' operator cannot be applied to type 'symbol'.
12+
tests/cases/compiler/noImplicitSymbolToString.ts(33,6): error TS2469: The '+' operator cannot be applied to type 'symbol'.
13+
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(...)'.
14+
tests/cases/compiler/noImplicitSymbolToString.ts(44,5): error TS2469: The '+' operator cannot be applied to type 'symbol'.
15+
tests/cases/compiler/noImplicitSymbolToString.ts(45,6): error TS2469: The '+' operator cannot be applied to type 'symbol'.
616

717

8-
==== tests/cases/compiler/noImplicitSymbolToString.ts (5 errors) ====
18+
==== tests/cases/compiler/noImplicitSymbolToString.ts (15 errors) ====
919
// Fix #19666
1020

1121
let symbol!: symbol;
@@ -29,4 +39,57 @@ tests/cases/compiler/noImplicitSymbolToString.ts(13,90): error TS2731: Implicit
2939
!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'.
3040
~~~~~~~~~~~~~~~~~
3141
!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'.
42+
43+
44+
// Fix #44462
45+
46+
type StringOrSymbol = string | symbol;
47+
48+
function getKey<S extends StringOrSymbol>(key: S) {
49+
return `${key} is the key`;
50+
~~~
51+
!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'.
52+
}
53+
54+
function getKey1<S extends symbol>(key: S) {
55+
let s1!: S;
56+
`${s1}`;
57+
~~
58+
!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'.
59+
s1 + '';
60+
~~
61+
!!! error TS2469: The '+' operator cannot be applied to type 'symbol'.
62+
+s1;
63+
~~
64+
!!! error TS2469: The '+' operator cannot be applied to type 'symbol'.
65+
66+
let s2!: S | string;
67+
`${s2}`;
68+
~~
69+
!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'.
70+
s2 + '';
71+
~~
72+
!!! error TS2469: The '+' operator cannot be applied to type 'symbol'.
73+
+s2;
74+
~~
75+
!!! error TS2469: The '+' operator cannot be applied to type 'symbol'.
76+
}
77+
78+
function getKey2<S extends string>(key: S) {
79+
let s1!: S;
80+
`${s1}`;
81+
s1 + '';
82+
+s1;
83+
84+
let s2!: S | symbol;
85+
`${s2}`;
86+
~~
87+
!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'.
88+
s2 + '';
89+
~~
90+
!!! error TS2469: The '+' operator cannot be applied to type 'symbol'.
91+
+s2;
92+
~~
93+
!!! error TS2469: The '+' operator cannot be applied to type 'symbol'.
94+
}
3295

tests/baselines/reference/noImplicitSymbolToString.js

+56
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,39 @@ let symbolUnionNumber!: symbol | number;
1212
let symbolUnionString!: symbol | string;
1313

1414
const templateStrUnion = `union with number ${symbolUnionNumber} and union with string ${symbolUnionString}`;
15+
16+
17+
// Fix #44462
18+
19+
type StringOrSymbol = string | symbol;
20+
21+
function getKey<S extends StringOrSymbol>(key: S) {
22+
return `${key} is the key`;
23+
}
24+
25+
function getKey1<S extends symbol>(key: S) {
26+
let s1!: S;
27+
`${s1}`;
28+
s1 + '';
29+
+s1;
30+
31+
let s2!: S | string;
32+
`${s2}`;
33+
s2 + '';
34+
+s2;
35+
}
36+
37+
function getKey2<S extends string>(key: S) {
38+
let s1!: S;
39+
`${s1}`;
40+
s1 + '';
41+
+s1;
42+
43+
let s2!: S | symbol;
44+
`${s2}`;
45+
s2 + '';
46+
+s2;
47+
}
1548

1649

1750
//// [noImplicitSymbolToString.js]
@@ -24,3 +57,26 @@ str += symbol;
2457
var symbolUnionNumber;
2558
var symbolUnionString;
2659
var templateStrUnion = "union with number ".concat(symbolUnionNumber, " and union with string ").concat(symbolUnionString);
60+
function getKey(key) {
61+
return "".concat(key, " is the key");
62+
}
63+
function getKey1(key) {
64+
var s1;
65+
"".concat(s1);
66+
s1 + '';
67+
+s1;
68+
var s2;
69+
"".concat(s2);
70+
s2 + '';
71+
+s2;
72+
}
73+
function getKey2(key) {
74+
var s1;
75+
"".concat(s1);
76+
s1 + '';
77+
+s1;
78+
var s2;
79+
"".concat(s2);
80+
s2 + '';
81+
+s2;
82+
}

tests/baselines/reference/noImplicitSymbolToString.symbols

+83
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,86 @@ const templateStrUnion = `union with number ${symbolUnionNumber} and union with
3030
>symbolUnionNumber : Symbol(symbolUnionNumber, Decl(noImplicitSymbolToString.ts, 9, 3))
3131
>symbolUnionString : Symbol(symbolUnionString, Decl(noImplicitSymbolToString.ts, 10, 3))
3232

33+
34+
// Fix #44462
35+
36+
type StringOrSymbol = string | symbol;
37+
>StringOrSymbol : Symbol(StringOrSymbol, Decl(noImplicitSymbolToString.ts, 12, 109))
38+
39+
function getKey<S extends StringOrSymbol>(key: S) {
40+
>getKey : Symbol(getKey, Decl(noImplicitSymbolToString.ts, 17, 38))
41+
>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 19, 16))
42+
>StringOrSymbol : Symbol(StringOrSymbol, Decl(noImplicitSymbolToString.ts, 12, 109))
43+
>key : Symbol(key, Decl(noImplicitSymbolToString.ts, 19, 42))
44+
>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 19, 16))
45+
46+
return `${key} is the key`;
47+
>key : Symbol(key, Decl(noImplicitSymbolToString.ts, 19, 42))
48+
}
49+
50+
function getKey1<S extends symbol>(key: S) {
51+
>getKey1 : Symbol(getKey1, Decl(noImplicitSymbolToString.ts, 21, 1))
52+
>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 23, 17))
53+
>key : Symbol(key, Decl(noImplicitSymbolToString.ts, 23, 35))
54+
>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 23, 17))
55+
56+
let s1!: S;
57+
>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 24, 7))
58+
>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 23, 17))
59+
60+
`${s1}`;
61+
>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 24, 7))
62+
63+
s1 + '';
64+
>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 24, 7))
65+
66+
+s1;
67+
>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 24, 7))
68+
69+
let s2!: S | string;
70+
>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 29, 7))
71+
>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 23, 17))
72+
73+
`${s2}`;
74+
>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 29, 7))
75+
76+
s2 + '';
77+
>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 29, 7))
78+
79+
+s2;
80+
>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 29, 7))
81+
}
82+
83+
function getKey2<S extends string>(key: S) {
84+
>getKey2 : Symbol(getKey2, Decl(noImplicitSymbolToString.ts, 33, 1))
85+
>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 35, 17))
86+
>key : Symbol(key, Decl(noImplicitSymbolToString.ts, 35, 35))
87+
>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 35, 17))
88+
89+
let s1!: S;
90+
>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 36, 7))
91+
>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 35, 17))
92+
93+
`${s1}`;
94+
>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 36, 7))
95+
96+
s1 + '';
97+
>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 36, 7))
98+
99+
+s1;
100+
>s1 : Symbol(s1, Decl(noImplicitSymbolToString.ts, 36, 7))
101+
102+
let s2!: S | symbol;
103+
>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 41, 7))
104+
>S : Symbol(S, Decl(noImplicitSymbolToString.ts, 35, 17))
105+
106+
`${s2}`;
107+
>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 41, 7))
108+
109+
s2 + '';
110+
>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 41, 7))
111+
112+
+s2;
113+
>s2 : Symbol(s2, Decl(noImplicitSymbolToString.ts, 41, 7))
114+
}
115+

tests/baselines/reference/noImplicitSymbolToString.types

+89
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,92 @@ const templateStrUnion = `union with number ${symbolUnionNumber} and union with
3636
>symbolUnionNumber : number | symbol
3737
>symbolUnionString : string | symbol
3838

39+
40+
// Fix #44462
41+
42+
type StringOrSymbol = string | symbol;
43+
>StringOrSymbol : StringOrSymbol
44+
45+
function getKey<S extends StringOrSymbol>(key: S) {
46+
>getKey : <S extends StringOrSymbol>(key: S) => string
47+
>key : S
48+
49+
return `${key} is the key`;
50+
>`${key} is the key` : string
51+
>key : S
52+
}
53+
54+
function getKey1<S extends symbol>(key: S) {
55+
>getKey1 : <S extends symbol>(key: S) => void
56+
>key : S
57+
58+
let s1!: S;
59+
>s1 : S
60+
61+
`${s1}`;
62+
>`${s1}` : string
63+
>s1 : S
64+
65+
s1 + '';
66+
>s1 + '' : string
67+
>s1 : S
68+
>'' : ""
69+
70+
+s1;
71+
>+s1 : number
72+
>s1 : S
73+
74+
let s2!: S | string;
75+
>s2 : string | S
76+
77+
`${s2}`;
78+
>`${s2}` : string
79+
>s2 : string | S
80+
81+
s2 + '';
82+
>s2 + '' : string
83+
>s2 : string | S
84+
>'' : ""
85+
86+
+s2;
87+
>+s2 : number
88+
>s2 : string | S
89+
}
90+
91+
function getKey2<S extends string>(key: S) {
92+
>getKey2 : <S extends string>(key: S) => void
93+
>key : S
94+
95+
let s1!: S;
96+
>s1 : S
97+
98+
`${s1}`;
99+
>`${s1}` : string
100+
>s1 : S
101+
102+
s1 + '';
103+
>s1 + '' : string
104+
>s1 : S
105+
>'' : ""
106+
107+
+s1;
108+
>+s1 : number
109+
>s1 : S
110+
111+
let s2!: S | symbol;
112+
>s2 : symbol | S
113+
114+
`${s2}`;
115+
>`${s2}` : string
116+
>s2 : symbol | S
117+
118+
s2 + '';
119+
>s2 + '' : string
120+
>s2 : symbol | S
121+
>'' : ""
122+
123+
+s2;
124+
>+s2 : number
125+
>s2 : symbol | S
126+
}
127+

0 commit comments

Comments
 (0)