| // Copyright (c) 2017, 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.md file. |
| |
| import 'package:front_end/src/base/instrumentation.dart'; |
| import 'package:front_end/src/fasta/kernel/kernel_shadow_ast.dart'; |
| import 'package:front_end/src/fasta/type_inference/type_inference_engine.dart'; |
| import 'package:front_end/src/fasta/type_inference/type_inference_listener.dart'; |
| import 'package:front_end/src/fasta/type_inference/type_promotion.dart'; |
| import 'package:front_end/src/fasta/type_inference/type_schema.dart'; |
| import 'package:front_end/src/fasta/type_inference/type_schema_environment.dart'; |
| import 'package:kernel/ast.dart' |
| show |
| Arguments, |
| BottomType, |
| Class, |
| DartType, |
| DynamicType, |
| Expression, |
| Field, |
| FunctionType, |
| InterfaceType, |
| Member, |
| Name, |
| Procedure, |
| Statement, |
| TypeParameterType, |
| VariableDeclaration; |
| import 'package:kernel/class_hierarchy.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/type_algebra.dart'; |
| |
| /// Keeps track of information about the innermost function or closure being |
| /// inferred. |
| class ClosureContext { |
| final bool isAsync; |
| |
| final bool isGenerator; |
| |
| final DartType returnContext; |
| |
| DartType _inferredReturnType; |
| |
| ClosureContext(this.isAsync, this.isGenerator, this.returnContext); |
| |
| /// Gets the return type that was inferred for the current closure, or `null` |
| /// if there were no `return` statements. |
| get inferredReturnType { |
| return _inferredReturnType; |
| } |
| |
| /// Updates the inferred return type based on the presence of a return |
| /// statement returning the given [type]. |
| void updateInferredReturnType(TypeInferrerImpl inferrer, DartType type) { |
| if (_inferredReturnType == null) { |
| _inferredReturnType = type; |
| } else { |
| _inferredReturnType = inferrer.typeSchemaEnvironment |
| .getLeastUpperBound(_inferredReturnType, type); |
| } |
| } |
| } |
| |
| /// Keeps track of the local state for the type inference that occurs during |
| /// compilation of a single method body or top level initializer. |
| /// |
| /// This class describes the interface for use by clients of type inference |
| /// (e.g. BodyBuilder). Derived classes should derive from [TypeInferrerImpl]. |
| abstract class TypeInferrer { |
| /// Gets the [TypePromoter] that can be used to perform type promotion within |
| /// this method body or initializer. |
| TypePromoter<Expression, VariableDeclaration> get typePromoter; |
| |
| /// The URI of the code for which type inference is currently being |
| /// performed--this is used for testing. |
| String get uri; |
| |
| /// Gets the [FieldNode] corresponding to the given [readTarget], if any. |
| FieldNode getFieldNodeForReadTarget(Member readTarget); |
| |
| /// Performs type inference on the given [statement]. |
| /// |
| /// Derived classes should override this method with logic that dispatches on |
| /// the statement type and calls the appropriate specialized "infer" method. |
| void inferStatement(Statement statement); |
| } |
| |
| /// Derived class containing generic implementations of [TypeInferrer]. |
| /// |
| /// This class contains as much of the implementation of type inference as |
| /// possible without knowing the identity of the type parameters. It defers to |
| /// abstract methods for everything else. |
| abstract class TypeInferrerImpl extends TypeInferrer { |
| static final FunctionType _functionReturningDynamic = |
| new FunctionType(const [], const DynamicType()); |
| |
| @override |
| final String uri; |
| |
| /// Indicates whether the construct we are currently performing inference for |
| /// is outside of a method body, and hence top level type inference rules |
| /// should apply. |
| final bool isTopLevel = false; |
| |
| final CoreTypes coreTypes; |
| |
| final bool strongMode; |
| |
| final ClassHierarchy classHierarchy; |
| |
| final Instrumentation instrumentation; |
| |
| final TypeSchemaEnvironment typeSchemaEnvironment; |
| |
| final TypeInferenceListener listener; |
| |
| /// Context information for the current closure, or `null` if we are not |
| /// inside a closure. |
| ClosureContext closureContext; |
| |
| TypeInferrerImpl(TypeInferenceEngineImpl engine, this.uri, this.listener) |
| : coreTypes = engine.coreTypes, |
| strongMode = engine.strongMode, |
| classHierarchy = engine.classHierarchy, |
| instrumentation = engine.instrumentation, |
| typeSchemaEnvironment = engine.typeSchemaEnvironment; |
| |
| /// Gets the type promoter that should be used to promote types during |
| /// inference. |
| TypePromoter<Expression, VariableDeclaration> get typePromoter; |
| |
| FunctionType getCalleeFunctionType(Member interfaceMember, |
| DartType receiverType, Name methodName, int offset) { |
| if (receiverType is InterfaceType) { |
| if (interfaceMember == null) return _functionReturningDynamic; |
| var memberClass = interfaceMember.enclosingClass; |
| if (interfaceMember is Procedure) { |
| instrumentation?.record(Uri.parse(uri), offset, 'target', |
| new InstrumentationValueForProcedure(interfaceMember)); |
| var memberFunctionType = interfaceMember.function.functionType; |
| if (memberClass.typeParameters.isNotEmpty) { |
| var castedType = classHierarchy.getClassAsInstanceOf( |
| receiverType.classNode, memberClass); |
| memberFunctionType = Substitution |
| .fromInterfaceType(Substitution |
| .fromInterfaceType(receiverType) |
| .substituteType(castedType.asInterfaceType)) |
| .substituteType(memberFunctionType); |
| } |
| return memberFunctionType; |
| } else if (interfaceMember is Field) { |
| // TODO(paulberry): handle this case |
| return _functionReturningDynamic; |
| } else { |
| return _functionReturningDynamic; |
| } |
| } else if (receiverType is DynamicType) { |
| return _functionReturningDynamic; |
| } else if (receiverType is FunctionType) { |
| // TODO(paulberry): handle the case of invoking .call() or .toString() on |
| // a function type. |
| return _functionReturningDynamic; |
| } else if (receiverType is TypeParameterType) { |
| // TODO(paulberry): use the bound |
| return _functionReturningDynamic; |
| } else { |
| // TODO(paulberry): handle the case of invoking .toString() on a type |
| // that's none of the above (e.g. `dynamic` or `bottom`) |
| return _functionReturningDynamic; |
| } |
| } |
| |
| /// Gets the initializer for the given [field], or `null` if there is no |
| /// initializer. |
| Expression getFieldInitializer(KernelField field); |
| |
| DartType getNamedParameterType(FunctionType functionType, String name) { |
| return functionType.getNamedParameter(name) ?? const DynamicType(); |
| } |
| |
| DartType getPositionalParameterType(FunctionType functionType, int i) { |
| if (i < functionType.positionalParameters.length) { |
| return functionType.positionalParameters[i]; |
| } else { |
| return const DynamicType(); |
| } |
| } |
| |
| DartType getTypeArgumentOf(DartType type, Class class_) { |
| if (type is InterfaceType && identical(type.classNode, class_)) { |
| return type.typeArguments[0]; |
| } else { |
| return null; |
| } |
| } |
| |
| /// Modifies a type as appropriate when inferring a declared variable's type. |
| DartType inferDeclarationType(DartType initializerType) { |
| if (initializerType is BottomType || |
| (initializerType is InterfaceType && |
| initializerType.classNode == coreTypes.nullClass)) { |
| // If the initializer type is Null or bottom, the inferred type is |
| // dynamic. |
| // TODO(paulberry): this rule is inherited from analyzer behavior but is |
| // not spec'ed anywhere. |
| return const DynamicType(); |
| } |
| return initializerType; |
| } |
| |
| /// Performs type inference on the given [expression]. |
| /// |
| /// [typeContext] is the expected type of the expression, based on surrounding |
| /// code. [typeNeeded] indicates whether it is necessary to compute the |
| /// actual type of the expression. If [typeNeeded] is `true`, the actual type |
| /// of the expression is returned; otherwise `null` is returned. |
| /// |
| /// Derived classes should override this method with logic that dispatches on |
| /// the expression type and calls the appropriate specialized "infer" method. |
| DartType inferExpression( |
| Expression expression, DartType typeContext, bool typeNeeded); |
| |
| /// Performs type inference on the given [field]'s initializer expression. |
| /// |
| /// Derived classes should provide an implementation that calls |
| /// [inferExpression] for the given [field]'s initializer expression. |
| DartType inferFieldInitializer( |
| KernelField field, DartType type, bool typeNeeded); |
| |
| /// Performs the type inference steps that are shared by all kinds of |
| /// invocations (constructors, instance methods, and static methods). |
| DartType inferInvocation(DartType typeContext, bool typeNeeded, int offset, |
| FunctionType calleeType, DartType returnType, Arguments arguments, |
| {bool isOverloadedArithmeticOperator: false, DartType receiverType}) { |
| var calleeTypeParameters = calleeType.typeParameters; |
| List<DartType> explicitTypeArguments = getExplicitTypeArguments(arguments); |
| bool inferenceNeeded = explicitTypeArguments == null && |
| strongMode && |
| calleeTypeParameters.isNotEmpty; |
| List<DartType> inferredTypes; |
| Substitution substitution; |
| List<DartType> formalTypes; |
| List<DartType> actualTypes; |
| if (inferenceNeeded) { |
| inferredTypes = new List<DartType>.filled( |
| calleeTypeParameters.length, const UnknownType()); |
| typeSchemaEnvironment.inferGenericFunctionOrType(returnType, |
| calleeTypeParameters, null, null, typeContext, inferredTypes); |
| substitution = |
| Substitution.fromPairs(calleeTypeParameters, inferredTypes); |
| formalTypes = []; |
| actualTypes = []; |
| } else if (explicitTypeArguments != null && |
| calleeTypeParameters.length == explicitTypeArguments.length) { |
| substitution = |
| Substitution.fromPairs(calleeTypeParameters, explicitTypeArguments); |
| } else if (calleeTypeParameters.length != 0) { |
| substitution = Substitution.fromPairs( |
| calleeTypeParameters, |
| new List<DartType>.filled( |
| calleeTypeParameters.length, const DynamicType())); |
| } |
| int i = 0; |
| _forEachArgument(arguments, (name, expression) { |
| DartType formalType = name != null |
| ? getNamedParameterType(calleeType, name) |
| : getPositionalParameterType(calleeType, i++); |
| DartType inferredFormalType = substitution != null |
| ? substitution.substituteType(formalType) |
| : formalType; |
| var expressionType = inferExpression(expression, inferredFormalType, |
| inferenceNeeded || isOverloadedArithmeticOperator); |
| if (inferenceNeeded) { |
| formalTypes.add(formalType); |
| actualTypes.add(expressionType); |
| } |
| if (isOverloadedArithmeticOperator) { |
| returnType = typeSchemaEnvironment.getTypeOfOverloadedArithmetic( |
| receiverType, expressionType); |
| } |
| }); |
| if (inferenceNeeded) { |
| typeSchemaEnvironment.inferGenericFunctionOrType( |
| returnType, |
| calleeTypeParameters, |
| formalTypes, |
| actualTypes, |
| typeContext, |
| inferredTypes); |
| substitution = |
| Substitution.fromPairs(calleeTypeParameters, inferredTypes); |
| instrumentation?.record(Uri.parse(uri), offset, 'typeArgs', |
| new InstrumentationValueForTypeArgs(inferredTypes)); |
| arguments.types.clear(); |
| arguments.types.addAll(inferredTypes); |
| } |
| DartType inferredType; |
| if (typeNeeded) { |
| inferredType = substitution == null |
| ? returnType |
| : substitution.substituteType(returnType); |
| } |
| return inferredType; |
| } |
| |
| /// Modifies a type as appropriate when inferring a closure return type. |
| DartType inferReturnType(DartType returnType, bool isExpressionFunction) { |
| if (returnType == null) { |
| // Analyzer infers `Null` if there is no `return` expression; the spec |
| // says to return `void`. TODO(paulberry): resolve this difference. |
| return coreTypes.nullClass.rawType; |
| } |
| if (isExpressionFunction && |
| returnType is InterfaceType && |
| identical(returnType.classNode, coreTypes.nullClass)) { |
| // Analyzer coerces `Null` to `dynamic` in expression functions; the spec |
| // doesn't say to do this. TODO(paulberry): resolve this difference. |
| return const DynamicType(); |
| } |
| return returnType; |
| } |
| |
| DartType wrapType(DartType type, Class class_) { |
| return new InterfaceType(class_, <DartType>[type]); |
| } |
| |
| void _forEachArgument( |
| Arguments arguments, void callback(String name, Expression expression)) { |
| for (var expression in arguments.positional) { |
| callback(null, expression); |
| } |
| for (var namedExpression in arguments.named) { |
| callback(namedExpression.name, namedExpression.value); |
| } |
| } |
| } |