Skip to content

Treat void-typed properties as optional #40823

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
@@ -1352,6 +1352,18 @@ namespace ts {
}
}

/**
* Attempts to return the `SymbolLinks` for `symbol`, if one already exists.
* Does not allocate a symbol id or a `SymbolLinks` if it doesn't already exists.
*/
function tryGetSymbolLinks(symbol: Symbol): SymbolLinks | undefined {
if (symbol.flags & SymbolFlags.Transient) return <TransientSymbol>symbol;
return symbol.id ? symbolLinks[symbol.id] : undefined;
}

/**
* Gets or creates a `SymbolLinks` for `symbol`.
*/
function getSymbolLinks(symbol: Symbol): SymbolLinks {
if (symbol.flags & SymbolFlags.Transient) return <TransientSymbol>symbol;
const id = getSymbolId(symbol);
@@ -7666,11 +7678,11 @@ namespace ts {
function hasType(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean {
switch (propertyName) {
case TypeSystemPropertyName.Type:
return !!getSymbolLinks(<Symbol>target).type;
return !!tryGetSymbolLinks(<Symbol>target)?.type;
case TypeSystemPropertyName.EnumTagType:
return !!(getNodeLinks(target as JSDocEnumTag).resolvedEnumType);
case TypeSystemPropertyName.DeclaredType:
return !!getSymbolLinks(<Symbol>target).declaredType;
return !!tryGetSymbolLinks(<Symbol>target)?.declaredType;
case TypeSystemPropertyName.ResolvedBaseConstructorType:
return !!(<InterfaceType>target).resolvedBaseConstructorType;
case TypeSystemPropertyName.ResolvedReturnType:
@@ -8509,6 +8521,14 @@ namespace ts {
return links.type;
}

function isOptionalProperty(prop: Symbol) {
if (prop.flags & SymbolFlags.Optional) return true;
// We don't use `getTypeOfSymbol` here as we may be in the middle of resolving the type for `prop` and
// we shouldn't re-enter to check for `void`.
const type = tryGetSymbolLinks(prop)?.type;
return !!(type && forEachType(type, acceptsVoid));
}

function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol) {
// Handle prototype property
if (symbol.flags & SymbolFlags.Prototype) {
@@ -17894,7 +17914,7 @@ namespace ts {
return Ternary.False;
}
// When checking for comparability, be more lenient with optional properties.
if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) {
if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && !isOptionalProperty(targetProp)) {
// TypeScript 1.0 spec (April 2014): 3.8.3
// S is a subtype of a type T, and T is a supertype of S if ...
// S' and T are object types and, for each member M in T..
@@ -19709,7 +19729,7 @@ namespace ts {
if (isStaticPrivateIdentifierProperty(targetProp)) {
continue;
}
if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial)) {
if (requireOptionalProperties || !((getCheckFlags(targetProp) & CheckFlags.Partial) || isOptionalProperty(targetProp))) {
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
if (!sourceProp) {
yield targetProp;
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ tests/cases/conformance/jsx/inline/index.tsx(24,48): error TS2322: Type 'import(
[e: string]: {};
}
interface Element {
__domBrand: void;
__domBrand: never;
props: {
children?: Element[];
};
@@ -42,7 +42,7 @@ tests/cases/conformance/jsx/inline/index.tsx(24,48): error TS2322: Type 'import(
[e: string]: {};
}
interface Element {
__predomBrand: void;
__predomBrand: never;
props: {
children?: Element[];
};
@@ -66,7 +66,7 @@ tests/cases/conformance/jsx/inline/index.tsx(24,48): error TS2322: Type 'import(
!!! error TS2532: Object is possibly 'undefined'.

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

class DOMClass implements dom.JSX.Element {
__domBrand!: void;
__domBrand!: never;
constructor(public props: {x: number, y: number, children?: dom.JSX.Element[]}) {}
render() {
return <p>{this.props.x} + {this.props.y} = {this.props.x + this.props.y}{...this.props.children}</p>;
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ export namespace dom {
[e: string]: {};
}
interface Element {
__domBrand: void;
__domBrand: never;
props: {
children?: Element[];
};
@@ -27,7 +27,7 @@ export namespace predom {
[e: string]: {};
}
interface Element {
__predomBrand: void;
__predomBrand: never;
props: {
children?: Element[];
};
@@ -47,7 +47,7 @@ import { predom } from "./renderer2"
export const MySFC = (props: {x: number, y: number, children?: predom.JSX.Element[]}) => <p>{props.x} + {props.y} = {props.x + props.y}{...this.props.children}</p>;

export class MyClass implements predom.JSX.Element {
__predomBrand!: void;
__predomBrand!: never;
constructor(public props: {x: number, y: number, children?: predom.JSX.Element[]}) {}
render() {
return <p>
@@ -70,7 +70,7 @@ elem = <h></h>; // Expect assignability error here
const DOMSFC = (props: {x: number, y: number, children?: dom.JSX.Element[]}) => <p>{props.x} + {props.y} = {props.x + props.y}{props.children}</p>;

class DOMClass implements dom.JSX.Element {
__domBrand!: void;
__domBrand!: never;
constructor(public props: {x: number, y: number, children?: dom.JSX.Element[]}) {}
render() {
return <p>{this.props.x} + {this.props.y} = {this.props.x + this.props.y}{...this.props.children}</p>;
Original file line number Diff line number Diff line change
@@ -14,11 +14,11 @@ export namespace dom {
interface Element {
>Element : Symbol(Element, Decl(renderer.d.ts, 4, 9))

__domBrand: void;
__domBrand: never;
>__domBrand : Symbol(Element.__domBrand, Decl(renderer.d.ts, 5, 27))

props: {
>props : Symbol(Element.props, Decl(renderer.d.ts, 6, 29))
>props : Symbol(Element.props, Decl(renderer.d.ts, 6, 30))

children?: Element[];
>children : Symbol(children, Decl(renderer.d.ts, 7, 20))
@@ -65,11 +65,11 @@ export namespace predom {
interface Element {
>Element : Symbol(Element, Decl(renderer2.d.ts, 4, 9))

__predomBrand: void;
__predomBrand: never;
>__predomBrand : Symbol(Element.__predomBrand, Decl(renderer2.d.ts, 5, 27))

props: {
>props : Symbol(Element.props, Decl(renderer2.d.ts, 6, 32))
>props : Symbol(Element.props, Decl(renderer2.d.ts, 6, 33))

children?: Element[];
>children : Symbol(children, Decl(renderer2.d.ts, 7, 20))
@@ -137,7 +137,7 @@ export class MyClass implements predom.JSX.Element {
>JSX : Symbol(predom.JSX, Decl(renderer2.d.ts, 0, 25))
>Element : Symbol(predom.JSX.Element, Decl(renderer2.d.ts, 4, 9))

__predomBrand!: void;
__predomBrand!: never;
>__predomBrand : Symbol(MyClass.__predomBrand, Decl(component.tsx, 5, 52))

constructor(public props: {x: number, y: number, children?: predom.JSX.Element[]}) {}
@@ -260,7 +260,7 @@ class DOMClass implements dom.JSX.Element {
>JSX : Symbol(dom.JSX, Decl(renderer.d.ts, 0, 22))
>Element : Symbol(dom.JSX.Element, Decl(renderer.d.ts, 4, 9))

__domBrand!: void;
__domBrand!: never;
>__domBrand : Symbol(DOMClass.__domBrand, Decl(index.tsx, 8, 43))

constructor(public props: {x: number, y: number, children?: dom.JSX.Element[]}) {}
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@ export namespace dom {
>e : string
}
interface Element {
__domBrand: void;
>__domBrand : void
__domBrand: never;
>__domBrand : never

props: {
>props : { children?: Element[]; }
@@ -41,8 +41,8 @@ export namespace predom {
>e : string
}
interface Element {
__predomBrand: void;
>__predomBrand : void
__predomBrand: never;
>__predomBrand : never

props: {
>props : { children?: Element[]; }
@@ -110,8 +110,8 @@ export class MyClass implements predom.JSX.Element {
>predom : () => predom.JSX.Element
>JSX : any

__predomBrand!: void;
>__predomBrand : void
__predomBrand!: never;
>__predomBrand : never

constructor(public props: {x: number, y: number, children?: predom.JSX.Element[]}) {}
>props : { x: number; y: number; children?: predom.JSX.Element[]; }
@@ -246,8 +246,8 @@ class DOMClass implements dom.JSX.Element {
>dom : () => dom.JSX.Element
>JSX : any

__domBrand!: void;
>__domBrand : void
__domBrand!: never;
>__domBrand : never

constructor(public props: {x: number, y: number, children?: dom.JSX.Element[]}) {}
>props : { x: number; y: number; children?: dom.JSX.Element[]; }
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ tests/cases/conformance/jsx/inline/index.tsx(5,1): error TS2741: Property '__pre
[e: string]: {};
}
interface Element {
__domBrand: void;
__domBrand: never;
children: Element[];
props: {};
}
@@ -24,7 +24,7 @@ tests/cases/conformance/jsx/inline/index.tsx(5,1): error TS2741: Property '__pre
[e: string]: {};
}
interface Element {
__predomBrand: void;
__predomBrand: never;
children: Element[];
props: {};
}
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ declare global {
[e: string]: {};
}
interface Element {
__domBrand: void;
__domBrand: never;
children: Element[];
props: {};
}
@@ -23,7 +23,7 @@ export namespace predom {
[e: string]: {};
}
interface Element {
__predomBrand: void;
__predomBrand: never;
children: Element[];
props: {};
}
Original file line number Diff line number Diff line change
@@ -14,11 +14,11 @@ declare global {
interface Element {
>Element : Symbol(Element, Decl(renderer.d.ts, 4, 9))

__domBrand: void;
__domBrand: never;
>__domBrand : Symbol(Element.__domBrand, Decl(renderer.d.ts, 5, 27))

children: Element[];
>children : Symbol(Element.children, Decl(renderer.d.ts, 6, 29))
>children : Symbol(Element.children, Decl(renderer.d.ts, 6, 30))
>Element : Symbol(Element, Decl(renderer.d.ts, 4, 9))

props: {};
@@ -54,11 +54,11 @@ export namespace predom {
interface Element {
>Element : Symbol(Element, Decl(renderer2.d.ts, 4, 9))

__predomBrand: void;
__predomBrand: never;
>__predomBrand : Symbol(Element.__predomBrand, Decl(renderer2.d.ts, 5, 27))

children: Element[];
>children : Symbol(Element.children, Decl(renderer2.d.ts, 6, 32))
>children : Symbol(Element.children, Decl(renderer2.d.ts, 6, 33))
>Element : Symbol(Element, Decl(renderer2.d.ts, 4, 9))

props: {};
Original file line number Diff line number Diff line change
@@ -8,8 +8,8 @@ declare global {
>e : string
}
interface Element {
__domBrand: void;
>__domBrand : void
__domBrand: never;
>__domBrand : never

children: Element[];
>children : Element[]
@@ -36,8 +36,8 @@ export namespace predom {
>e : string
}
interface Element {
__predomBrand: void;
>__predomBrand : void
__predomBrand: never;
>__predomBrand : never

children: Element[];
>children : Element[]
Original file line number Diff line number Diff line change
@@ -264,9 +264,9 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(155,5): error TS2322: Type '(c
i4 = i2; // Ok
i4 = i3; // Ok

interface Animal { animal: void }
interface Dog extends Animal { dog: void }
interface Cat extends Animal { cat: void }
interface Animal { animal: never }
interface Dog extends Animal { dog: never }
interface Cat extends Animal { cat: never }

interface Comparer1<T> {
compare(a: T, b: T): number;
6 changes: 3 additions & 3 deletions tests/baselines/reference/strictFunctionTypesErrors.js
Original file line number Diff line number Diff line change
@@ -88,9 +88,9 @@ i4 = i1; // Ok
i4 = i2; // Ok
i4 = i3; // Ok

interface Animal { animal: void }
interface Dog extends Animal { dog: void }
interface Cat extends Animal { cat: void }
interface Animal { animal: never }
interface Dog extends Animal { dog: never }
interface Cat extends Animal { cat: never }

interface Comparer1<T> {
compare(a: T, b: T): number;
Loading