From 411e7144a9d1ea35bbb4ac2632c37b711de1ecc1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Apr 2019 16:53:38 -1000 Subject: [PATCH 1/4] Add new nonInferrableType with ObjectFlags.NonInferrableType --- src/compiler/checker.ts | 32 ++++++++++++++------------------ src/compiler/types.ts | 6 +++--- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 79480be336d04..a1e21e010fb9a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -404,10 +404,10 @@ namespace ts { const wildcardType = createIntrinsicType(TypeFlags.Any, "any"); const errorType = createIntrinsicType(TypeFlags.Any, "error"); const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); - const undefinedType = createNullableType(TypeFlags.Undefined, "undefined", 0); - const undefinedWideningType = strictNullChecks ? undefinedType : createNullableType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); - const nullType = createNullableType(TypeFlags.Null, "null", 0); - const nullWideningType = strictNullChecks ? nullType : createNullableType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); + const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); + const nullType = createIntrinsicType(TypeFlags.Null, "null"); + const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); const stringType = createIntrinsicType(TypeFlags.String, "string"); const numberType = createIntrinsicType(TypeFlags.Number, "number"); const bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); @@ -433,6 +433,7 @@ namespace ts { const voidType = createIntrinsicType(TypeFlags.Void, "void"); const neverType = createIntrinsicType(TypeFlags.Never, "never"); const silentNeverType = createIntrinsicType(TypeFlags.Never, "never"); + const nonInferrableType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType); const implicitNeverType = createIntrinsicType(TypeFlags.Never, "never"); const nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); @@ -453,7 +454,7 @@ namespace ts { const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated // in getPropagatingFlagsOfTypes, and it is checked in inferFromTypes. - anyFunctionType.objectFlags |= ObjectFlags.ContainsAnyFunctionType; + anyFunctionType.objectFlags |= ObjectFlags.NonInferrableType; const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); @@ -2797,14 +2798,9 @@ namespace ts { return result; } - function createIntrinsicType(kind: TypeFlags, intrinsicName: string): IntrinsicType { + function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags: ObjectFlags = 0): IntrinsicType { const type = createType(kind); type.intrinsicName = intrinsicName; - return type; - } - - function createNullableType(kind: TypeFlags, intrinsicName: string, objectFlags: ObjectFlags): NullableType { - const type = createIntrinsicType(kind, intrinsicName); type.objectFlags = objectFlags; return type; } @@ -14513,7 +14509,7 @@ namespace ts { // If any property contains context sensitive functions that have been skipped, the source type // is incomplete and we can't infer a meaningful input type. for (const prop of properties) { - if (getObjectFlags(getTypeOfSymbol(prop)) & ObjectFlags.ContainsAnyFunctionType) { + if (getObjectFlags(getTypeOfSymbol(prop)) & ObjectFlags.NonInferrableType) { return undefined; } } @@ -14670,7 +14666,7 @@ namespace ts { // not contain anyFunctionType when we come back to this argument for its second round // of inference. Also, we exclude inferences for silentNeverType (which is used as a wildcard // when constructing types from type parameters that had no inference candidates). - if (getObjectFlags(source) & ObjectFlags.ContainsAnyFunctionType || source === silentNeverType || (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType))) { + if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === silentNeverType || (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType))) { return; } const inference = getInferenceInfoForType(target); @@ -14991,7 +14987,7 @@ namespace ts { const sourceLen = sourceSignatures.length; const targetLen = targetSignatures.length; const len = sourceLen < targetLen ? sourceLen : targetLen; - const skipParameters = !!(getObjectFlags(source) & ObjectFlags.ContainsAnyFunctionType); + const skipParameters = !!(getObjectFlags(source) & ObjectFlags.NonInferrableType); for (let i = 0; i < len; i++) { inferFromSignature(getBaseSignature(sourceSignatures[sourceLen - len + i]), getBaseSignature(targetSignatures[targetLen - len + i]), skipParameters); } @@ -21120,7 +21116,7 @@ namespace ts { // returns a function type, we choose to defer processing. This narrowly permits function composition // operators to flow inferences through return types, but otherwise processes calls right away. We // use the resolvingSignature singleton to indicate that we deferred processing. This result will be - // propagated out and eventually turned into silentNeverType (a type that is assignable to anything and + // propagated out and eventually turned into nonInferrableType (a type that is assignable to anything and // from which we never make inferences). if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) { skippedGenericFunction(node, checkMode); @@ -21603,8 +21599,8 @@ namespace ts { const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode); if (signature === resolvingSignature) { // CheckMode.SkipGenericFunctions is enabled and this is a call to a generic function that - // returns a function type. We defer checking and return anyFunctionType. - return silentNeverType; + // returns a function type. We defer checking and return nonInferrableType. + return nonInferrableType; } if (node.expression.kind === SyntaxKind.SuperKeyword) { @@ -22411,7 +22407,7 @@ namespace ts { const returnType = getReturnTypeFromBody(node, checkMode); const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, undefined, undefined); - returnOnlyType.objectFlags |= ObjectFlags.ContainsAnyFunctionType; + returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; return links.contextFreeType = returnOnlyType; } return anyFunctionType; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 55492081050a8..97e6c3dcf56a5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3944,7 +3944,7 @@ namespace ts { Instantiable = InstantiableNonPrimitive | InstantiablePrimitive, StructuredOrInstantiable = StructuredType | Instantiable, /* @internal */ - ObjectFlagsType = Nullable | Object | Union | Intersection, + ObjectFlagsType = Nullable | Never | Object | Union | Intersection, // 'Narrowable' types are types where narrowing actually narrows. // This *should* be every type other than null, undefined, void, and never Narrowable = Any | Unknown | StructuredOrInstantiable | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive, @@ -4064,12 +4064,12 @@ namespace ts { /* @internal */ ContainsObjectLiteral = 1 << 18, // Type is or contains object literal type /* @internal */ - ContainsAnyFunctionType = 1 << 19, // Type is or contains the anyFunctionType + NonInferrableType = 1 << 19, // Type is or contains anyFunctionType or silentNeverType ClassOrInterface = Class | Interface, /* @internal */ RequiresWidening = ContainsWideningType | ContainsObjectLiteral, /* @internal */ - PropagatingFlags = ContainsWideningType | ContainsObjectLiteral | ContainsAnyFunctionType + PropagatingFlags = ContainsWideningType | ContainsObjectLiteral | NonInferrableType } /* @internal */ From 4b813e310c3e5e7b758b85eada14ba45b898233e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Apr 2019 17:05:07 -1000 Subject: [PATCH 2/4] Simplify non-inferrable property check to rely on propagation --- src/compiler/checker.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a1e21e010fb9a..e3fda9e6e42a8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14502,16 +14502,10 @@ namespace ts { } function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) { - const properties = getPropertiesOfType(source); - if (properties.length === 0 && !getIndexInfoOfType(source, IndexKind.String)) { - return undefined; - } // If any property contains context sensitive functions that have been skipped, the source type // is incomplete and we can't infer a meaningful input type. - for (const prop of properties) { - if (getObjectFlags(getTypeOfSymbol(prop)) & ObjectFlags.NonInferrableType) { - return undefined; - } + if (getObjectFlags(source) & ObjectFlags.NonInferrableType || getPropertiesOfType(source).length === 0 && !getIndexInfoOfType(source, IndexKind.String)) { + return undefined; } // For arrays and tuples we infer new arrays and tuples where the reverse mapping has been // applied to the element type(s). From 8cd5d61644ff31139a041aa3289608b7f0540d77 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Apr 2019 17:13:04 -1000 Subject: [PATCH 3/4] Add regression test --- tests/cases/compiler/genericFunctionInference2.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/cases/compiler/genericFunctionInference2.ts diff --git a/tests/cases/compiler/genericFunctionInference2.ts b/tests/cases/compiler/genericFunctionInference2.ts new file mode 100644 index 0000000000000..56caca847c23d --- /dev/null +++ b/tests/cases/compiler/genericFunctionInference2.ts @@ -0,0 +1,15 @@ +// Repro from #30685 + +type Reducer = (state: S) => S; +declare function combineReducers(reducers: { [K in keyof S]: Reducer }): Reducer; + +type MyState = { combined: { foo: number } }; +declare const foo: Reducer; + +const myReducer1: Reducer = combineReducers({ + combined: combineReducers({ foo }), +}); + +const myReducer2 = combineReducers({ + combined: combineReducers({ foo }), +}); From 791f56d22f080ee1dcc90b3b2e84c1af5cb2fd1c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Apr 2019 17:13:11 -1000 Subject: [PATCH 4/4] Accept new baselines --- .../reference/genericFunctionInference2.js | 26 +++++++++ .../genericFunctionInference2.symbols | 56 +++++++++++++++++++ .../reference/genericFunctionInference2.types | 49 ++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 tests/baselines/reference/genericFunctionInference2.js create mode 100644 tests/baselines/reference/genericFunctionInference2.symbols create mode 100644 tests/baselines/reference/genericFunctionInference2.types diff --git a/tests/baselines/reference/genericFunctionInference2.js b/tests/baselines/reference/genericFunctionInference2.js new file mode 100644 index 0000000000000..5a95abae8cb83 --- /dev/null +++ b/tests/baselines/reference/genericFunctionInference2.js @@ -0,0 +1,26 @@ +//// [genericFunctionInference2.ts] +// Repro from #30685 + +type Reducer = (state: S) => S; +declare function combineReducers(reducers: { [K in keyof S]: Reducer }): Reducer; + +type MyState = { combined: { foo: number } }; +declare const foo: Reducer; + +const myReducer1: Reducer = combineReducers({ + combined: combineReducers({ foo }), +}); + +const myReducer2 = combineReducers({ + combined: combineReducers({ foo }), +}); + + +//// [genericFunctionInference2.js] +// Repro from #30685 +var myReducer1 = combineReducers({ + combined: combineReducers({ foo: foo }) +}); +var myReducer2 = combineReducers({ + combined: combineReducers({ foo: foo }) +}); diff --git a/tests/baselines/reference/genericFunctionInference2.symbols b/tests/baselines/reference/genericFunctionInference2.symbols new file mode 100644 index 0000000000000..d77f1182b926a --- /dev/null +++ b/tests/baselines/reference/genericFunctionInference2.symbols @@ -0,0 +1,56 @@ +=== tests/cases/compiler/genericFunctionInference2.ts === +// Repro from #30685 + +type Reducer = (state: S) => S; +>Reducer : Symbol(Reducer, Decl(genericFunctionInference2.ts, 0, 0)) +>S : Symbol(S, Decl(genericFunctionInference2.ts, 2, 13)) +>state : Symbol(state, Decl(genericFunctionInference2.ts, 2, 19)) +>S : Symbol(S, Decl(genericFunctionInference2.ts, 2, 13)) +>S : Symbol(S, Decl(genericFunctionInference2.ts, 2, 13)) + +declare function combineReducers(reducers: { [K in keyof S]: Reducer }): Reducer; +>combineReducers : Symbol(combineReducers, Decl(genericFunctionInference2.ts, 2, 34)) +>S : Symbol(S, Decl(genericFunctionInference2.ts, 3, 33)) +>reducers : Symbol(reducers, Decl(genericFunctionInference2.ts, 3, 36)) +>K : Symbol(K, Decl(genericFunctionInference2.ts, 3, 49)) +>S : Symbol(S, Decl(genericFunctionInference2.ts, 3, 33)) +>Reducer : Symbol(Reducer, Decl(genericFunctionInference2.ts, 0, 0)) +>S : Symbol(S, Decl(genericFunctionInference2.ts, 3, 33)) +>K : Symbol(K, Decl(genericFunctionInference2.ts, 3, 49)) +>Reducer : Symbol(Reducer, Decl(genericFunctionInference2.ts, 0, 0)) +>S : Symbol(S, Decl(genericFunctionInference2.ts, 3, 33)) + +type MyState = { combined: { foo: number } }; +>MyState : Symbol(MyState, Decl(genericFunctionInference2.ts, 3, 93)) +>combined : Symbol(combined, Decl(genericFunctionInference2.ts, 5, 16)) +>foo : Symbol(foo, Decl(genericFunctionInference2.ts, 5, 28)) + +declare const foo: Reducer; +>foo : Symbol(foo, Decl(genericFunctionInference2.ts, 6, 13)) +>Reducer : Symbol(Reducer, Decl(genericFunctionInference2.ts, 0, 0)) +>MyState : Symbol(MyState, Decl(genericFunctionInference2.ts, 3, 93)) + +const myReducer1: Reducer = combineReducers({ +>myReducer1 : Symbol(myReducer1, Decl(genericFunctionInference2.ts, 8, 5)) +>Reducer : Symbol(Reducer, Decl(genericFunctionInference2.ts, 0, 0)) +>MyState : Symbol(MyState, Decl(genericFunctionInference2.ts, 3, 93)) +>combineReducers : Symbol(combineReducers, Decl(genericFunctionInference2.ts, 2, 34)) + + combined: combineReducers({ foo }), +>combined : Symbol(combined, Decl(genericFunctionInference2.ts, 8, 54)) +>combineReducers : Symbol(combineReducers, Decl(genericFunctionInference2.ts, 2, 34)) +>foo : Symbol(foo, Decl(genericFunctionInference2.ts, 9, 31)) + +}); + +const myReducer2 = combineReducers({ +>myReducer2 : Symbol(myReducer2, Decl(genericFunctionInference2.ts, 12, 5)) +>combineReducers : Symbol(combineReducers, Decl(genericFunctionInference2.ts, 2, 34)) + + combined: combineReducers({ foo }), +>combined : Symbol(combined, Decl(genericFunctionInference2.ts, 12, 36)) +>combineReducers : Symbol(combineReducers, Decl(genericFunctionInference2.ts, 2, 34)) +>foo : Symbol(foo, Decl(genericFunctionInference2.ts, 13, 31)) + +}); + diff --git a/tests/baselines/reference/genericFunctionInference2.types b/tests/baselines/reference/genericFunctionInference2.types new file mode 100644 index 0000000000000..48b8d084fd629 --- /dev/null +++ b/tests/baselines/reference/genericFunctionInference2.types @@ -0,0 +1,49 @@ +=== tests/cases/compiler/genericFunctionInference2.ts === +// Repro from #30685 + +type Reducer = (state: S) => S; +>Reducer : Reducer +>state : S + +declare function combineReducers(reducers: { [K in keyof S]: Reducer }): Reducer; +>combineReducers : (reducers: { [K in keyof S]: Reducer; }) => Reducer +>reducers : { [K in keyof S]: Reducer; } + +type MyState = { combined: { foo: number } }; +>MyState : MyState +>combined : { foo: number; } +>foo : number + +declare const foo: Reducer; +>foo : Reducer + +const myReducer1: Reducer = combineReducers({ +>myReducer1 : Reducer +>combineReducers({ combined: combineReducers({ foo }),}) : Reducer<{ combined: { foo: any; }; }> +>combineReducers : (reducers: { [K in keyof S]: Reducer; }) => Reducer +>{ combined: combineReducers({ foo }),} : { combined: Reducer<{ foo: number; }>; } + + combined: combineReducers({ foo }), +>combined : Reducer<{ foo: number; }> +>combineReducers({ foo }) : Reducer<{ foo: number; }> +>combineReducers : (reducers: { [K in keyof S]: Reducer; }) => Reducer +>{ foo } : { foo: Reducer; } +>foo : Reducer + +}); + +const myReducer2 = combineReducers({ +>myReducer2 : Reducer<{ combined: { foo: any; }; }> +>combineReducers({ combined: combineReducers({ foo }),}) : Reducer<{ combined: { foo: any; }; }> +>combineReducers : (reducers: { [K in keyof S]: Reducer; }) => Reducer +>{ combined: combineReducers({ foo }),} : { combined: Reducer<{ foo: number; }>; } + + combined: combineReducers({ foo }), +>combined : Reducer<{ foo: number; }> +>combineReducers({ foo }) : Reducer<{ foo: number; }> +>combineReducers : (reducers: { [K in keyof S]: Reducer; }) => Reducer +>{ foo } : { foo: Reducer; } +>foo : Reducer + +}); +