Skip to content

Property handle recursive type references in type inference #33678

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

Merged
merged 3 commits into from
Sep 30, 2019
Merged
Show file tree
Hide file tree
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
11 changes: 9 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17021,7 +17021,7 @@ namespace ts {
function couldContainTypeVariables(type: Type): boolean {
const objectFlags = getObjectFlags(type);
return !!(type.flags & TypeFlags.Instantiable ||
objectFlags & ObjectFlags.Reference && forEach(getTypeArguments(<TypeReference>type), couldContainTypeVariables) ||
objectFlags & ObjectFlags.Reference && ((<TypeReference>type).node || forEach(getTypeArguments(<TypeReference>type), couldContainTypeVariables)) ||
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ||
objectFlags & ObjectFlags.Mapped ||
type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && couldUnionOrIntersectionContainTypeVariables(<UnionOrIntersectionType>type));
Expand Down Expand Up @@ -17318,7 +17318,8 @@ namespace ts {
}
}
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target))) {
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target)) &&
!((<TypeReference>source).node && (<TypeReference>target).node)) {
// If source and target are references to the same generic type, infer from type arguments
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target));
}
Expand Down Expand Up @@ -17599,6 +17600,12 @@ namespace ts {
}

function inferFromObjectTypesWorker(source: Type, target: Type) {
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target))) {
// If source and target are references to the same generic type, infer from type arguments
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target));
return;
}
if (isGenericMappedType(source) && isGenericMappedType(target)) {
// The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer
// from S to T and from X to Y.
Expand Down
79 changes: 78 additions & 1 deletion tests/baselines/reference/recursiveTypeReferences1.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@ tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeRefe
tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts(60,7): error TS2322: Type 'number' is not assignable to type 'string | RecArray<string>'.
tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts(66,8): error TS2322: Type 'number' is not assignable to type 'string | string[]'.
tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts(72,8): error TS2322: Type 'number' is not assignable to type 'string | (string | string[])[]'.
tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts(102,10): error TS2304: Cannot find name 'html'.
tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts(104,12): error TS2304: Cannot find name 'html'.
tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts(105,7): error TS2304: Cannot find name 'html'.
tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts(106,52): error TS2304: Cannot find name 'frag'.
tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts(116,11): error TS2304: Cannot find name 'concat'.
tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts(116,24): error TS2304: Cannot find name 'concat'.
tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts(117,11): error TS2304: Cannot find name 'concat'.
tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts(122,11): error TS2304: Cannot find name 'concat'.
tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts(127,3): error TS2304: Cannot find name 'assert'.


==== tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts (4 errors) ====
==== tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeReferences1.ts (13 errors) ====
type ValueOrArray<T> = T | Array<ValueOrArray<T>>;

const a0: ValueOrArray<number> = 1;
Expand Down Expand Up @@ -92,4 +101,72 @@ tests/cases/conformance/types/typeRelationships/recursiveTypes/recursiveTypeRefe
type T13 = T13[] | string;
type T14 = T14[] & { x: string };
type T15<X> = X extends string ? T15<X>[] : never;

type ValueOrArray1<T> = T | ValueOrArray1<T>[];
type ValueOrArray2<T> = T | ValueOrArray2<T>[];

declare function foo1<T>(a: ValueOrArray1<T>): T;
declare let ra1: ValueOrArray2<string>;

let x1 = foo1(ra1); // Boom!

type NumberOrArray1<T> = T | ValueOrArray1<T>[];
type NumberOrArray2<T> = T | ValueOrArray2<T>[];

declare function foo2<T>(a: ValueOrArray1<T>): T;
declare let ra2: ValueOrArray2<string>;

let x2 = foo2(ra2); // Boom!

// Repro from #33617 (errors are expected)

type Tree = [HTMLHeadingElement, Tree][];

function parse(node: Tree, index: number[] = []): HTMLUListElement {
return html('ul', node.map(([el, children], i) => {
~~~~
!!! error TS2304: Cannot find name 'html'.
const idx = [...index, i + 1];
return html('li', [
~~~~
!!! error TS2304: Cannot find name 'html'.
html('a', { href: `#${el.id}`, rel: 'noopener', 'data-index': idx.join('.') }, el.textContent!),
~~~~
!!! error TS2304: Cannot find name 'html'.
children.length > 0 ? parse(children, idx) : frag()
~~~~
!!! error TS2304: Cannot find name 'frag'.
]);
}));
}

function cons(hs: HTMLHeadingElement[]): Tree {
return hs
.reduce<HTMLHeadingElement[][]>((hss, h) => {
const hs = hss.pop()!;
return hs.length === 0 || level(h) > level(hs[0])
? concat(hss, [concat(hs, [h])])
~~~~~~
!!! error TS2304: Cannot find name 'concat'.
~~~~~~
!!! error TS2304: Cannot find name 'concat'.
: concat(hss, [hs, [h]]);
~~~~~~
!!! error TS2304: Cannot find name 'concat'.
}, [[]])
.reduce<Tree>((node, hs) =>
hs.length === 0
? node
: concat<Tree[number]>(node, [[hs.shift()!, cons(hs)]])
~~~~~~
!!! error TS2304: Cannot find name 'concat'.
, []);
}

function level(h: HTMLHeadingElement): number {
assert(isFinite(+h.tagName[1]));
~~~~~~
!!! error TS2304: Cannot find name 'assert'.
return +h.tagName[1];
}

102 changes: 102 additions & 0 deletions tests/baselines/reference/recursiveTypeReferences1.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,67 @@ type T12 = (T12)[];
type T13 = T13[] | string;
type T14 = T14[] & { x: string };
type T15<X> = X extends string ? T15<X>[] : never;

type ValueOrArray1<T> = T | ValueOrArray1<T>[];
type ValueOrArray2<T> = T | ValueOrArray2<T>[];

declare function foo1<T>(a: ValueOrArray1<T>): T;
declare let ra1: ValueOrArray2<string>;

let x1 = foo1(ra1); // Boom!

type NumberOrArray1<T> = T | ValueOrArray1<T>[];
type NumberOrArray2<T> = T | ValueOrArray2<T>[];

declare function foo2<T>(a: ValueOrArray1<T>): T;
declare let ra2: ValueOrArray2<string>;

let x2 = foo2(ra2); // Boom!

// Repro from #33617 (errors are expected)

type Tree = [HTMLHeadingElement, Tree][];

function parse(node: Tree, index: number[] = []): HTMLUListElement {
return html('ul', node.map(([el, children], i) => {
const idx = [...index, i + 1];
return html('li', [
html('a', { href: `#${el.id}`, rel: 'noopener', 'data-index': idx.join('.') }, el.textContent!),
children.length > 0 ? parse(children, idx) : frag()
]);
}));
}

function cons(hs: HTMLHeadingElement[]): Tree {
return hs
.reduce<HTMLHeadingElement[][]>((hss, h) => {
const hs = hss.pop()!;
return hs.length === 0 || level(h) > level(hs[0])
? concat(hss, [concat(hs, [h])])
: concat(hss, [hs, [h]]);
}, [[]])
.reduce<Tree>((node, hs) =>
hs.length === 0
? node
: concat<Tree[number]>(node, [[hs.shift()!, cons(hs)]])
, []);
}

function level(h: HTMLHeadingElement): number {
assert(isFinite(+h.tagName[1]));
return +h.tagName[1];
}


//// [recursiveTypeReferences1.js]
"use strict";
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
var a0 = 1;
var a1 = [1, [2, 3], [4, [5, [6, 7]]]];
var hypertextNode = ["div", { id: "parent" },
Expand Down Expand Up @@ -124,6 +181,37 @@ flat2([[[0]]]); // number[]
flat2([1, 'a', [2]]); // (string | number)[]
flat2([1, [2, 'a']]); // (string | number)[]
flat2([1, ['a']]); // Error
var x1 = foo1(ra1); // Boom!
var x2 = foo2(ra2); // Boom!
function parse(node, index) {
if (index === void 0) { index = []; }
return html('ul', node.map(function (_a, i) {
var el = _a[0], children = _a[1];
var idx = __spreadArrays(index, [i + 1]);
return html('li', [
html('a', { href: "#" + el.id, rel: 'noopener', 'data-index': idx.join('.') }, el.textContent),
children.length > 0 ? parse(children, idx) : frag()
]);
}));
}
function cons(hs) {
return hs
.reduce(function (hss, h) {
var hs = hss.pop();
return hs.length === 0 || level(h) > level(hs[0])
? concat(hss, [concat(hs, [h])])
: concat(hss, [hs, [h]]);
}, [[]])
.reduce(function (node, hs) {
return hs.length === 0
? node
: concat(node, [[hs.shift(), cons(hs)]]);
}, []);
}
function level(h) {
assert(isFinite(+h.tagName[1]));
return +h.tagName[1];
}


//// [recursiveTypeReferences1.d.ts]
Expand Down Expand Up @@ -165,3 +253,17 @@ declare type T14 = T14[] & {
x: string;
};
declare type T15<X> = X extends string ? T15<X>[] : never;
declare type ValueOrArray1<T> = T | ValueOrArray1<T>[];
declare type ValueOrArray2<T> = T | ValueOrArray2<T>[];
declare function foo1<T>(a: ValueOrArray1<T>): T;
declare let ra1: ValueOrArray2<string>;
declare let x1: string;
declare type NumberOrArray1<T> = T | ValueOrArray1<T>[];
declare type NumberOrArray2<T> = T | ValueOrArray2<T>[];
declare function foo2<T>(a: ValueOrArray1<T>): T;
declare let ra2: ValueOrArray2<string>;
declare let x2: string;
declare type Tree = [HTMLHeadingElement, Tree][];
declare function parse(node: Tree, index?: number[]): HTMLUListElement;
declare function cons(hs: HTMLHeadingElement[]): Tree;
declare function level(h: HTMLHeadingElement): number;
Loading