From e6259a557075beb808b264ff842e5b6c30d3fc9a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 4 Dec 2015 16:04:47 -0800 Subject: [PATCH 1/3] Fix union/union and intersection/intersection type inference --- src/compiler/checker.ts | 59 +++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a48160137531c..68ae6b204560f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6087,14 +6087,23 @@ namespace ts { function inferFromTypes(source: Type, target: Type) { if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union || source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) { - // Source and target are both unions or both intersections. To improve the quality of - // inferences we first reduce the types by removing constituents that are identically - // matched by a constituent in the other type. For example, when inferring from - // 'string | string[]' to 'string | T', we reduce the types to 'string[]' and 'T'. - const reducedSource = reduceUnionOrIntersectionType(source, target); - const reducedTarget = reduceUnionOrIntersectionType(target, source); - source = reducedSource; - target = reducedTarget; + // Source and target are both unions or both intersections. First, find each + // target constituent type that has an identically matching source constituent + // type, and for each such target constituent type, infer from the type to itself. + let matchingTypes: Type[]; + for (const t of (target).types) { + if (typeIdenticalToSomeType(t, (source).types)) { + (matchingTypes || (matchingTypes = [])).push(t); + inferFromTypes(t, t); + } + } + // To improve the quality of inferences, reduce the source and target types by + // removing the identically matched constituents. For example, when inferring from + // 'string | string[]' to 'string | T' we reduce the types to 'string[]' and 'T'. + if (matchingTypes) { + source = removeTypesFromUnionOrIntersection(source, matchingTypes); + target = removeTypesFromUnionOrIntersection(target, matchingTypes); + } } if (target.flags & TypeFlags.TypeParameter) { // If target is a type parameter, make an inference, unless the source type contains @@ -6256,9 +6265,9 @@ namespace ts { } } - function typeIdenticalToSomeType(source: Type, target: UnionOrIntersectionType): boolean { - for (const t of target.types) { - if (isTypeIdenticalTo(source, t)) { + function typeIdenticalToSomeType(type: Type, types: Type[]): boolean { + for (const t of types) { + if (isTypeIdenticalTo(t, type)) { return true; } } @@ -6266,29 +6275,17 @@ namespace ts { } /** - * Return the reduced form of the source type. This type is computed by by removing all source - * constituents that have an identical match in the target type. + * Return a new union or intersection type computed by removing a given set of types + * from a given union or intersection type. */ - function reduceUnionOrIntersectionType(source: UnionOrIntersectionType, target: UnionOrIntersectionType) { - let sourceTypes = source.types; - let sourceIndex = 0; - let modified = false; - while (sourceIndex < sourceTypes.length) { - if (typeIdenticalToSomeType(sourceTypes[sourceIndex], target)) { - if (!modified) { - sourceTypes = sourceTypes.slice(0); - modified = true; - } - sourceTypes.splice(sourceIndex, 1); - } - else { - sourceIndex++; + function removeTypesFromUnionOrIntersection(type: UnionOrIntersectionType, typesToRemove: Type[]) { + const reducedTypes: Type[] = []; + for (const t of type.types) { + if (!typeIdenticalToSomeType(t, typesToRemove)) { + reducedTypes.push(t); } } - if (modified) { - return source.flags & TypeFlags.Union ? getUnionType(sourceTypes, /*noSubtypeReduction*/ true) : getIntersectionType(sourceTypes); - } - return source; + return type.flags & TypeFlags.Union ? getUnionType(reducedTypes, /*noSubtypeReduction*/ true) : getIntersectionType(reducedTypes); } function getInferenceCandidates(context: InferenceContext, index: number): Type[] { From ae9d93b41a9c9b3a4ccb874b7c1766d3ff18c074 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 4 Dec 2015 16:05:01 -0800 Subject: [PATCH 2/3] Adding test --- .../reference/recursiveUnionTypeInference.js | 14 +++++++++++ .../recursiveUnionTypeInference.symbols | 23 ++++++++++++++++++ .../recursiveUnionTypeInference.types | 24 +++++++++++++++++++ .../compiler/recursiveUnionTypeInference.ts | 7 ++++++ 4 files changed, 68 insertions(+) create mode 100644 tests/baselines/reference/recursiveUnionTypeInference.js create mode 100644 tests/baselines/reference/recursiveUnionTypeInference.symbols create mode 100644 tests/baselines/reference/recursiveUnionTypeInference.types create mode 100644 tests/cases/compiler/recursiveUnionTypeInference.ts diff --git a/tests/baselines/reference/recursiveUnionTypeInference.js b/tests/baselines/reference/recursiveUnionTypeInference.js new file mode 100644 index 0000000000000..580a6bf83e5d0 --- /dev/null +++ b/tests/baselines/reference/recursiveUnionTypeInference.js @@ -0,0 +1,14 @@ +//// [recursiveUnionTypeInference.ts] +interface Foo { + x: T; +} + +function bar(x: Foo | string): T { + return bar(x); +} + + +//// [recursiveUnionTypeInference.js] +function bar(x) { + return bar(x); +} diff --git a/tests/baselines/reference/recursiveUnionTypeInference.symbols b/tests/baselines/reference/recursiveUnionTypeInference.symbols new file mode 100644 index 0000000000000..8823357394178 --- /dev/null +++ b/tests/baselines/reference/recursiveUnionTypeInference.symbols @@ -0,0 +1,23 @@ +=== tests/cases/compiler/recursiveUnionTypeInference.ts === +interface Foo { +>Foo : Symbol(Foo, Decl(recursiveUnionTypeInference.ts, 0, 0)) +>T : Symbol(T, Decl(recursiveUnionTypeInference.ts, 0, 14)) + + x: T; +>x : Symbol(x, Decl(recursiveUnionTypeInference.ts, 0, 18)) +>T : Symbol(T, Decl(recursiveUnionTypeInference.ts, 0, 14)) +} + +function bar(x: Foo | string): T { +>bar : Symbol(bar, Decl(recursiveUnionTypeInference.ts, 2, 1)) +>T : Symbol(T, Decl(recursiveUnionTypeInference.ts, 4, 13)) +>x : Symbol(x, Decl(recursiveUnionTypeInference.ts, 4, 16)) +>Foo : Symbol(Foo, Decl(recursiveUnionTypeInference.ts, 0, 0)) +>T : Symbol(T, Decl(recursiveUnionTypeInference.ts, 4, 13)) +>T : Symbol(T, Decl(recursiveUnionTypeInference.ts, 4, 13)) + + return bar(x); +>bar : Symbol(bar, Decl(recursiveUnionTypeInference.ts, 2, 1)) +>x : Symbol(x, Decl(recursiveUnionTypeInference.ts, 4, 16)) +} + diff --git a/tests/baselines/reference/recursiveUnionTypeInference.types b/tests/baselines/reference/recursiveUnionTypeInference.types new file mode 100644 index 0000000000000..e5d62a051a539 --- /dev/null +++ b/tests/baselines/reference/recursiveUnionTypeInference.types @@ -0,0 +1,24 @@ +=== tests/cases/compiler/recursiveUnionTypeInference.ts === +interface Foo { +>Foo : Foo +>T : T + + x: T; +>x : T +>T : T +} + +function bar(x: Foo | string): T { +>bar : (x: Foo | string) => T +>T : T +>x : Foo | string +>Foo : Foo +>T : T +>T : T + + return bar(x); +>bar(x) : T +>bar : (x: Foo | string) => T +>x : Foo | string +} + diff --git a/tests/cases/compiler/recursiveUnionTypeInference.ts b/tests/cases/compiler/recursiveUnionTypeInference.ts new file mode 100644 index 0000000000000..4ffc6fcf4a78f --- /dev/null +++ b/tests/cases/compiler/recursiveUnionTypeInference.ts @@ -0,0 +1,7 @@ +interface Foo { + x: T; +} + +function bar(x: Foo | string): T { + return bar(x); +} From 6901a98c85e309803c0faaa0055b460f99f89d10 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Dec 2015 08:53:21 -0800 Subject: [PATCH 3/3] Adding a bit more text to comments --- src/compiler/checker.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 68ae6b204560f..d2eeadea5279c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6089,7 +6089,9 @@ namespace ts { source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) { // Source and target are both unions or both intersections. First, find each // target constituent type that has an identically matching source constituent - // type, and for each such target constituent type, infer from the type to itself. + // type, and for each such target constituent type infer from the type to itself. + // When inferring from a type to itself we effectively find all type parameter + // occurrences within that type and infer themselves as their type arguments. let matchingTypes: Type[]; for (const t of (target).types) { if (typeIdenticalToSomeType(t, (source).types)) { @@ -6097,7 +6099,7 @@ namespace ts { inferFromTypes(t, t); } } - // To improve the quality of inferences, reduce the source and target types by + // Next, to improve the quality of inferences, reduce the source and target types by // removing the identically matched constituents. For example, when inferring from // 'string | string[]' to 'string | T' we reduce the types to 'string[]' and 'T'. if (matchingTypes) {