blob: 6fe73d0616f9fae4e779edf22240e71b35cfb6fe [file] [log] [blame]
// 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;
}
}