| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:math' as math; |
| |
| import 'package:_fe_analyzer_shared/src/type_inference/shared_inference_log.dart'; |
| import 'package:_fe_analyzer_shared/src/type_inference/type_constraint.dart'; |
| import 'package:_fe_analyzer_shared/src/types/shared_type.dart'; |
| import 'package:analyzer/dart/ast/ast.dart' |
| show |
| Annotation, |
| AsExpression, |
| AstNode, |
| ConstructorName, |
| Expression, |
| InvocationExpression, |
| SimpleIdentifier; |
| import 'package:analyzer/dart/ast/syntactic_entity.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/error/listener.dart' show ErrorReporter; |
| import 'package:analyzer/src/dart/ast/extensions.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/element/type_algebra.dart'; |
| import 'package:analyzer/src/dart/element/type_constraint_gatherer.dart'; |
| import 'package:analyzer/src/dart/element/type_provider.dart'; |
| import 'package:analyzer/src/dart/element/type_schema.dart'; |
| import 'package:analyzer/src/dart/element/type_system.dart'; |
| import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart'; |
| import 'package:analyzer/src/error/codes.dart' |
| show CompileTimeErrorCode, WarningCode; |
| import 'package:analyzer/src/generated/inference_log.dart'; |
| import 'package:collection/collection.dart'; |
| |
| /// Tracks upper and lower type bounds for a set of type parameters. |
| /// |
| /// This class is used by calling [isSubtypeOf]. When it encounters one of |
| /// the type parameters it is inferring, it will record the constraint, and |
| /// optimistically assume the constraint will be satisfied. |
| /// |
| /// For example if we are inferring type parameter A, and we ask if |
| /// `A <: num`, this will record that A must be a subtype of `num`. It also |
| /// handles cases when A appears as part of the structure of another type, for |
| /// example `Iterable<A> <: Iterable<num>` would infer the same constraint |
| /// (due to covariant generic types) as would `() -> A <: () -> num`. In |
| /// contrast `(A) -> void <: (num) -> void`. |
| /// |
| /// Once the lower/upper bounds are determined, [infer] should be called to |
| /// finish the inference. It will instantiate a generic function type with the |
| /// inferred types for each type parameter. |
| /// |
| /// It can also optionally compute a partial solution, in case some of the type |
| /// parameters could not be inferred (because the constraints cannot be |
| /// satisfied), or bail on the inference when this happens. |
| /// |
| /// As currently designed, an instance of this class should only be used to |
| /// infer a single call and discarded immediately afterwards. |
| class GenericInferrer { |
| final TypeSystemImpl _typeSystem; |
| final Set<TypeParameterElement> _typeParameters = Set.identity(); |
| final Map< |
| TypeParameterElement, |
| List< |
| MergedTypeConstraint< |
| DartType, |
| TypeParameterElement, |
| PromotableElement, |
| InterfaceType, |
| InterfaceElement>>> _constraints = {}; |
| |
| /// The list of type parameters being inferred. |
| final List<TypeParameterElement> _typeFormals; |
| |
| /// The [ErrorReporter] to which inference errors should be reported, or |
| /// `null` if errors shouldn't be reported. |
| final ErrorReporter? errorReporter; |
| |
| /// The [SyntacticEntity] to which errors should be attached. May be `null` |
| /// if errors are not being reported (that is, if [errorReporter] is also |
| /// `null`). |
| final SyntacticEntity? errorEntity; |
| |
| /// Indicates whether the "generic metadata" feature is enabled. When it is, |
| /// type arguments are allowed to be instantiated with generic function types. |
| final bool genericMetadataIsEnabled; |
| |
| final bool _strictInference; |
| |
| /// Map whose keys are type parameters for which a previous inference phase |
| /// has fixed a type, and whose values are the corresponding fixed types. |
| /// |
| /// Background: sometimes the upwards inference phase of generic type |
| /// inference is capable of assigning a more specific type than the downwards |
| /// inference phase, but we don't want to use the more specific type due to |
| /// Dart's "runtime checked covariant generics" design. For example, in this |
| /// code: |
| /// |
| /// List<num> x = [1, 2, 3]; |
| /// x.add(4.0); |
| /// |
| /// Downwards inference provisionally considers the list to be a `List<num>`. |
| /// Without this heuristic, upwards inference would refine the type to |
| /// `List<int>`, leading to a runtime failure. So what we do is fix the type |
| /// parameter to `num` after downwards inference, preventing upwards inference |
| /// from doing any further refinement. |
| /// |
| /// (Note that the heuristic isn't needed for type parameters whose variance |
| /// is explicitly specified using the as-yet-unreleased "variance" feature, |
| /// since type parameters whose variance is explicitly specified don't undergo |
| /// implicit runtime checks). |
| final Map<TypeParameterElement, DartType> _typesInferredSoFar = {}; |
| |
| final TypeSystemOperations _typeSystemOperations; |
| |
| final TypeConstraintGenerationDataForTesting? dataForTesting; |
| |
| GenericInferrer(this._typeSystem, this._typeFormals, |
| {this.errorReporter, |
| this.errorEntity, |
| required this.genericMetadataIsEnabled, |
| required bool strictInference, |
| required TypeSystemOperations typeSystemOperations, |
| required this.dataForTesting}) |
| : _strictInference = strictInference, |
| _typeSystemOperations = typeSystemOperations { |
| if (errorReporter != null) { |
| assert(errorEntity != null); |
| } |
| _typeParameters.addAll(_typeFormals); |
| for (var formal in _typeFormals) { |
| _constraints[formal] = []; |
| } |
| } |
| |
| TypeProviderImpl get typeProvider => _typeSystem.typeProvider; |
| |
| /// Performs upwards inference, producing a final set of inferred types that |
| /// does not contain references to the "unknown type". |
| List<DartType> chooseFinalTypes() => tryChooseFinalTypes(failAtError: false)!; |
| |
| /// Performs partial (either downwards or horizontal) inference, producing a |
| /// set of inferred types that may contain references to the "unknown type". |
| List<DartType> choosePreliminaryTypes() { |
| var types = _chooseTypes(preliminary: true); |
| inferenceLogWriter?.recordPreliminaryTypes(types); |
| return types; |
| } |
| |
| /// Apply an argument constraint, which asserts that the [argument] staticType |
| /// is a subtype of the [parameterType]. |
| void constrainArgument( |
| DartType argumentType, DartType parameterType, String parameterName, |
| {InterfaceElement? genericClass, required AstNode? nodeForTesting}) { |
| var origin = TypeConstraintFromArgument<DartType, PromotableElement, |
| TypeParameterElement, InterfaceType, InterfaceElement>( |
| argumentType: SharedTypeView(argumentType), |
| parameterType: SharedTypeView(parameterType), |
| parameterName: parameterName, |
| genericClassName: genericClass?.name, |
| isGenericClassInDartCore: genericClass?.library.isDartCore ?? false, |
| ); |
| inferenceLogWriter?.enterConstraintGeneration( |
| ConstraintGenerationSource.argument, argumentType, parameterType); |
| _tryMatchSubtypeOf(argumentType, parameterType, origin, |
| covariant: false, nodeForTesting: nodeForTesting); |
| inferenceLogWriter?.exitConstraintGeneration(); |
| } |
| |
| /// Applies all the argument constraints implied by [parameters] and |
| /// [argumentTypes]. |
| void constrainArguments( |
| {InterfaceElement? genericClass, |
| required List<ParameterElement> parameters, |
| required List<DartType> argumentTypes, |
| required AstNode? nodeForTesting}) { |
| for (int i = 0; i < argumentTypes.length; i++) { |
| // Try to pass each argument to each parameter, recording any type |
| // parameter bounds that were implied by this assignment. |
| constrainArgument( |
| argumentTypes[i], |
| parameters[i].type, |
| parameters[i].name, |
| genericClass: genericClass, |
| nodeForTesting: nodeForTesting, |
| ); |
| } |
| } |
| |
| /// Constrain a universal function type [fnType] used in a context |
| /// [contextType]. |
| void constrainGenericFunctionInContext( |
| FunctionType fnType, DartType contextType, |
| {required AstNode? nodeForTesting}) { |
| var origin = TypeConstraintFromFunctionContext< |
| DartType, |
| DartType, |
| DartType, |
| PromotableElement, |
| TypeParameterElement, |
| InterfaceType, |
| InterfaceElement>(functionType: fnType, contextType: contextType); |
| |
| // Since we're trying to infer the instantiation, we want to ignore type |
| // formals as we check the parameters and return type. |
| var inferFnType = FunctionTypeImpl( |
| typeFormals: const [], |
| parameters: fnType.parameters, |
| returnType: fnType.returnType, |
| nullabilitySuffix: fnType.nullabilitySuffix, |
| ); |
| inferenceLogWriter?.enterConstraintGeneration( |
| ConstraintGenerationSource.genericFunctionInContext, |
| inferFnType, |
| contextType); |
| _tryMatchSubtypeOf(inferFnType, contextType, origin, |
| covariant: true, nodeForTesting: nodeForTesting); |
| inferenceLogWriter?.exitConstraintGeneration(); |
| } |
| |
| /// Apply a return type constraint, which asserts that the [declaredType] |
| /// is a subtype of the [contextType]. |
| void constrainReturnType(DartType declaredType, DartType contextType, |
| {required AstNode? nodeForTesting}) { |
| var origin = TypeConstraintFromReturnType< |
| DartType, |
| DartType, |
| DartType, |
| PromotableElement, |
| TypeParameterElement, |
| InterfaceType, |
| InterfaceElement>(declaredType: declaredType, contextType: contextType); |
| inferenceLogWriter?.enterConstraintGeneration( |
| ConstraintGenerationSource.returnType, declaredType, contextType); |
| _tryMatchSubtypeOf(declaredType, contextType, origin, |
| covariant: true, nodeForTesting: nodeForTesting); |
| inferenceLogWriter?.exitConstraintGeneration(); |
| } |
| |
| /// Same as [chooseFinalTypes], but if [failAtError] is `true` (the default) |
| /// and inference fails, returns `null` rather than trying to perform error |
| /// recovery. |
| List<DartType>? tryChooseFinalTypes({bool failAtError = true}) { |
| var inferredTypes = _chooseTypes(preliminary: false); |
| // Check the inferred types against all of the constraints. |
| var knownTypes = <TypeParameterElement, DartType>{}; |
| var hasErrorReported = false; |
| for (int i = 0; i < _typeFormals.length; i++) { |
| TypeParameterElement parameter = _typeFormals[i]; |
| var constraints = _constraints[parameter]!; |
| |
| var inferred = inferredTypes[i]; |
| bool success = constraints.every((c) => |
| c.isSatisfiedBy(SharedTypeView(inferred), _typeSystemOperations)); |
| |
| // If everything else succeeded, check the `extends` constraint. |
| if (success) { |
| var parameterBoundRaw = parameter.bound; |
| if (parameterBoundRaw != null) { |
| var parameterBound = |
| Substitution.fromPairs(_typeFormals, inferredTypes) |
| .substituteType(parameterBoundRaw); |
| var extendsConstraint = MergedTypeConstraint< |
| DartType, |
| TypeParameterElement, |
| PromotableElement, |
| InterfaceType, |
| InterfaceElement>.fromExtends( |
| typeParameterName: parameter.name, |
| boundType: SharedTypeView(parameterBoundRaw), |
| extendsType: SharedTypeView(parameterBound), |
| typeAnalyzerOperations: _typeSystemOperations, |
| ); |
| constraints.add(extendsConstraint); |
| success = extendsConstraint.isSatisfiedBy( |
| SharedTypeView(inferred), _typeSystemOperations); |
| } |
| } |
| |
| if (!success) { |
| if (failAtError) { |
| inferenceLogWriter?.exitGenericInference(failed: true); |
| return null; |
| } |
| hasErrorReported = true; |
| errorReporter?.atEntity( |
| errorEntity!, |
| CompileTimeErrorCode.COULD_NOT_INFER, |
| arguments: [ |
| parameter.name, |
| _formatError(parameter, inferred, constraints) |
| ], |
| ); |
| |
| // Heuristic: even if we failed, keep the erroneous type. |
| // It should satisfy at least some of the constraints (e.g. the return |
| // context). If we fall back to instantiateToBounds, we'll typically get |
| // more errors (e.g. because `dynamic` is the most common bound). |
| } |
| |
| if (inferred is FunctionType && |
| inferred.typeFormals.isNotEmpty && |
| !genericMetadataIsEnabled && |
| errorReporter != null) { |
| if (failAtError) { |
| inferenceLogWriter?.exitGenericInference(failed: true); |
| return null; |
| } |
| hasErrorReported = true; |
| var typeFormals = inferred.typeFormals; |
| var typeFormalsStr = typeFormals.map(_elementStr).join(', '); |
| errorReporter!.atEntity( |
| errorEntity!, |
| CompileTimeErrorCode.COULD_NOT_INFER, |
| arguments: [ |
| parameter.name, |
| ' Inferred candidate type ${_typeStr(inferred)} has type parameters' |
| ' [$typeFormalsStr], but a function with' |
| ' type parameters cannot be used as a type argument.' |
| ], |
| ); |
| } |
| |
| if (UnknownInferredType.isKnown(inferred)) { |
| knownTypes[parameter] = inferred; |
| } else if (_strictInference) { |
| // [typeParam] could not be inferred. A result will still be returned |
| // by [infer], with [typeParam] filled in as its bounds. This is |
| // considered a failure of inference, under the "strict-inference" |
| // mode. |
| _reportInferenceFailure( |
| errorReporter: errorReporter, |
| errorEntity: errorEntity, |
| genericMetadataIsEnabled: genericMetadataIsEnabled, |
| ); |
| } |
| } |
| |
| // Use instantiate to bounds to finish things off. |
| var hasError = List<bool>.filled(_typeFormals.length, false); |
| var result = _typeSystem.instantiateTypeFormalsToBounds(_typeFormals, |
| hasError: hasError, knownTypes: knownTypes); |
| |
| // Report any errors from instantiateToBounds. |
| for (int i = 0; i < hasError.length; i++) { |
| if (hasError[i]) { |
| if (failAtError) { |
| inferenceLogWriter?.exitGenericInference(failed: true); |
| return null; |
| } |
| hasErrorReported = true; |
| TypeParameterElement typeParam = _typeFormals[i]; |
| var typeParamBound = Substitution.fromPairs(_typeFormals, inferredTypes) |
| .substituteType(typeParam.bound ?? typeProvider.objectType); |
| // TODO(jmesserly): improve this error message. |
| errorReporter?.atEntity( |
| errorEntity!, |
| CompileTimeErrorCode.COULD_NOT_INFER, |
| arguments: [ |
| typeParam.name, |
| "\nRecursive bound cannot be instantiated: '$typeParamBound'." |
| "\nConsider passing explicit type argument(s) " |
| "to the generic.\n\n'" |
| ], |
| ); |
| } |
| } |
| |
| if (!hasErrorReported) { |
| _checkArgumentsNotMatchingBounds( |
| errorEntity: errorEntity, |
| errorReporter: errorReporter, |
| typeArguments: result, |
| ); |
| } |
| |
| _demoteTypes(result); |
| inferenceLogWriter?.exitGenericInference(finalTypes: result); |
| return result; |
| } |
| |
| /// Check that inferred [typeArguments] satisfy the [typeParameters] bounds. |
| void _checkArgumentsNotMatchingBounds({ |
| required SyntacticEntity? errorEntity, |
| required ErrorReporter? errorReporter, |
| required List<DartType> typeArguments, |
| }) { |
| for (int i = 0; i < _typeFormals.length; i++) { |
| var parameter = _typeFormals[i]; |
| var argument = typeArguments[i]; |
| |
| var rawBound = parameter.bound; |
| if (rawBound == null) { |
| continue; |
| } |
| |
| var substitution = Substitution.fromPairs(_typeFormals, typeArguments); |
| var bound = substitution.substituteType(rawBound); |
| if (!_typeSystem.isSubtypeOf(argument, bound)) { |
| errorReporter?.atEntity( |
| errorEntity!, |
| CompileTimeErrorCode.COULD_NOT_INFER, |
| arguments: [ |
| parameter.name, |
| "\n'${_typeStr(argument)}' doesn't conform to " |
| "the bound '${_typeStr(bound)}'" |
| ", instantiated from '${_typeStr(rawBound)}'" |
| " using type arguments ${typeArguments.map(_typeStr).toList()}.", |
| ], |
| ); |
| } |
| } |
| } |
| |
| /// Choose the bound that was implied by the return type, if any. |
| /// |
| /// Which bound this is depends on what positions the type parameter |
| /// appears in. If the type only appears only in a contravariant position, |
| /// we will choose the lower bound instead. |
| /// |
| /// For example given: |
| /// |
| /// Func1<T, bool> makeComparer<T>(T x) => (T y) => x() == y; |
| /// |
| /// main() { |
| /// Func1<num, bool> t = makeComparer/* infer <num> */(42); |
| /// print(t(42.0)); /// false, no error. |
| /// } |
| /// |
| /// The constraints we collect are: |
| /// |
| /// * `num <: T` |
| /// * `int <: T` |
| /// |
| /// ... and no upper bound. Therefore the lower bound is the best choice. |
| /// |
| /// If [isContravariant] is `true`, then we are solving for a contravariant |
| /// type parameter which means we choose the upper bound rather than the |
| /// lower bound for normally covariant type parameters. |
| DartType _chooseTypeFromConstraints( |
| Iterable< |
| MergedTypeConstraint<DartType, TypeParameterElement, |
| PromotableElement, InterfaceType, InterfaceElement>> |
| constraints, |
| {bool toKnownType = false, |
| required bool isContravariant}) { |
| DartType lower = UnknownInferredType.instance; |
| DartType upper = UnknownInferredType.instance; |
| for (var constraint in constraints) { |
| // Given constraints: |
| // |
| // L1 <: T <: U1 |
| // L2 <: T <: U2 |
| // |
| // These can be combined to produce: |
| // |
| // LUB(L1, L2) <: T <: GLB(U1, U2). |
| // |
| // This can then be done for all constraints in sequence. |
| // |
| // This resulting constraint may be unsatisfiable; in that case inference |
| // will fail. |
| upper = _typeSystem.greatestLowerBound( |
| upper, constraint.upper.unwrapTypeSchemaView()); |
| lower = _typeSystem.leastUpperBound( |
| lower, constraint.lower.unwrapTypeSchemaView()); |
| } |
| |
| // Prefer the known bound, if any. |
| // Otherwise take whatever bound has partial information, e.g. `Iterable<?>` |
| // |
| // For both of those, prefer the lower bound (arbitrary heuristic) or upper |
| // bound if [isContravariant] is `true` |
| if (isContravariant) { |
| if (UnknownInferredType.isKnown(upper)) { |
| return upper; |
| } |
| if (UnknownInferredType.isKnown(lower)) { |
| return lower; |
| } |
| if (!identical(UnknownInferredType.instance, upper)) { |
| return toKnownType ? _typeSystem.greatestClosureOfSchema(upper) : upper; |
| } |
| if (!identical(UnknownInferredType.instance, lower)) { |
| return toKnownType ? _typeSystem.leastClosureOfSchema(lower) : lower; |
| } |
| return upper; |
| } else { |
| if (UnknownInferredType.isKnown(lower)) { |
| return lower; |
| } |
| if (UnknownInferredType.isKnown(upper)) { |
| return upper; |
| } |
| if (!identical(UnknownInferredType.instance, lower)) { |
| return toKnownType ? _typeSystem.leastClosureOfSchema(lower) : lower; |
| } |
| if (!identical(UnknownInferredType.instance, upper)) { |
| return toKnownType ? _typeSystem.greatestClosureOfSchema(upper) : upper; |
| } |
| return lower; |
| } |
| } |
| |
| /// Computes (or recomputes) a set of [inferredTypes] based on the constraints |
| /// that have been recorded so far. |
| List<DartType> _chooseTypes({required bool preliminary}) { |
| var inferredTypes = List<DartType>.filled( |
| _typeFormals.length, UnknownInferredType.instance); |
| for (int i = 0; i < _typeFormals.length; i++) { |
| // TODO(kallentu): : Clean up TypeParameterElementImpl casting once |
| // variance is added to the interface. |
| var typeParam = _typeFormals[i] as TypeParameterElementImpl; |
| MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement, |
| InterfaceType, InterfaceElement>? extendsClause; |
| var bound = typeParam.bound; |
| if (bound != null) { |
| extendsClause = MergedTypeConstraint<DartType, TypeParameterElement, |
| PromotableElement, InterfaceType, InterfaceElement>.fromExtends( |
| typeParameterName: typeParam.name, |
| boundType: SharedTypeView(bound), |
| extendsType: SharedTypeView( |
| Substitution.fromPairs(_typeFormals, inferredTypes) |
| .substituteType(bound)), |
| typeAnalyzerOperations: _typeSystemOperations, |
| ); |
| } |
| |
| var constraints = _constraints[typeParam]!; |
| var previouslyInferredType = _typesInferredSoFar[typeParam]; |
| if (previouslyInferredType != null) { |
| inferredTypes[i] = previouslyInferredType; |
| } else if (preliminary) { |
| var inferredType = _inferTypeParameterFromContext( |
| constraints, extendsClause, |
| isContravariant: typeParam.variance.isContravariant); |
| inferredTypes[i] = inferredType; |
| if (typeParam.isLegacyCovariant && |
| UnknownInferredType.isKnown(inferredType)) { |
| _typesInferredSoFar[typeParam] = inferredType; |
| } |
| } else { |
| inferredTypes[i] = _inferTypeParameterFromAll( |
| constraints, extendsClause, |
| isContravariant: typeParam.variance.isContravariant); |
| } |
| } |
| |
| return inferredTypes; |
| } |
| |
| void _demoteTypes(List<DartType> types) { |
| for (var i = 0; i < types.length; i++) { |
| types[i] = _typeSystem.demoteType(types[i]); |
| } |
| } |
| |
| String _elementStr(Element element) { |
| return element.getDisplayString(); |
| } |
| |
| String _formatError( |
| TypeParameterElement typeParam, |
| DartType inferred, |
| Iterable< |
| MergedTypeConstraint<DartType, TypeParameterElement, |
| PromotableElement, InterfaceType, InterfaceElement>> |
| constraints) { |
| var inferredStr = inferred.getDisplayString(); |
| var intro = "Tried to infer '$inferredStr' for '${typeParam.name}'" |
| " which doesn't work:"; |
| |
| var constraintsByOrigin = <TypeConstraintOrigin<DartType, PromotableElement, |
| TypeParameterElement, InterfaceType, InterfaceElement>, |
| List< |
| MergedTypeConstraint<DartType, TypeParameterElement, |
| PromotableElement, InterfaceType, InterfaceElement>>>{}; |
| for (var c in constraints) { |
| constraintsByOrigin.putIfAbsent(c.origin, () => []).add(c); |
| } |
| |
| // Only report unique constraint origins. |
| Iterable< |
| MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement, |
| InterfaceType, InterfaceElement>> isSatisfied(bool expected) => |
| constraintsByOrigin.values |
| .where((l) => |
| l.every((c) => c.isSatisfiedBy( |
| SharedTypeView(inferred), _typeSystemOperations)) == |
| expected) |
| .flattenedToList; |
| |
| String unsatisfied = |
| _formatConstraints(isSatisfied(false), _typeSystemOperations); |
| String satisfied = |
| _formatConstraints(isSatisfied(true), _typeSystemOperations); |
| |
| assert(unsatisfied.isNotEmpty); |
| if (satisfied.isNotEmpty) { |
| satisfied = "\nThe type '$inferredStr' was inferred from:\n$satisfied"; |
| } |
| |
| return '\n\n$intro\n$unsatisfied$satisfied\n\n' |
| 'Consider passing explicit type argument(s) to the generic.\n\n'; |
| } |
| |
| DartType _inferTypeParameterFromAll( |
| List< |
| MergedTypeConstraint<DartType, TypeParameterElement, |
| PromotableElement, InterfaceType, InterfaceElement>> |
| constraints, |
| MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement, |
| InterfaceType, InterfaceElement>? |
| extendsClause, |
| {required bool isContravariant}) { |
| if (extendsClause != null) { |
| constraints = constraints.toList()..add(extendsClause); |
| } |
| |
| var choice = _chooseTypeFromConstraints(constraints, |
| toKnownType: true, isContravariant: isContravariant); |
| return choice; |
| } |
| |
| DartType _inferTypeParameterFromContext( |
| Iterable< |
| MergedTypeConstraint<DartType, TypeParameterElement, |
| PromotableElement, InterfaceType, InterfaceElement>> |
| constraints, |
| MergedTypeConstraint<DartType, TypeParameterElement, PromotableElement, |
| InterfaceType, InterfaceElement>? |
| extendsClause, |
| {required bool isContravariant}) { |
| DartType t = _chooseTypeFromConstraints(constraints, |
| isContravariant: isContravariant); |
| if (UnknownInferredType.isUnknown(t)) { |
| return t; |
| } |
| |
| // If we're about to make our final choice, apply the extends clause. |
| // This gives us a chance to refine the choice, in case it would violate |
| // the `extends` clause. For example: |
| // |
| // Object obj = math.min/*<infer Object, error>*/(1, 2); |
| // |
| // If we consider the `T extends num` we conclude `<num>`, which works. |
| if (extendsClause != null) { |
| constraints = constraints.toList()..add(extendsClause); |
| return _chooseTypeFromConstraints(constraints, |
| isContravariant: isContravariant); |
| } |
| return t; |
| } |
| |
| /// Reports an inference failure on [errorEntity] according to its type. |
| void _reportInferenceFailure({ |
| ErrorReporter? errorReporter, |
| SyntacticEntity? errorEntity, |
| required bool genericMetadataIsEnabled, |
| }) { |
| if (errorReporter == null || errorEntity == null) { |
| return; |
| } |
| if (errorEntity is AstNode && |
| errorEntity.parent is InvocationExpression && |
| errorEntity.parent?.parent is AsExpression) { |
| // Casts via `as` do not play a part in downward inference. We allow an |
| // exception when inference has "failed" but the return value is |
| // immediately cast with `as`. |
| return; |
| } |
| if (errorEntity is ConstructorName && |
| !(errorEntity.type.type as InterfaceType).element.hasOptionalTypeArgs) { |
| String constructorName = errorEntity.name == null |
| ? errorEntity.type.qualifiedName |
| : '${errorEntity.type}.${errorEntity.name}'; |
| errorReporter.atNode( |
| errorEntity, |
| WarningCode.INFERENCE_FAILURE_ON_INSTANCE_CREATION, |
| arguments: [constructorName], |
| ); |
| } else if (errorEntity is Annotation) { |
| if (genericMetadataIsEnabled) { |
| // Only report an error if generic metadata is valid syntax. |
| var element = errorEntity.name.staticElement; |
| if (element != null && !element.hasOptionalTypeArgs) { |
| String constructorName = errorEntity.constructorName == null |
| ? errorEntity.name.name |
| : '${errorEntity.name.name}.${errorEntity.constructorName}'; |
| errorReporter.atNode( |
| errorEntity, |
| WarningCode.INFERENCE_FAILURE_ON_INSTANCE_CREATION, |
| arguments: [constructorName], |
| ); |
| } |
| } |
| } else if (errorEntity is SimpleIdentifier) { |
| var element = errorEntity.staticElement; |
| if (element != null) { |
| if (element is VariableElement) { |
| // For variable elements, we check their type and possible alias type. |
| var type = element.type; |
| var typeElement = type is InterfaceType ? type.element : null; |
| if (typeElement != null && typeElement.hasOptionalTypeArgs) { |
| return; |
| } |
| var typeAliasElement = type.alias?.element; |
| if (typeAliasElement != null && |
| typeAliasElement.hasOptionalTypeArgs) { |
| return; |
| } |
| } |
| if (!element.hasOptionalTypeArgs) { |
| errorReporter.atNode( |
| errorEntity, |
| WarningCode.INFERENCE_FAILURE_ON_FUNCTION_INVOCATION, |
| arguments: [errorEntity.name], |
| ); |
| return; |
| } |
| } |
| } else if (errorEntity is Expression) { |
| var type = errorEntity.staticType; |
| if (type != null) { |
| var typeDisplayString = _typeStr(type); |
| errorReporter.atNode( |
| errorEntity, |
| WarningCode.INFERENCE_FAILURE_ON_GENERIC_INVOCATION, |
| arguments: [typeDisplayString], |
| ); |
| return; |
| } |
| } |
| } |
| |
| /// Tries to make [i1] a subtype of [i2] and accumulate constraints as needed. |
| /// |
| /// The return value indicates whether the match was successful. If it was |
| /// unsuccessful, any constraints that were accumulated during the match |
| /// attempt have been rewound (see [_rewindConstraints]). |
| bool _tryMatchSubtypeOf( |
| DartType t1, |
| DartType t2, |
| TypeConstraintOrigin<DartType, PromotableElement, TypeParameterElement, |
| InterfaceType, InterfaceElement> |
| origin, |
| {required bool covariant, |
| required AstNode? nodeForTesting}) { |
| var gatherer = TypeConstraintGatherer( |
| typeSystem: _typeSystem, |
| typeParameters: _typeParameters, |
| typeSystemOperations: _typeSystemOperations, |
| dataForTesting: dataForTesting); |
| var success = gatherer.trySubtypeMatch(t1, t2, !covariant, |
| nodeForTesting: nodeForTesting); |
| if (success) { |
| var constraints = gatherer.computeConstraints(); |
| for (var entry in constraints.entries) { |
| if (!entry.value.isEmpty(_typeSystemOperations) && |
| !_typesInferredSoFar.containsKey(entry.key)) { |
| var constraint = _constraints[entry.key]!; |
| constraint.add(entry.value..origin = origin); |
| inferenceLogWriter?.recordGeneratedConstraint(entry.key, entry.value); |
| } |
| } |
| } |
| |
| return success; |
| } |
| |
| String _typeStr(DartType type) { |
| return type.getDisplayString(); |
| } |
| |
| static String _formatConstraints( |
| Iterable< |
| MergedTypeConstraint<DartType, TypeParameterElement, |
| PromotableElement, InterfaceType, InterfaceElement>> |
| constraints, |
| TypeSystemOperations typeSystemOperations) { |
| List<List<String>> lineParts = Set< |
| TypeConstraintOrigin< |
| DartType, |
| PromotableElement, |
| TypeParameterElement, |
| InterfaceType, |
| InterfaceElement>>.from(constraints.map((c) => c.origin)) |
| .map((o) => o.formatError(typeSystemOperations)) |
| .toList(); |
| |
| int prefixMax = lineParts.map((p) => p[0].length).fold(0, math.max); |
| |
| // Use a set to prevent identical message lines. |
| // (It's not uncommon for the same constraint to show up in a few places.) |
| var messageLines = Set<String>.from(lineParts.map((parts) { |
| var prefix = parts[0]; |
| var middle = parts[1]; |
| var prefixPad = ' ' * (prefixMax - prefix.length); |
| var middlePad = ' ' * prefixMax; |
| var end = ""; |
| if (parts.length > 2) { |
| end = '\n $middlePad ${parts[2]}'; |
| } |
| return ' $prefix$prefixPad $middle$end'; |
| })); |
| |
| return messageLines.join('\n'); |
| } |
| } |