Skip to content

Commit be9b85d

Browse files
committed
Treat void-typed properties as optional
1 parent 3e824f1 commit be9b85d

11 files changed

+699
-22
lines changed

src/compiler/checker.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,18 @@ namespace ts {
13521352
}
13531353
}
13541354

1355+
/**
1356+
* Attempts to return the `SymbolLinks` for `symbol`, if one already exists.
1357+
* Does not allocate a symbol id or a `SymbolLinks` if it doesn't already exists.
1358+
*/
1359+
function tryGetSymbolLinks(symbol: Symbol): SymbolLinks | undefined {
1360+
if (symbol.flags & SymbolFlags.Transient) return <TransientSymbol>symbol;
1361+
return symbol.id ? symbolLinks[symbol.id] : undefined;
1362+
}
1363+
1364+
/**
1365+
* Gets or creates a `SymbolLinks` for `symbol`.
1366+
*/
13551367
function getSymbolLinks(symbol: Symbol): SymbolLinks {
13561368
if (symbol.flags & SymbolFlags.Transient) return <TransientSymbol>symbol;
13571369
const id = getSymbolId(symbol);
@@ -7666,11 +7678,11 @@ namespace ts {
76667678
function hasType(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean {
76677679
switch (propertyName) {
76687680
case TypeSystemPropertyName.Type:
7669-
return !!getSymbolLinks(<Symbol>target).type;
7681+
return !!tryGetSymbolLinks(<Symbol>target)?.type;
76707682
case TypeSystemPropertyName.EnumTagType:
76717683
return !!(getNodeLinks(target as JSDocEnumTag).resolvedEnumType);
76727684
case TypeSystemPropertyName.DeclaredType:
7673-
return !!getSymbolLinks(<Symbol>target).declaredType;
7685+
return !!tryGetSymbolLinks(<Symbol>target)?.declaredType;
76747686
case TypeSystemPropertyName.ResolvedBaseConstructorType:
76757687
return !!(<InterfaceType>target).resolvedBaseConstructorType;
76767688
case TypeSystemPropertyName.ResolvedReturnType:
@@ -8509,6 +8521,14 @@ namespace ts {
85098521
return links.type;
85108522
}
85118523

8524+
function isOptionalProperty(prop: Symbol) {
8525+
if (prop.flags & SymbolFlags.Optional) return true;
8526+
// We don't use `getTypeOfSymbol` here as we may be in the middle of resolving the type for `prop` and
8527+
// we shouldn't re-enter to check for `void`.
8528+
const type = tryGetSymbolLinks(prop)?.type;
8529+
return !!(type && forEachType(type, acceptsVoid));
8530+
}
8531+
85128532
function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol) {
85138533
// Handle prototype property
85148534
if (symbol.flags & SymbolFlags.Prototype) {
@@ -17894,7 +17914,7 @@ namespace ts {
1789417914
return Ternary.False;
1789517915
}
1789617916
// When checking for comparability, be more lenient with optional properties.
17897-
if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) {
17917+
if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && !isOptionalProperty(targetProp)) {
1789817918
// TypeScript 1.0 spec (April 2014): 3.8.3
1789917919
// S is a subtype of a type T, and T is a supertype of S if ...
1790017920
// S' and T are object types and, for each member M in T..
@@ -19709,7 +19729,7 @@ namespace ts {
1970919729
if (isStaticPrivateIdentifierProperty(targetProp)) {
1971019730
continue;
1971119731
}
19712-
if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial)) {
19732+
if (requireOptionalProperties || !((getCheckFlags(targetProp) & CheckFlags.Partial) || isOptionalProperty(targetProp))) {
1971319733
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
1971419734
if (!sourceProp) {
1971519735
yield targetProp;

tests/baselines/reference/inlineJsxFactoryDeclarationsLocalTypes.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ tests/cases/conformance/jsx/inline/index.tsx(24,48): error TS2322: Type 'import(
2222
[e: string]: {};
2323
}
2424
interface Element {
25-
__domBrand: void;
25+
__domBrand: never;
2626
props: {
2727
children?: Element[];
2828
};
@@ -42,7 +42,7 @@ tests/cases/conformance/jsx/inline/index.tsx(24,48): error TS2322: Type 'import(
4242
[e: string]: {};
4343
}
4444
interface Element {
45-
__predomBrand: void;
45+
__predomBrand: never;
4646
props: {
4747
children?: Element[];
4848
};
@@ -66,7 +66,7 @@ tests/cases/conformance/jsx/inline/index.tsx(24,48): error TS2322: Type 'import(
6666
!!! error TS2532: Object is possibly 'undefined'.
6767

6868
export class MyClass implements predom.JSX.Element {
69-
__predomBrand!: void;
69+
__predomBrand!: never;
7070
constructor(public props: {x: number, y: number, children?: predom.JSX.Element[]}) {}
7171
render() {
7272
return <p>
@@ -92,7 +92,7 @@ tests/cases/conformance/jsx/inline/index.tsx(24,48): error TS2322: Type 'import(
9292
const DOMSFC = (props: {x: number, y: number, children?: dom.JSX.Element[]}) => <p>{props.x} + {props.y} = {props.x + props.y}{props.children}</p>;
9393

9494
class DOMClass implements dom.JSX.Element {
95-
__domBrand!: void;
95+
__domBrand!: never;
9696
constructor(public props: {x: number, y: number, children?: dom.JSX.Element[]}) {}
9797
render() {
9898
return <p>{this.props.x} + {this.props.y} = {this.props.x + this.props.y}{...this.props.children}</p>;

tests/baselines/reference/inlineJsxFactoryLocalTypeGlobalFallback.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ tests/cases/conformance/jsx/inline/index.tsx(5,1): error TS2741: Property '__pre
88
[e: string]: {};
99
}
1010
interface Element {
11-
__domBrand: void;
11+
__domBrand: never;
1212
children: Element[];
1313
props: {};
1414
}
@@ -24,7 +24,7 @@ tests/cases/conformance/jsx/inline/index.tsx(5,1): error TS2741: Property '__pre
2424
[e: string]: {};
2525
}
2626
interface Element {
27-
__predomBrand: void;
27+
__predomBrand: never;
2828
children: Element[];
2929
props: {};
3030
}

tests/baselines/reference/strictFunctionTypesErrors.errors.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,9 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(155,5): error TS2322: Type '(c
264264
i4 = i2; // Ok
265265
i4 = i3; // Ok
266266

267-
interface Animal { animal: void }
268-
interface Dog extends Animal { dog: void }
269-
interface Cat extends Animal { cat: void }
267+
interface Animal { animal: never }
268+
interface Dog extends Animal { dog: never }
269+
interface Cat extends Animal { cat: never }
270270

271271
interface Comparer1<T> {
272272
compare(a: T, b: T): number;
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(16,1): error TS2322: Type 'X<void>' is not assignable to type 'X<number>'.
2+
Type 'void' is not assignable to type 'number'.
3+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(17,1): error TS2322: Type 'X<number | void>' is not assignable to type 'X<number>'.
4+
Type 'number | void' is not assignable to type 'number'.
5+
Type 'void' is not assignable to type 'number'.
6+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(18,1): error TS2322: Type 'Y<number>' is not assignable to type 'X<number>'.
7+
Types of property 'value' are incompatible.
8+
Type 'number | undefined' is not assignable to type 'number'.
9+
Type 'undefined' is not assignable to type 'number'.
10+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(19,1): error TS2741: Property 'value' is missing in type '{ done: true; }' but required in type 'X<number>'.
11+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(21,19): error TS2322: Type 'undefined' is not assignable to type 'number'.
12+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(22,19): error TS2322: Type 'undefined' is not assignable to type 'number'.
13+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(23,19): error TS2322: Type 'void' is not assignable to type 'number'.
14+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(25,1): error TS2322: Type 'X<number>' is not assignable to type 'X<void>'.
15+
Type 'number' is not assignable to type 'void'.
16+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(26,1): error TS2322: Type 'X<number | void>' is not assignable to type 'X<void>'.
17+
Type 'number | void' is not assignable to type 'void'.
18+
Type 'number' is not assignable to type 'void'.
19+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(27,1): error TS2322: Type 'Y<number>' is not assignable to type 'X<void>'.
20+
Types of property 'value' are incompatible.
21+
Type 'number | undefined' is not assignable to type 'void'.
22+
Type 'number' is not assignable to type 'void'.
23+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(29,19): error TS2322: Type 'number' is not assignable to type 'void'.
24+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(44,1): error TS2322: Type 'X<void>' is not assignable to type 'Y<number>'.
25+
Types of property 'value' are incompatible.
26+
Type 'void' is not assignable to type 'number | undefined'.
27+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(45,1): error TS2322: Type 'X<number | void>' is not assignable to type 'Y<number>'.
28+
Types of property 'value' are incompatible.
29+
Type 'number | void' is not assignable to type 'number | undefined'.
30+
Type 'void' is not assignable to type 'number | undefined'.
31+
tests/cases/conformance/types/members/typesWithVoidProperty.ts(50,19): error TS2322: Type 'void' is not assignable to type 'number | undefined'.
32+
33+
34+
==== tests/cases/conformance/types/members/typesWithVoidProperty.ts (14 errors) ====
35+
interface X<T> {
36+
done: true;
37+
value: T;
38+
}
39+
40+
interface Y<T> {
41+
done: true;
42+
value?: T;
43+
}
44+
45+
declare let a: X<number>;
46+
declare let b: X<void>;
47+
declare let c: X<number | void>;
48+
declare let d: Y<number>;
49+
50+
a = b; // not allowed because `value` must be `number`
51+
~
52+
!!! error TS2322: Type 'X<void>' is not assignable to type 'X<number>'.
53+
!!! error TS2322: Type 'void' is not assignable to type 'number'.
54+
a = c; // not allowed because `value` must be `number`
55+
~
56+
!!! error TS2322: Type 'X<number | void>' is not assignable to type 'X<number>'.
57+
!!! error TS2322: Type 'number | void' is not assignable to type 'number'.
58+
!!! error TS2322: Type 'void' is not assignable to type 'number'.
59+
a = d; // not allowed because `value` must be `number`
60+
~
61+
!!! error TS2322: Type 'Y<number>' is not assignable to type 'X<number>'.
62+
!!! error TS2322: Types of property 'value' are incompatible.
63+
!!! error TS2322: Type 'number | undefined' is not assignable to type 'number'.
64+
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.
65+
a = { done: true }; // not allowed because `value` is not optional (non-`void`)
66+
~
67+
!!! error TS2741: Property 'value' is missing in type '{ done: true; }' but required in type 'X<number>'.
68+
!!! related TS2728 tests/cases/conformance/types/members/typesWithVoidProperty.ts:3:5: 'value' is declared here.
69+
a = { done: true, value: 1 }; // allowed because `value` must be `number`
70+
a = { done: true, value: undefined }; // not allowed because `value` must be `number`
71+
~~~~~
72+
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.
73+
!!! related TS6500 tests/cases/conformance/types/members/typesWithVoidProperty.ts:3:5: The expected type comes from property 'value' which is declared here on type 'X<number>'
74+
a = { done: true, value: undefined as undefined }; // not allowed because `value` must be `number`
75+
~~~~~
76+
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.
77+
!!! related TS6500 tests/cases/conformance/types/members/typesWithVoidProperty.ts:3:5: The expected type comes from property 'value' which is declared here on type 'X<number>'
78+
a = { done: true, value: undefined as void }; // not allowed because `value` must be `number`
79+
~~~~~
80+
!!! error TS2322: Type 'void' is not assignable to type 'number'.
81+
!!! related TS6500 tests/cases/conformance/types/members/typesWithVoidProperty.ts:3:5: The expected type comes from property 'value' which is declared here on type 'X<number>'
82+
83+
b = a; // not allowed because `value` must be `void`
84+
~
85+
!!! error TS2322: Type 'X<number>' is not assignable to type 'X<void>'.
86+
!!! error TS2322: Type 'number' is not assignable to type 'void'.
87+
b = c; // not allowed because `value` must be `void`
88+
~
89+
!!! error TS2322: Type 'X<number | void>' is not assignable to type 'X<void>'.
90+
!!! error TS2322: Type 'number | void' is not assignable to type 'void'.
91+
!!! error TS2322: Type 'number' is not assignable to type 'void'.
92+
b = d; // not allowed because `value` must be `void`
93+
~
94+
!!! error TS2322: Type 'Y<number>' is not assignable to type 'X<void>'.
95+
!!! error TS2322: Types of property 'value' are incompatible.
96+
!!! error TS2322: Type 'number | undefined' is not assignable to type 'void'.
97+
!!! error TS2322: Type 'number' is not assignable to type 'void'.
98+
b = { done: true }; // allowed because `value` is optional due to `void`
99+
b = { done: true, value: 1 }; // not allowed because `value` must be `void`
100+
~~~~~
101+
!!! error TS2322: Type 'number' is not assignable to type 'void'.
102+
!!! related TS6500 tests/cases/conformance/types/members/typesWithVoidProperty.ts:3:5: The expected type comes from property 'value' which is declared here on type 'X<void>'
103+
b = { done: true, value: undefined }; // allowed because `value` can be `undefined` (assignable to `void`)
104+
b = { done: true, value: undefined as undefined }; // allowed because `value` can be `undefined` (assignable to `void`)
105+
b = { done: true, value: undefined as void }; // allowed because `value` must be `void`
106+
107+
c = a; // allowed because `value` can be `number`
108+
c = b; // allowed because `value` can be `void`
109+
c = d; // allowed because `value` can be `undefined`
110+
c = { done: true }; // allowed because `value` is optional due to `void`
111+
c = { done: true, value: 1 }; // allowed because `value` can be `number`
112+
c = { done: true, value: undefined }; // allowed because `value` can be `undefined` (assignable to `void`)
113+
c = { done: true, value: undefined as undefined }; // allowed because `value` can be `undefined` (assignable to `void`)
114+
c = { done: true, value: undefined as void }; // allowed because `value` can be `void`
115+
116+
d = a; // allowed because `value` must be `number | void`
117+
d = b; // not allowed because `value` must be `undefined`, and `void` is a supertype of `undefined`
118+
~
119+
!!! error TS2322: Type 'X<void>' is not assignable to type 'Y<number>'.
120+
!!! error TS2322: Types of property 'value' are incompatible.
121+
!!! error TS2322: Type 'void' is not assignable to type 'number | undefined'.
122+
d = c; // not allowed allowed because `value` must be `undefined`, and `void` is a supertype of `undefined`
123+
~
124+
!!! error TS2322: Type 'X<number | void>' is not assignable to type 'Y<number>'.
125+
!!! error TS2322: Types of property 'value' are incompatible.
126+
!!! error TS2322: Type 'number | void' is not assignable to type 'number | undefined'.
127+
!!! error TS2322: Type 'void' is not assignable to type 'number | undefined'.
128+
d = { done: true }; // allowed because `value` is optional
129+
d = { done: true, value: 1 }; // allowed because `value` can be `number`
130+
d = { done: true, value: undefined }; // allowed because `value` can be `undefined`
131+
d = { done: true, value: undefined as undefined }; // allowed because `value` can be `undefined`
132+
d = { done: true, value: undefined as void }; // not allowed because `value` can be `undefined`, and `void` is a supertype of `undefined
133+
~~~~~
134+
!!! error TS2322: Type 'void' is not assignable to type 'number | undefined'.
135+
!!! related TS6500 tests/cases/conformance/types/members/typesWithVoidProperty.ts:8:5: The expected type comes from property 'value' which is declared here on type 'Y<number>'
136+

0 commit comments

Comments
 (0)