From 42b6ef0c96fe80432ed91c9be25d579c604ed9be Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 11 Mar 2019 06:45:26 -0700 Subject: [PATCH 1/6] Rename contextualMapper to inferenceContext (which it always is) --- src/compiler/checker.ts | 71 +++++++++++++++++++---------------------- src/compiler/types.ts | 4 +-- 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1b0904723df..2766fe6da59 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10778,14 +10778,8 @@ namespace ts { return t => typeParameters.indexOf(t) >= index ? emptyObjectType : t; } - function isInferenceContext(mapper: TypeMapper): mapper is InferenceContext { - return !!(mapper).typeParameters; - } - - function cloneTypeMapper(mapper: TypeMapper, extraFlags: InferenceFlags = 0): TypeMapper { - return mapper && isInferenceContext(mapper) ? - createInferenceContext(mapper.typeParameters, mapper.signature, mapper.flags | extraFlags, mapper.compareTypes, mapper.inferences) : - mapper; + function cloneInferenceContext(context: InferenceContext, extraFlags: InferenceFlags = 0): InferenceContext { + return createInferenceContext(context.typeParameters, context.signature, context.flags | extraFlags, context.compareTypes, context.inferences); } function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined { @@ -11755,7 +11749,7 @@ namespace ts { if (source.typeParameters && source.typeParameters !== target.typeParameters) { target = getCanonicalSignature(target); - source = instantiateSignatureInContextOf(source, target, /*contextualMapper*/ undefined, compareTypes); + source = instantiateSignatureInContextOf(source, target, /*inferenceContext*/ undefined, compareTypes); } const sourceCount = getParameterCount(source); @@ -17531,7 +17525,7 @@ namespace ts { while (type) { const thisType = getThisTypeFromContextualType(type); if (thisType) { - return instantiateType(thisType, getContextualMapper(containingLiteral)); + return instantiateType(thisType, getInferenceContext(containingLiteral)); } if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { break; @@ -18030,9 +18024,9 @@ namespace ts { // return type inferences is available, instantiate those types using that mapper. function instantiateContextualType(contextualType: Type | undefined, node: Expression): Type | undefined { if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) { - const returnMapper = (getContextualMapper(node)).returnMapper; - if (returnMapper) { - return instantiateInstantiableTypes(contextualType, returnMapper); + const inferenceContext = getInferenceContext(node); + if (inferenceContext && inferenceContext.returnMapper) { + return instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); } } return contextualType; @@ -18134,9 +18128,9 @@ namespace ts { return undefined; } - function getContextualMapper(node: Node) { - const ancestor = findAncestor(node, n => !!n.contextualMapper); - return ancestor ? ancestor.contextualMapper! : identityMapper; + function getInferenceContext(node: Node) { + const ancestor = findAncestor(node, n => !!n.inferenceContext); + return ancestor && ancestor.inferenceContext!; } function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement) { @@ -20135,19 +20129,19 @@ namespace ts { } // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec) - function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper?: TypeMapper, compareTypes?: TypeComparer): Signature { + function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, inferenceContext?: InferenceContext, compareTypes?: TypeComparer): Signature { const context = createInferenceContext(signature.typeParameters!, signature, InferenceFlags.None, compareTypes); - // We clone the contextualMapper to avoid fixing. For example, when the source signature is (x: T) => T[] and + // We clone the inferenceContext to avoid fixing. For example, when the source signature is (x: T) => T[] and // the contextual signature is (...args: A) => B, we want to infer the element type of A's constraint (say 'any') // for T but leave it possible to later infer '[any]' back to A. const restType = getEffectiveRestType(contextualSignature); - const mapper = contextualMapper && restType && restType.flags & TypeFlags.TypeParameter ? cloneTypeMapper(contextualMapper) : contextualMapper; + const mapper = inferenceContext && restType && restType.flags & TypeFlags.TypeParameter ? cloneInferenceContext(inferenceContext) : inferenceContext; const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature; forEachMatchingParameterType(sourceSignature, signature, (source, target) => { // Type parameters from outer context referenced by source type are fixed by instantiation of the source type inferTypes(context.inferences, source, target); }); - if (!contextualMapper) { + if (!inferenceContext) { inferTypes(context.inferences, getReturnTypeOfSignature(contextualSignature), getReturnTypeOfSignature(signature), InferencePriority.ReturnType); const signaturePredicate = getTypePredicateOfSignature(signature); const contextualPredicate = getTypePredicateOfSignature(sourceSignature); @@ -20192,7 +20186,8 @@ namespace ts { // We clone the contextual mapper to avoid disturbing a resolution in progress for an // outer call expression. Effectively we just want a snapshot of whatever has been // inferred for any outer call expression so far. - const instantiatedType = instantiateType(contextualType, cloneTypeMapper(getContextualMapper(node), InferenceFlags.NoDefault)); + const outerContext = getInferenceContext(node); + const instantiatedType = instantiateType(contextualType, outerContext && cloneInferenceContext(outerContext, InferenceFlags.NoDefault)); // If the contextual type is a generic function type with a single call signature, we // instantiate the type with its own type parameters and type arguments. This ensures that // the type parameters are not erased to type any during type inference such that they can @@ -20326,7 +20321,7 @@ namespace ts { // However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props, // can be specified by users through attributes property. const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); - const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*contextualMapper*/ undefined, checkMode); + const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode); return checkTypeRelatedToAndOptionallyElaborate(attributesType, paramType, relation, reportErrors ? node.tagName : undefined, node.attributes); } @@ -20360,7 +20355,7 @@ namespace ts { const arg = args[i]; if (arg.kind !== SyntaxKind.OmittedExpression) { const paramType = getTypeAtPosition(signature, i); - const argType = checkExpressionWithContextualType(arg, paramType, /*contextualMapper*/ undefined, checkMode); + const argType = checkExpressionWithContextualType(arg, paramType, /*inferenceContext*/ undefined, checkMode); // If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive), // we obtain the regular type of any object literal arguments because we may not have inferred complete // parameter types yet and therefore excess property checks may yield false positives (see #17041). @@ -21855,14 +21850,14 @@ namespace ts { return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : fallbackType; } - function inferFromAnnotatedParameters(signature: Signature, context: Signature, mapper: TypeMapper) { + function inferFromAnnotatedParameters(signature: Signature, context: Signature, inferenceContext: InferenceContext) { const len = signature.parameters.length - (signature.hasRestParameter ? 1 : 0); for (let i = 0; i < len; i++) { const declaration = signature.parameters[i].valueDeclaration; if (declaration.type) { const typeNode = getEffectiveTypeAnnotationNode(declaration); if (typeNode) { - inferTypes((mapper).inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i)); + inferTypes(inferenceContext.inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i)); } } } @@ -21870,12 +21865,12 @@ namespace ts { if (restType && restType.flags & TypeFlags.TypeParameter) { // The contextual signature has a generic rest parameter. We first instantiate the contextual // signature (without fixing type parameters) and assign types to contextually typed parameters. - const instantiatedContext = instantiateSignature(context, cloneTypeMapper(mapper)); + const instantiatedContext = instantiateSignature(context, cloneInferenceContext(inferenceContext)); assignContextualParameterTypes(signature, instantiatedContext); // We then infer from a tuple type representing the parameters that correspond to the contextual // rest parameter. const restPos = getParameterCount(context) - 1; - inferTypes((mapper).inferences, getRestTypeAtPosition(signature, restPos), restType); + inferTypes(inferenceContext.inferences, getRestTypeAtPosition(signature, restPos), restType); } } @@ -22316,12 +22311,12 @@ namespace ts { if (contextualSignature) { const signature = getSignaturesOfType(type, SignatureKind.Call)[0]; if (isContextSensitive(node)) { - const contextualMapper = getContextualMapper(node); + const inferenceContext = getInferenceContext(node); if (checkMode && checkMode & CheckMode.Inferential) { - inferFromAnnotatedParameters(signature, contextualSignature, contextualMapper); + inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); } - const instantiatedContextualSignature = contextualMapper === identityMapper ? - contextualSignature : instantiateSignature(contextualSignature, contextualMapper); + const instantiatedContextualSignature = inferenceContext ? + instantiateSignature(contextualSignature, inferenceContext) : contextualSignature; assignContextualParameterTypes(signature, instantiatedContextualSignature); } if (!getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { @@ -23328,20 +23323,20 @@ namespace ts { return node; } - function checkExpressionWithContextualType(node: Expression, contextualType: Type, contextualMapper: TypeMapper | undefined, checkMode: CheckMode): Type { + function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { const context = getContextNode(node); const saveContextualType = context.contextualType; - const saveContextualMapper = context.contextualMapper; + const saveInferenceContext = context.inferenceContext; context.contextualType = contextualType; - context.contextualMapper = contextualMapper; - const type = checkExpression(node, checkMode | CheckMode.Contextual | (contextualMapper ? CheckMode.Inferential : 0)); + context.inferenceContext = inferenceContext; + const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); // We strip literal freshness when an appropriate contextual type is present such that contextually typed // literals always preserve their literal types (otherwise they might widen during type inference). An alternative // here would be to not mark contextually typed literals as fresh in the first place. const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node)) ? getRegularTypeOfLiteralType(type) : type; context.contextualType = saveContextualType; - context.contextualMapper = saveContextualMapper; + context.inferenceContext = saveInferenceContext; return result; } @@ -23467,7 +23462,7 @@ namespace ts { if (contextualType) { const contextualSignature = getSingleCallSignature(getNonNullableType(contextualType)); if (contextualSignature && !contextualSignature.typeParameters) { - const context = getContextualMapper(node); + const context = getInferenceContext(node); // We have an expression that is an argument of a generic function for which we are performing // type argument inference. The expression is of a function type with a single generic call // signature and a contextual function type with a single non-generic call signature. Now check @@ -23507,7 +23502,7 @@ namespace ts { if (checkMode & CheckMode.Inferential) { // We have skipped a generic function during inferential typing. Obtain the inference context and // indicate this has occurred such that we know a second pass of inference is be needed. - const context = getContextualMapper(node); + const context = getInferenceContext(node); context.flags |= InferenceFlags.SkippedGenericFunction; } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8c0091f0ba6..58ce47c2b1a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -630,7 +630,7 @@ namespace ts { /* @internal */ flowNode?: FlowNode; // Associated FlowNode (initialized by binding) /* @internal */ emitNode?: EmitNode; // Associated EmitNode (initialized by transforms) /* @internal */ contextualType?: Type; // Used to temporarily assign a contextual type during overload resolution - /* @internal */ contextualMapper?: TypeMapper; // Mapper for contextual type + /* @internal */ inferenceContext?: InferenceContext; // Inference context for contextual type } export interface JSDocContainer { @@ -4454,7 +4454,7 @@ namespace ts { flags: InferenceFlags; // Inference flags compareTypes: TypeComparer; // Type comparer function returnMapper?: TypeMapper; // Type mapper for inferences from return types (if any) - inferredTypeParameters?: ReadonlyArray; + inferredTypeParameters?: ReadonlyArray; // Inferred type parameters for function result } /* @internal */ From af8cf90e494984ec7d0b2b16fb0f328f45820071 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 11 Mar 2019 15:56:03 -0700 Subject: [PATCH 2/6] InferenceContext has-a (not is-a) mapper and has-a new nonFixingMapper --- src/compiler/checker.ts | 105 ++++++++++++++++++---------------------- src/compiler/types.ts | 7 +-- 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2766fe6da59..61483be26da 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10169,7 +10169,7 @@ namespace ts { // types rules (i.e. proper contravariance) for inferences. inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); } - combinedMapper = combineTypeMappers(mapper, context); + combinedMapper = combineTypeMappers(mapper, context.mapper); } // Instantiate the extends type including inferences for 'infer T' type parameters const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; @@ -10778,8 +10778,8 @@ namespace ts { return t => typeParameters.indexOf(t) >= index ? emptyObjectType : t; } - function cloneInferenceContext(context: InferenceContext, extraFlags: InferenceFlags = 0): InferenceContext { - return createInferenceContext(context.typeParameters, context.signature, context.flags | extraFlags, context.compareTypes, context.inferences); + function cloneInferenceContext(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined { + return context && createInferenceContext(context.typeParameters, context.signature, context.flags | extraFlags, context.compareTypes, context.inferences); } function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined { @@ -10793,10 +10793,9 @@ namespace ts { inferences.push(info); } } - if (!params.length) { - return undefined; - } - return createInferenceContext(params, context.signature, context.flags | InferenceFlags.NoDefault, context.compareTypes, inferences); + return params.length ? + createInferenceContext(params, context.signature, context.flags | InferenceFlags.NoDefault, context.compareTypes, inferences) : + undefined; } function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper; @@ -14303,26 +14302,31 @@ namespace ts { } function createInferenceContext(typeParameters: ReadonlyArray, signature: Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer, baseInferences?: InferenceInfo[]): InferenceContext { - const inferences = baseInferences ? baseInferences.map(cloneInferenceInfo) : typeParameters.map(createInferenceInfo); - const context = mapper as InferenceContext; - context.typeParameters = typeParameters; - context.signature = signature; - context.inferences = inferences; - context.flags = flags; - context.compareTypes = compareTypes || compareTypesAssignable; + const context: InferenceContext = { + typeParameters, + signature, + inferences: baseInferences ? baseInferences.map(cloneInferenceInfo) : typeParameters.map(createInferenceInfo), + flags, + compareTypes: compareTypes || compareTypesAssignable, + mapper: t => mapToInferredType(context, t, /*fix*/ true), + nonFixingMapper: t => mapToInferredType(context, t, /*fix*/ false), + }; return context; + } - function mapper(t: Type): Type { - for (let i = 0; i < inferences.length; i++) { - if (t === inferences[i].typeParameter) { - if (!(context.flags & InferenceFlags.NoFixing)) { - inferences[i].isFixed = true; - } - return getInferredType(context, i); + function mapToInferredType(context: InferenceContext, t: Type, fix: boolean): Type { + const inferences = context.inferences; + for (let i = 0; i < inferences.length; i++) { + const inference = inferences[i]; + if (t === inference.typeParameter) { + if (fix && !inference.isFixed) { + inference.isFixed = true; + inference.inferredType = undefined; } + return getInferredType(context, i); } - return t; } + return t; } function createInferenceInfo(typeParameter: TypeParameter): InferenceInfo { @@ -14349,6 +14353,10 @@ namespace ts { }; } + function getMapperFromContext(context: T): TypeMapper | T & undefined { + return context && context.mapper; + } + // Return true if the given type could possibly reference a type parameter for which // we perform type inference (i.e. a type parameter of a generic function). We cache // results for union and intersection types for performance reasons. @@ -14591,15 +14599,18 @@ namespace ts { const candidate = propagationType || source; // We make contravariant inferences only if we are in a pure contravariant position, // i.e. only if we have not descended into a bivariant position. - if (contravariant && !bivariant) { - inference.contraCandidates = appendIfUnique(inference.contraCandidates, candidate); + if (contravariant && !bivariant && !contains(inference.contraCandidates, candidate)) { + inference.contraCandidates = append(inference.contraCandidates, candidate); + inference.inferredType = undefined; } - else { - inference.candidates = appendIfUnique(inference.candidates, candidate); + else if (!contains(inference.candidates, candidate)) { + inference.candidates = append(inference.candidates, candidate); + inference.inferredType = undefined; } } - if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && !isTypeParameterAtTopLevel(originalTarget, target)) { + if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && inference.topLevel && !isTypeParameterAtTopLevel(originalTarget, target)) { inference.topLevel = false; + inference.inferredType = undefined; } } return; @@ -15032,7 +15043,7 @@ namespace ts { inferredType = instantiateType(defaultType, combineTypeMappers( createBackreferenceMapper(context.typeParameters, index), - context)); + context.nonFixingMapper)); } else { inferredType = getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault)); @@ -15047,12 +15058,10 @@ namespace ts { const constraint = getConstraintOfTypeParameter(inference.typeParameter); if (constraint) { - context.flags |= InferenceFlags.NoFixing; - const instantiatedConstraint = instantiateType(constraint, context); + const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); if (!context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { inference.inferredType = inferredType = instantiatedConstraint; } - context.flags &= ~InferenceFlags.NoFixing; } } @@ -17525,7 +17534,7 @@ namespace ts { while (type) { const thisType = getThisTypeFromContextualType(type); if (thisType) { - return instantiateType(thisType, getInferenceContext(containingLiteral)); + return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral))); } if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { break; @@ -20135,7 +20144,7 @@ namespace ts { // the contextual signature is (...args: A) => B, we want to infer the element type of A's constraint (say 'any') // for T but leave it possible to later infer '[any]' back to A. const restType = getEffectiveRestType(contextualSignature); - const mapper = inferenceContext && restType && restType.flags & TypeFlags.TypeParameter ? cloneInferenceContext(inferenceContext) : inferenceContext; + const mapper = inferenceContext && (restType && restType.flags & TypeFlags.TypeParameter ? inferenceContext.nonFixingMapper : inferenceContext.mapper); const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature; forEachMatchingParameterType(sourceSignature, signature, (source, target) => { // Type parameters from outer context referenced by source type are fixed by instantiation of the source type @@ -20161,17 +20170,6 @@ namespace ts { } function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: ReadonlyArray, checkMode: CheckMode, context: InferenceContext): Type[] { - // Clear out all the inference results from the last time inferTypeArguments was called on this context - for (const inference of context.inferences) { - // As an optimization, we don't have to clear (and later recompute) inferred types - // for type parameters that have already been fixed on the previous call to inferTypeArguments. - // It would be just as correct to reset all of them. But then we'd be repeating the same work - // for the type parameters that were fixed, namely the work done by getInferredType. - if (!inference.isFixed) { - inference.inferredType = undefined; - } - } - if (isJsxOpeningLikeElement(node)) { return inferJsxTypeArguments(node, signature, checkMode, context); } @@ -20183,11 +20181,11 @@ namespace ts { if (node.kind !== SyntaxKind.Decorator) { const contextualType = getContextualType(node); if (contextualType) { - // We clone the contextual mapper to avoid disturbing a resolution in progress for an + // We clone the inference context to avoid disturbing a resolution in progress for an // outer call expression. Effectively we just want a snapshot of whatever has been // inferred for any outer call expression so far. - const outerContext = getInferenceContext(node); - const instantiatedType = instantiateType(contextualType, outerContext && cloneInferenceContext(outerContext, InferenceFlags.NoDefault)); + const outerMapper = getMapperFromContext(cloneInferenceContext(getInferenceContext(node), InferenceFlags.NoDefault)); + const instantiatedType = instantiateType(contextualType, outerMapper); // If the contextual type is a generic function type with a single call signature, we // instantiate the type with its own type parameters and type arguments. This ensures that // the type parameters are not erased to type any during type inference such that they can @@ -20204,7 +20202,7 @@ namespace ts { inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); // Create a type mapper for instantiating generic contextual types using the inferences made // from the return type. - context.returnMapper = cloneInferredPartOfContext(context); + context.returnMapper = getMapperFromContext(cloneInferredPartOfContext(context)); } } @@ -21865,7 +21863,7 @@ namespace ts { if (restType && restType.flags & TypeFlags.TypeParameter) { // The contextual signature has a generic rest parameter. We first instantiate the contextual // signature (without fixing type parameters) and assign types to contextually typed parameters. - const instantiatedContext = instantiateSignature(context, cloneInferenceContext(inferenceContext)); + const instantiatedContext = instantiateSignature(context, inferenceContext.nonFixingMapper); assignContextualParameterTypes(signature, instantiatedContext); // We then infer from a tuple type representing the parameters that correspond to the contextual // rest parameter. @@ -22316,7 +22314,7 @@ namespace ts { inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); } const instantiatedContextualSignature = inferenceContext ? - instantiateSignature(contextualSignature, inferenceContext) : contextualSignature; + instantiateSignature(contextualSignature, inferenceContext.mapper) : contextualSignature; assignContextualParameterTypes(signature, instantiatedContextualSignature); } if (!getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { @@ -23617,13 +23615,6 @@ namespace ts { return type; } - // Checks an expression and returns its type. The contextualMapper parameter serves two purposes: When - // contextualMapper is not undefined and not equal to the identityMapper function object it indicates that the - // expression is being inferentially typed (section 4.15.2 in spec) and provides the type mapper to use in - // conjunction with the generic contextual type. When contextualMapper is equal to the identityMapper function - // object, it serves as an indicator that all contained function and arrow expressions should be considered to - // have the wildcard function type; this form of type check is used during overload resolution to exclude - // contextually typed function and arrow expressions in the initial phase. function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, forceTuple?: boolean): Type { const saveCurrentNode = currentNode; currentNode = node; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 58ce47c2b1a..73604ca6f93 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4423,8 +4423,7 @@ namespace ts { None = 0, // No special inference behaviors NoDefault = 1 << 0, // Infer unknownType for no inferences (otherwise anyType or emptyObjectType) AnyDefault = 1 << 1, // Infer anyType for no inferences (otherwise emptyObjectType) - NoFixing = 1 << 2, // Disable type parameter fixing - SkippedGenericFunction = 1 << 3, + SkippedGenericFunction = 1 << 2, // A generic function was skipped during inference } /** @@ -4447,12 +4446,14 @@ namespace ts { export type TypeComparer = (s: Type, t: Type, reportErrors?: boolean) => Ternary; /* @internal */ - export interface InferenceContext extends TypeMapper { + export interface InferenceContext { typeParameters: ReadonlyArray; // Type parameters for which inferences are made signature?: Signature; // Generic signature for which inferences are made (if any) inferences: InferenceInfo[]; // Inferences made for each type parameter flags: InferenceFlags; // Inference flags compareTypes: TypeComparer; // Type comparer function + mapper: TypeMapper; // Mapper that fixes inferences + nonFixingMapper: TypeMapper; // Mapper that doesn't fix inferences returnMapper?: TypeMapper; // Type mapper for inferences from return types (if any) inferredTypeParameters?: ReadonlyArray; // Inferred type parameters for function result } From de3f5f90cdcab87f31e8b4208c4b039103758a8b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 12 Mar 2019 06:17:33 -0700 Subject: [PATCH 3/6] Remove redundant typeParameters property from InferenceContext --- src/compiler/checker.ts | 53 +++++++++++++++++------------------------ src/compiler/types.ts | 3 +-- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 61483be26da..32dcf01d981 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10774,28 +10774,8 @@ namespace ts { * Maps forward-references to later types parameters to the empty object type. * This is used during inference when instantiating type parameter defaults. */ - function createBackreferenceMapper(typeParameters: ReadonlyArray, index: number): TypeMapper { - return t => typeParameters.indexOf(t) >= index ? emptyObjectType : t; - } - - function cloneInferenceContext(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined { - return context && createInferenceContext(context.typeParameters, context.signature, context.flags | extraFlags, context.compareTypes, context.inferences); - } - - function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined { - // Filter context to only those parameters which actually have inference candidates - const params = []; - const inferences = []; - for (let i = 0; i < context.typeParameters.length; i++) { - const info = context.inferences[i]; - if (info.candidates || info.contraCandidates) { - params.push(context.typeParameters[i]); - inferences.push(info); - } - } - return params.length ? - createInferenceContext(params, context.signature, context.flags | InferenceFlags.NoDefault, context.compareTypes, inferences) : - undefined; + function createBackreferenceMapper(context: InferenceContext, index: number): TypeMapper { + return t => findIndex(context.inferences, info => info.typeParameter === t) >= index ? emptyObjectType : t; } function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper; @@ -14301,13 +14281,27 @@ namespace ts { } } - function createInferenceContext(typeParameters: ReadonlyArray, signature: Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer, baseInferences?: InferenceInfo[]): InferenceContext { + function createInferenceContext(typeParameters: ReadonlyArray, signature: Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer): InferenceContext { + return createInferenceContextWorker(typeParameters.map(createInferenceInfo), signature, flags, compareTypes || compareTypesAssignable); + } + + function cloneInferenceContext(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined { + return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes); + } + + function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined { + const inferences = filter(context.inferences, hasInferenceCandidates); + return inferences.length ? + createInferenceContextWorker(map(inferences, cloneInferenceInfo), context.signature, context.flags, context.compareTypes) : + undefined; + } + + function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext { const context: InferenceContext = { - typeParameters, + inferences, signature, - inferences: baseInferences ? baseInferences.map(cloneInferenceInfo) : typeParameters.map(createInferenceInfo), flags, - compareTypes: compareTypes || compareTypesAssignable, + compareTypes, mapper: t => mapToInferredType(context, t, /*fix*/ true), nonFixingMapper: t => mapToInferredType(context, t, /*fix*/ false), }; @@ -15040,10 +15034,7 @@ namespace ts { if (defaultType) { // Instantiate the default type. Any forward reference to a type // parameter should be instantiated to the empty object type. - inferredType = instantiateType(defaultType, - combineTypeMappers( - createBackreferenceMapper(context.typeParameters, index), - context.nonFixingMapper)); + inferredType = instantiateType(defaultType, combineTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper)); } else { inferredType = getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault)); @@ -23476,7 +23467,7 @@ namespace ts { const strippedType = getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(signature, uniqueTypeParameters)); // Infer from the stripped expression type to the contextual type starting with an empty // set of inference candidates. - const inferences = map(context.typeParameters, createInferenceInfo); + const inferences = map(context.inferences, info => createInferenceInfo(info.typeParameter)); inferTypes(inferences, strippedType, contextualType); // If we produced some inference candidates and if the type parameters for which we produced // candidates do not already have existing inferences, we adopt the new inference candidates and diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 73604ca6f93..25e1ebe0a13 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4447,9 +4447,8 @@ namespace ts { /* @internal */ export interface InferenceContext { - typeParameters: ReadonlyArray; // Type parameters for which inferences are made - signature?: Signature; // Generic signature for which inferences are made (if any) inferences: InferenceInfo[]; // Inferences made for each type parameter + signature?: Signature; // Generic signature for which inferences are made (if any) flags: InferenceFlags; // Inference flags compareTypes: TypeComparer; // Type comparer function mapper: TypeMapper; // Mapper that fixes inferences From 7f90ad11557511722b3733271583653478bd584f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 12 Mar 2019 06:29:33 -0700 Subject: [PATCH 4/6] Add tests --- tests/cases/compiler/genericFunctionInference1.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/cases/compiler/genericFunctionInference1.ts b/tests/cases/compiler/genericFunctionInference1.ts index f2a71ca7f02..237b135ca94 100644 --- a/tests/cases/compiler/genericFunctionInference1.ts +++ b/tests/cases/compiler/genericFunctionInference1.ts @@ -185,3 +185,11 @@ const fn62 = pipe( x => x, x => first(x), ); + +// Repro from #30297 + +declare function foo2(fn: T, a?: U, b?: U): [T, U]; + +foo2(() => {}); +foo2(identity); +foo2(identity, 1); From 4b2fc794519e08fa68f91c306bf7d638f4708f8c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 12 Mar 2019 06:29:40 -0700 Subject: [PATCH 5/6] Accept new baselines --- .../genericFunctionInference1.errors.txt | 8 ++++++ .../reference/genericFunctionInference1.js | 11 ++++++++ .../genericFunctionInference1.symbols | 27 +++++++++++++++++++ .../reference/genericFunctionInference1.types | 24 +++++++++++++++++ 4 files changed, 70 insertions(+) diff --git a/tests/baselines/reference/genericFunctionInference1.errors.txt b/tests/baselines/reference/genericFunctionInference1.errors.txt index 9b082d39ab2..fd423a4ea5a 100644 --- a/tests/baselines/reference/genericFunctionInference1.errors.txt +++ b/tests/baselines/reference/genericFunctionInference1.errors.txt @@ -190,4 +190,12 @@ tests/cases/compiler/genericFunctionInference1.ts(83,14): error TS2345: Argument x => x, x => first(x), ); + + // Repro from #30297 + + declare function foo2(fn: T, a?: U, b?: U): [T, U]; + + foo2(() => {}); + foo2(identity); + foo2(identity, 1); \ No newline at end of file diff --git a/tests/baselines/reference/genericFunctionInference1.js b/tests/baselines/reference/genericFunctionInference1.js index 4e89f9c0795..bfca871a6c9 100644 --- a/tests/baselines/reference/genericFunctionInference1.js +++ b/tests/baselines/reference/genericFunctionInference1.js @@ -183,6 +183,14 @@ const fn62 = pipe( x => x, x => first(x), ); + +// Repro from #30297 + +declare function foo2(fn: T, a?: U, b?: U): [T, U]; + +foo2(() => {}); +foo2(identity); +foo2(identity, 1); //// [genericFunctionInference1.js] @@ -265,3 +273,6 @@ const fn40 = pipe(getString, string => orUndefined(string), identity); const fn60 = pipe(getArray, x => x, first); const fn61 = pipe(getArray, identity, first); const fn62 = pipe(getArray, x => x, x => first(x)); +foo2(() => { }); +foo2(identity); +foo2(identity, 1); diff --git a/tests/baselines/reference/genericFunctionInference1.symbols b/tests/baselines/reference/genericFunctionInference1.symbols index 9ee085feae8..cad4d862d9c 100644 --- a/tests/baselines/reference/genericFunctionInference1.symbols +++ b/tests/baselines/reference/genericFunctionInference1.symbols @@ -848,3 +848,30 @@ const fn62 = pipe( ); +// Repro from #30297 + +declare function foo2(fn: T, a?: U, b?: U): [T, U]; +>foo2 : Symbol(foo2, Decl(genericFunctionInference1.ts, 183, 2)) +>T : Symbol(T, Decl(genericFunctionInference1.ts, 187, 22)) +>U : Symbol(U, Decl(genericFunctionInference1.ts, 187, 24)) +>T : Symbol(T, Decl(genericFunctionInference1.ts, 187, 22)) +>fn : Symbol(fn, Decl(genericFunctionInference1.ts, 187, 32)) +>T : Symbol(T, Decl(genericFunctionInference1.ts, 187, 22)) +>a : Symbol(a, Decl(genericFunctionInference1.ts, 187, 38)) +>U : Symbol(U, Decl(genericFunctionInference1.ts, 187, 24)) +>b : Symbol(b, Decl(genericFunctionInference1.ts, 187, 45)) +>U : Symbol(U, Decl(genericFunctionInference1.ts, 187, 24)) +>T : Symbol(T, Decl(genericFunctionInference1.ts, 187, 22)) +>U : Symbol(U, Decl(genericFunctionInference1.ts, 187, 24)) + +foo2(() => {}); +>foo2 : Symbol(foo2, Decl(genericFunctionInference1.ts, 183, 2)) + +foo2(identity); +>foo2 : Symbol(foo2, Decl(genericFunctionInference1.ts, 183, 2)) +>identity : Symbol(identity, Decl(genericFunctionInference1.ts, 154, 13)) + +foo2(identity, 1); +>foo2 : Symbol(foo2, Decl(genericFunctionInference1.ts, 183, 2)) +>identity : Symbol(identity, Decl(genericFunctionInference1.ts, 154, 13)) + diff --git a/tests/baselines/reference/genericFunctionInference1.types b/tests/baselines/reference/genericFunctionInference1.types index e68b0ed6da0..a06ad6cffc5 100644 --- a/tests/baselines/reference/genericFunctionInference1.types +++ b/tests/baselines/reference/genericFunctionInference1.types @@ -799,3 +799,27 @@ const fn62 = pipe( ); +// Repro from #30297 + +declare function foo2(fn: T, a?: U, b?: U): [T, U]; +>foo2 : (fn: T, a?: U | undefined, b?: U | undefined) => [T, U] +>fn : T +>a : U | undefined +>b : U | undefined + +foo2(() => {}); +>foo2(() => {}) : [() => void, () => void] +>foo2 : (fn: T, a?: U | undefined, b?: U | undefined) => [T, U] +>() => {} : () => void + +foo2(identity); +>foo2(identity) : [(value: T) => T, {}] +>foo2 : (fn: T, a?: U | undefined, b?: U | undefined) => [T, U] +>identity : (value: T) => T + +foo2(identity, 1); +>foo2(identity, 1) : [(value: T) => T, number] +>foo2 : (fn: T, a?: U | undefined, b?: U | undefined) => [T, U] +>identity : (value: T) => T +>1 : 1 + From 12cd995e04437bafcb6bf014452f3081731385e8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 12 Mar 2019 06:59:25 -0700 Subject: [PATCH 6/6] Change casts to non-null assertions --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 32dcf01d981..66be0fb2727 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23451,7 +23451,7 @@ namespace ts { if (contextualType) { const contextualSignature = getSingleCallSignature(getNonNullableType(contextualType)); if (contextualSignature && !contextualSignature.typeParameters) { - const context = getInferenceContext(node); + const context = getInferenceContext(node)!; // We have an expression that is an argument of a generic function for which we are performing // type argument inference. The expression is of a function type with a single generic call // signature and a contextual function type with a single non-generic call signature. Now check @@ -23491,7 +23491,7 @@ namespace ts { if (checkMode & CheckMode.Inferential) { // We have skipped a generic function during inferential typing. Obtain the inference context and // indicate this has occurred such that we know a second pass of inference is be needed. - const context = getInferenceContext(node); + const context = getInferenceContext(node)!; context.flags |= InferenceFlags.SkippedGenericFunction; } }