From 65f453f98b06f57034fd196650ae0d13dce09e58 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 12 May 2022 11:12:44 -0700 Subject: [PATCH] Skip unnecessary inference pass --- src/compiler/checker.ts | 134 +++++++++--------- src/compiler/types.ts | 9 +- ...ndingPatternCannotBeOnlyInferenceSource.ts | 4 +- 3 files changed, 76 insertions(+), 71 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 25fd5004567..7d15bc9adc3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22947,46 +22947,39 @@ namespace ts { if (!inference.inferredType) { let inferredType: Type | undefined; const signature = context.signature; - if (!(inference.priority! & InferencePriority.BindingPattern)) { - // Binding pattern inferences provide a contextual type for other inferences, - // but cannot stand alone - they are expected to be overwritten by another - // inference with higher priority before fixing. This prevents highly suspicious - // patterns like `function f(): T; const { foo } = f()` from inferring T as - // `{ foo: any }`. - if (signature) { - const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined; - if (inference.contraCandidates) { - // If we have both co- and contra-variant inferences, we prefer the contra-variant inference - // unless the co-variant inference is a subtype of some contra-variant inference and not 'never'. - inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) && - some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) ? - inferredCovariantType : getContravariantInference(inference); - } - else if (inferredCovariantType) { - inferredType = inferredCovariantType; - } - else if (context.flags & InferenceFlags.NoDefault) { - // We use silentNeverType as the wildcard that signals no inferences. - inferredType = silentNeverType; - } - else { - // Infer either the default or the empty object type when no inferences were - // made. It is important to remember that in this case, inference still - // succeeds, meaning there is no error for not having inference candidates. An - // inference error only occurs when there are *conflicting* candidates, i.e. - // candidates with no common supertype. - const defaultType = getDefaultFromTypeParameter(inference.typeParameter); - if (defaultType) { - // Instantiate the default type. Any forward reference to a type - // parameter should be instantiated to the empty object type. - inferredType = instantiateType(defaultType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper)); - } - } + if (signature) { + const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined; + if (inference.contraCandidates) { + // If we have both co- and contra-variant inferences, we prefer the contra-variant inference + // unless the co-variant inference is a subtype of some contra-variant inference and not 'never'. + inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) && + some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) ? + inferredCovariantType : getContravariantInference(inference); + } + else if (inferredCovariantType) { + inferredType = inferredCovariantType; + } + else if (context.flags & InferenceFlags.NoDefault) { + // We use silentNeverType as the wildcard that signals no inferences. + inferredType = silentNeverType; } else { - inferredType = getTypeFromInference(inference); + // Infer either the default or the empty object type when no inferences were + // made. It is important to remember that in this case, inference still + // succeeds, meaning there is no error for not having inference candidates. An + // inference error only occurs when there are *conflicting* candidates, i.e. + // candidates with no common supertype. + const defaultType = getDefaultFromTypeParameter(inference.typeParameter); + if (defaultType) { + // Instantiate the default type. Any forward reference to a type + // parameter should be instantiated to the empty object type. + inferredType = instantiateType(defaultType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper)); + } } } + else { + inferredType = getTypeFromInference(inference); + } inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault)); @@ -27062,22 +27055,22 @@ namespace ts { const inferenceContext = getInferenceContext(node); // If no inferences have been made, nothing is gained from instantiating as type parameters // would just be replaced with their defaults similar to the apparent type. - if (inferenceContext && some(inferenceContext.inferences, hasInferenceCandidates)) { + if (inferenceContext && contextFlags! & ContextFlags.Signature && (inferenceContext.returnMapper || some(inferenceContext.inferences, hasInferenceCandidates))) { // For contextual signatures we incorporate all inferences made so far, e.g. from return // types as well as arguments to the left in a function call. - if (contextFlags && contextFlags & ContextFlags.Signature) { - return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper); - } + return instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper + ? combineTypeMappers(inferenceContext.nonFixingMapper, inferenceContext.returnMapper) + : inferenceContext.nonFixingMapper); + } + if (inferenceContext?.returnMapper) { // For other purposes (e.g. determining whether to produce literal types) we only // incorporate inferences made from the return type in a function call. We remove // the 'boolean' type from the contextual type such that contextually typed boolean // literals actually end up widening to 'boolean' (see #48363). - if (inferenceContext.returnMapper) { - const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); - return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ? - filterType(type, t => t !== regularFalseType && t !== regularTrueType) : - type; - } + const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); + return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ? + filterType(type, t => t !== regularFalseType && t !== regularTrueType) : + type; } } return contextualType; @@ -29918,27 +29911,38 @@ namespace ts { if (contextualType) { const inferenceTargetType = getReturnTypeOfSignature(signature); if (couldContainTypeVariables(inferenceTargetType)) { - // 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 outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, 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 - // be inferred as actual types from the contextual type. For example: - // declare function arrayMap(f: (x: T) => U): (a: T[]) => U[]; - // const boxElements: (a: A[]) => { value: A }[] = arrayMap(value => ({ value })); - // Above, the type of the 'value' parameter is inferred to be 'A'. - const contextualSignature = getSingleCallSignature(instantiatedType); - const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ? - getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) : - instantiatedType; - // Inferences made from return types have lower priority than all other inferences. const isFromBindingPattern = !skipBindingPatterns && getContextualType(node, ContextFlags.SkipBindingPatterns) !== contextualType; - const priority = InferencePriority.ReturnType | (isFromBindingPattern ? InferencePriority.BindingPattern : 0); - inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, priority); + // A return type inference from a binding pattern can be used in instantiating the contextual + // type of an argument later in inference, but cannot stand on its own as the final return type. + // It is incorporated into `context.returnMapper` which is used in `instantiateContextualType`, + // but doesn't need to go into `context.inferences`. This allows a an array binding pattern to + // produce a tuple for `T` in + // declare function f(cb: () => T): T; + // const [e1, e2, e3] = f(() => [1, "hi", true]); + // but does not produce any inference for `T` in + // declare function f(): T; + // const [e1, e2, e3] = f(); + if (!isFromBindingPattern) { + // 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 outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, 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 + // be inferred as actual types from the contextual type. For example: + // declare function arrayMap(f: (x: T) => U): (a: T[]) => U[]; + // const boxElements: (a: A[]) => { value: A }[] = arrayMap(value => ({ value })); + // Above, the type of the 'value' parameter is inferred to be 'A'. + const contextualSignature = getSingleCallSignature(instantiatedType); + const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ? + getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) : + instantiatedType; + // Inferences made from return types have lower priority than all other inferences. + inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); + } // Create a type mapper for instantiating generic contextual types using the inferences made // from the return type. We need a separate inference pass here because (a) instantiation of // the source type uses the outer context's return mapper (which excludes inferences made from diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b0f1f4c869e..f72207f24dc 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5865,11 +5865,10 @@ namespace ts { MappedTypeConstraint = 1 << 5, // Reverse inference for mapped type ContravariantConditional = 1 << 6, // Conditional type in contravariant position ReturnType = 1 << 7, // Inference made from return type of generic function - BindingPattern = 1 << 8, // Inference made from binding pattern - LiteralKeyof = 1 << 9, // Inference made from a string literal to a keyof T - NoConstraints = 1 << 10, // Don't infer from constraints of instantiable types - AlwaysStrict = 1 << 11, // Always use strict rules for contravariant inferences - MaxValue = 1 << 12, // Seed for inference priority tracking + LiteralKeyof = 1 << 8, // Inference made from a string literal to a keyof T + NoConstraints = 1 << 9, // Don't infer from constraints of instantiable types + AlwaysStrict = 1 << 10, // Always use strict rules for contravariant inferences + MaxValue = 1 << 11, // Seed for inference priority tracking PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates Circularity = -1, // Inference circularity (value less than all other priorities) diff --git a/tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts b/tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts index 0ac5a1f26b8..361ae8631f6 100644 --- a/tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts +++ b/tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts @@ -1,5 +1,7 @@ +// @strictNullChecks: true + declare function f(): T; -const {} = f(); // error +const {} = f(); // error (only in strictNullChecks) const { p1 } = f(); // error const [] = f(); // error const [e1, e2] = f(); // error