| // 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/types/shared_type.dart'; |
| 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/ast.dart'; |
| 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. |
| /// |
| /// When the methods of this class encounter 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, `chooseFinalTypes` 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<TypeParameterElementImpl2> _typeParameters = Set.identity(); |
| final Map<TypeParameterElementImpl2, List<MergedTypeConstraint>> |
| _constraints = {}; |
| |
| /// The list of type parameters being inferred. |
| final List<TypeParameterElementImpl2> _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; |
| |
| /// Indicates whether the "inference using bounds" feature is enabled. When it |
| /// is, the bounds of type parameters will be used more extensively when |
| /// computing the solutions after each of the inference phases. |
| final bool inferenceUsingBoundsIsEnabled; |
| |
| 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<TypeParameterElementImpl2, TypeImpl> _typesInferredSoFar = {}; |
| |
| final TypeSystemOperations _typeSystemOperations; |
| |
| final TypeConstraintGenerationDataForTesting? dataForTesting; |
| |
| GenericInferrer( |
| this._typeSystem, |
| List<TypeParameterElement> typeFormals, { |
| this.errorReporter, |
| this.errorEntity, |
| required this.genericMetadataIsEnabled, |
| required this.inferenceUsingBoundsIsEnabled, |
| required bool strictInference, |
| required TypeSystemOperations typeSystemOperations, |
| required this.dataForTesting, |
| }) |
| // TODO(paulberry): make this cast unnecessary by changing `typeFormals` |
| // to `List<TypeParameterElementImpl2>`. |
| : _typeFormals = typeFormals.cast(), |
| _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<TypeImpl> 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<TypeImpl> choosePreliminaryTypes() { |
| var types = _chooseTypes(preliminary: true); |
| inferenceLogWriter?.recordPreliminaryTypes(types); |
| return types; |
| } |
| |
| /// Apply an argument constraint, which asserts that the [argumentType] static |
| /// type is a subtype of the [parameterType]. |
| void constrainArgument( |
| TypeImpl argumentType, |
| TypeImpl parameterType, |
| String parameterName, { |
| InterfaceFragmentImpl? genericClass, |
| required AstNodeImpl? nodeForTesting, |
| }) { |
| var origin = TypeConstraintFromArgument( |
| 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({ |
| InterfaceFragmentImpl? genericClass, |
| required List<ParameterElementMixin> parameters, |
| required List<TypeImpl> argumentTypes, |
| required AstNodeImpl? 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, |
| ); |
| } |
| } |
| |
| /// Applies all the argument constraints implied by [parameters] and |
| /// [argumentTypes]. |
| void constrainArguments2({ |
| InterfaceFragmentImpl? genericClass, |
| required List<FormalParameterElementMixin> parameters, |
| required List<TypeImpl> argumentTypes, |
| required AstNodeImpl? 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].name3!, |
| genericClass: genericClass, |
| nodeForTesting: nodeForTesting, |
| ); |
| } |
| } |
| |
| /// Constrain a universal function type [fnType] used in a context |
| /// [contextType]. |
| void constrainGenericFunctionInContext( |
| FunctionTypeImpl fnType, |
| TypeImpl contextType, { |
| required AstNodeImpl? nodeForTesting, |
| }) { |
| var origin = TypeConstraintFromFunctionContext( |
| 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( |
| TypeImpl declaredType, |
| TypeImpl contextType, { |
| required AstNodeImpl? nodeForTesting, |
| }) { |
| var origin = TypeConstraintFromReturnType( |
| 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<TypeImpl>? tryChooseFinalTypes({bool failAtError = true}) { |
| var inferredTypes = _chooseTypes(preliminary: false); |
| // Check the inferred types against all of the constraints. |
| var knownTypes = <TypeParameterElement, TypeImpl>{}; |
| var hasErrorReported = false; |
| for (int i = 0; i < _typeFormals.length; i++) { |
| TypeParameterElementImpl2 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 name = parameter.name3; |
| var parameterBoundRaw = parameter.bound; |
| if (name != null && parameterBoundRaw != null) { |
| var parameterBound = Substitution.fromPairs2( |
| _typeFormals, |
| inferredTypes, |
| ).substituteType(parameterBoundRaw); |
| var extendsConstraint = MergedTypeConstraint.fromExtends( |
| typeParameterName: 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; |
| |
| var name = parameter.name3; |
| if (name == null) { |
| return null; |
| } |
| |
| errorReporter?.atEntity( |
| errorEntity!, |
| CompileTimeErrorCode.COULD_NOT_INFER, |
| arguments: [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 FunctionTypeImpl && |
| inferred.typeFormals.isNotEmpty && |
| !genericMetadataIsEnabled && |
| errorReporter != null) { |
| if (failAtError) { |
| inferenceLogWriter?.exitGenericInference(failed: true); |
| return null; |
| } |
| hasErrorReported = true; |
| |
| var name = parameter.name3; |
| if (name == null) { |
| return null; |
| } |
| |
| var typeFormals = inferred.typeFormals; |
| var typeFormalsStr = typeFormals.map(_elementStr).join(', '); |
| errorReporter!.atEntity( |
| errorEntity!, |
| CompileTimeErrorCode.COULD_NOT_INFER, |
| arguments: [ |
| name, |
| ' Inferred candidate type ${_typeStr(inferred)} has type parameters' |
| ' [$typeFormalsStr], but a function with' |
| ' type parameters cannot be used as a type argument.', |
| ], |
| ); |
| } |
| |
| if (_typeSystemOperations.isKnownType(SharedTypeSchemaView(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.instantiateTypeFormalsToBounds2( |
| _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; |
| TypeParameterElementImpl2 typeParam = _typeFormals[i]; |
| |
| var name = typeParam.name3; |
| if (name == null) { |
| return null; |
| } |
| |
| var typeParamBound = Substitution.fromPairs2( |
| _typeFormals, |
| inferredTypes, |
| ).substituteType(typeParam.bound ?? typeProvider.objectType); |
| // TODO(jmesserly): improve this error message. |
| errorReporter?.atEntity( |
| errorEntity!, |
| CompileTimeErrorCode.COULD_NOT_INFER, |
| arguments: [ |
| 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<TypeImpl> 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 name = parameter.name3; |
| if (name == null) { |
| continue; |
| } |
| |
| var substitution = Substitution.fromPairs2( |
| _typeFormals.map((e) => e).toList(), |
| typeArguments, |
| ); |
| var bound = substitution.substituteType(rawBound); |
| if (!_typeSystem.isSubtypeOf(argument, bound)) { |
| errorReporter?.atEntity( |
| errorEntity!, |
| CompileTimeErrorCode.COULD_NOT_INFER, |
| arguments: [ |
| 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. |
| TypeImpl _chooseTypeFromConstraint( |
| MergedTypeConstraint constraint, { |
| bool toKnownType = false, |
| required bool isContravariant, |
| }) { |
| TypeImpl upper = constraint.upper.unwrapTypeSchemaView(); |
| TypeImpl 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 (_typeSystemOperations.isKnownType(SharedTypeSchemaView(upper))) { |
| return upper; |
| } |
| if (_typeSystemOperations.isKnownType(SharedTypeSchemaView(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 (_typeSystemOperations.isKnownType(SharedTypeSchemaView(lower))) { |
| return lower; |
| } |
| if (_typeSystemOperations.isKnownType(SharedTypeSchemaView(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 inferred types based on the constraints |
| /// that have been recorded so far. |
| List<TypeImpl> _chooseTypes({required bool preliminary}) { |
| var inferredTypes = List<TypeImpl>.filled( |
| _typeFormals.length, |
| UnknownInferredType.instance, |
| ); |
| var inferencePhaseConstraints = { |
| for (var typeParameter in _constraints.keys) |
| typeParameter: _squashConstraints(_constraints[typeParameter]!), |
| }; |
| 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]; |
| MergedTypeConstraint? extendsClause; |
| var name = typeParam.name3; |
| var bound = typeParam.bound; |
| if (name != null && bound != null) { |
| extendsClause = MergedTypeConstraint.fromExtends( |
| typeParameterName: name, |
| boundType: SharedTypeView(bound), |
| extendsType: SharedTypeView( |
| Substitution.fromPairs2( |
| _typeFormals, |
| inferredTypes, |
| ).substituteType(bound), |
| ), |
| typeAnalyzerOperations: _typeSystemOperations, |
| ); |
| } |
| |
| var constraint = inferencePhaseConstraints[typeParam]!; |
| var previouslyInferredType = _typesInferredSoFar[typeParam]; |
| if (previouslyInferredType != null) { |
| inferredTypes[i] = previouslyInferredType; |
| } else if (preliminary) { |
| var inferredType = _inferTypeParameterFromContext( |
| constraint, |
| extendsClause, |
| isContravariant: typeParam.variance.isContravariant, |
| typeParameterToInfer: typeParam, |
| inferencePhaseConstraints: inferencePhaseConstraints, |
| ); |
| |
| inferredTypes[i] = inferredType; |
| if (typeParam.isLegacyCovariant && |
| _typeSystemOperations.isKnownType( |
| SharedTypeSchemaView(inferredType), |
| )) { |
| _typesInferredSoFar[typeParam] = inferredType; |
| } |
| } else { |
| inferredTypes[i] = _inferTypeParameterFromAll( |
| constraint, |
| extendsClause, |
| isContravariant: typeParam.variance.isContravariant, |
| typeParameterToInfer: typeParam, |
| inferencePhaseConstraints: inferencePhaseConstraints, |
| ); |
| } |
| } |
| |
| return inferredTypes; |
| } |
| |
| void _demoteTypes(List<TypeImpl> types) { |
| for (var i = 0; i < types.length; i++) { |
| types[i] = _typeSystem.demoteType(types[i]); |
| } |
| } |
| |
| String _elementStr(FragmentImpl element) { |
| return element.getDisplayString(); |
| } |
| |
| String _formatError( |
| TypeParameterElementImpl2 typeParam, |
| TypeImpl inferred, |
| Iterable<MergedTypeConstraint> constraints, |
| ) { |
| var inferredStr = inferred.getDisplayString(); |
| var intro = |
| "Tried to infer '$inferredStr' for '${typeParam.name3}'" |
| " which doesn't work:"; |
| |
| var constraintsByOrigin = |
| <TypeConstraintOrigin, List<MergedTypeConstraint>>{}; |
| for (var c in constraints) { |
| constraintsByOrigin.putIfAbsent(c.origin, () => []).add(c); |
| } |
| |
| // Only report unique constraint origins. |
| Iterable<MergedTypeConstraint> 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'; |
| } |
| |
| TypeImpl _inferTypeParameterFromAll( |
| MergedTypeConstraint constraint, |
| MergedTypeConstraint? extendsClause, { |
| required bool isContravariant, |
| required TypeParameterElementImpl2 typeParameterToInfer, |
| required Map<TypeParameterElementImpl2, MergedTypeConstraint> |
| inferencePhaseConstraints, |
| }) { |
| if (extendsClause != null) { |
| MergedTypeConstraint? boundConstraint; |
| if (inferenceUsingBoundsIsEnabled) { |
| if (!identical( |
| constraint.lower.unwrapTypeSchemaView(), |
| UnknownInferredType.instance, |
| )) { |
| boundConstraint = _typeSystemOperations.mergeInConstraintsFromBound( |
| typeParameterToInfer: typeParameterToInfer, |
| typeParametersToInfer: _typeFormals.cast<SharedTypeParameterView>(), |
| lower: constraint.lower.unwrapTypeSchemaView(), |
| inferencePhaseConstraints: inferencePhaseConstraints, |
| dataForTesting: dataForTesting, |
| inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled, |
| ); |
| } |
| } |
| |
| constraint = _squashConstraints([ |
| constraint, |
| extendsClause, |
| if (boundConstraint != null && |
| !boundConstraint.isEmpty(_typeSystemOperations)) |
| boundConstraint, |
| ]); |
| } |
| |
| var choice = _chooseTypeFromConstraint( |
| constraint, |
| toKnownType: true, |
| isContravariant: isContravariant, |
| ); |
| return choice; |
| } |
| |
| TypeImpl _inferTypeParameterFromContext( |
| MergedTypeConstraint constraint, |
| MergedTypeConstraint? extendsClause, { |
| required bool isContravariant, |
| required TypeParameterElementImpl2 typeParameterToInfer, |
| required Map<TypeParameterElementImpl2, MergedTypeConstraint> |
| inferencePhaseConstraints, |
| }) { |
| // Both bits of the bound information should be available at the same time. |
| assert(extendsClause == null || typeParameterToInfer.bound != null); |
| |
| TypeImpl t = _chooseTypeFromConstraint( |
| constraint, |
| isContravariant: isContravariant, |
| ); |
| if (!_typeSystemOperations.isKnownType(SharedTypeSchemaView(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) { |
| MergedTypeConstraint? boundConstraint; |
| if (inferenceUsingBoundsIsEnabled) { |
| if (!identical( |
| constraint.lower.unwrapTypeSchemaView(), |
| UnknownInferredType.instance, |
| )) { |
| boundConstraint = _typeSystemOperations.mergeInConstraintsFromBound( |
| typeParameterToInfer: typeParameterToInfer, |
| typeParametersToInfer: _typeFormals.cast<SharedTypeParameterView>(), |
| lower: constraint.lower.unwrapTypeSchemaView(), |
| inferencePhaseConstraints: inferencePhaseConstraints, |
| dataForTesting: dataForTesting, |
| inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled, |
| ); |
| } |
| } |
| |
| constraint = _squashConstraints([ |
| constraint, |
| extendsClause, |
| if (boundConstraint != null && |
| !boundConstraint.isEmpty(_typeSystemOperations)) |
| boundConstraint, |
| ]); |
| return _chooseTypeFromConstraint( |
| constraint, |
| 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) |
| .element3 |
| .metadata2 |
| .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.element; |
| 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.element; |
| 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.element3 : null; |
| if (typeElement != null && |
| typeElement.metadata2.hasOptionalTypeArgs) { |
| return; |
| } |
| var typeAliasElement = type.alias?.element2; |
| if (typeAliasElement != null && |
| typeAliasElement.metadata2.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; |
| } |
| } |
| } |
| |
| MergedTypeConstraint _squashConstraints( |
| Iterable<MergedTypeConstraint> constraints, |
| ) { |
| TypeImpl lower = UnknownInferredType.instance; |
| TypeImpl upper = UnknownInferredType.instance; |
| TypeConstraintOrigin origin = UnknownTypeConstraintOrigin(); |
| |
| 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(), |
| ); |
| } |
| return MergedTypeConstraint( |
| lower: SharedTypeSchemaView(lower), |
| upper: SharedTypeSchemaView(upper), |
| origin: origin, |
| ); |
| } |
| |
| /// Tries to make [t1] a subtype of [t2] 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. |
| bool _tryMatchSubtypeOf( |
| TypeImpl t1, |
| TypeImpl t2, |
| TypeConstraintOrigin origin, { |
| required bool covariant, |
| required AstNodeImpl? nodeForTesting, |
| }) { |
| var gatherer = TypeConstraintGatherer( |
| typeParameters: _typeParameters, |
| typeSystemOperations: _typeSystemOperations, |
| inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled, |
| dataForTesting: dataForTesting, |
| ); |
| var success = gatherer.performSubtypeConstraintGenerationInternal( |
| t1, |
| t2, |
| leftSchema: !covariant, |
| astNodeForTesting: 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> constraints, |
| TypeSystemOperations typeSystemOperations, |
| ) { |
| List<List<String>> lineParts = |
| Set<TypeConstraintOrigin>.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'); |
| } |
| } |
| |
| extension on Element { |
| bool get hasOptionalTypeArgs { |
| if (this case Annotatable annotatable) { |
| return annotatable.metadata2.hasOptionalTypeArgs; |
| } |
| return false; |
| } |
| } |