Chromium Code Reviews| Index: pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart |
| diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart |
| index 2b86be8b4e764a99563980fc1d8b9bc7fb544965..de57dc6853dce9c5ac785b3f54c184f8223d3fd8 100644 |
| --- a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart |
| +++ b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart |
| @@ -3,8 +3,10 @@ |
| // 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/errors.dart'; |
| import 'package:front_end/src/fasta/kernel/kernel_shadow_ast.dart'; |
| -import 'package:front_end/src/fasta/names.dart' show callName; |
| +import 'package:front_end/src/fasta/names.dart' |
| + show callName, indexGetName, indexSetName; |
| 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'; |
| @@ -24,12 +26,16 @@ import 'package:kernel/ast.dart' |
| FunctionType, |
| Initializer, |
| InterfaceType, |
| + Let, |
| Member, |
| + MethodInvocation, |
| Name, |
| Procedure, |
| ProcedureKind, |
| Statement, |
| TypeParameterType, |
| + VariableDeclaration, |
| + VariableGet, |
| VoidType; |
| import 'package:kernel/class_hierarchy.dart'; |
| import 'package:kernel/core_types.dart'; |
| @@ -236,6 +242,44 @@ abstract class TypeInferrerImpl extends TypeInferrer { |
| /// inference. |
| TypePromoter get typePromoter; |
| + /// Finds a member of [receiverType] called [name], and if it is found, |
| + /// reports it through instrumentation using [fileOffset]. |
| + Member findInterfaceMember(DartType receiverType, Name name, int fileOffset, |
| + {bool setter: false, bool silent: false}) { |
| + // Our non-strong golden files currently don't include interface |
| + // targets, so we can't store the interface target without causing tests |
| + // to fail. TODO(paulberry): fix this. |
|
ahe
2017/06/08 14:16:33
As far as I understand, interface targets are for
Paul Berry
2017/06/08 16:17:04
Acknowledged.
|
| + if (!strongMode) return null; |
| + |
| + if (receiverType is InterfaceType) { |
| + var interfaceMember = classHierarchy |
| + .getInterfaceMember(receiverType.classNode, name, setter: setter); |
| + if (!silent && interfaceMember != null) { |
| + instrumentation?.record(Uri.parse(uri), fileOffset, 'target', |
| + new InstrumentationValueForMember(interfaceMember)); |
| + } |
| + return interfaceMember; |
| + } |
| + return null; |
| + } |
| + |
| + /// Finds a member of [receiverType] called [name], and if it is found, |
| + /// reports it through instrumentation and records it in [methodInvocation]. |
| + Member findMethodInvocationMember( |
| + DartType receiverType, MethodInvocation methodInvocation, |
| + {bool silent: false}) { |
| + var interfaceMember = findInterfaceMember( |
| + receiverType, methodInvocation.name, methodInvocation.fileOffset, |
| + silent: silent); |
| + // interfaceTarget is currently required to be a procedure, so we skip |
| + // if it's anything else. TODO(paulberry): fix this - see |
| + // https://codereview.chromium.org/2923653003/. |
| + if (interfaceMember is Procedure) { |
| + methodInvocation.interfaceTarget = interfaceMember; |
| + } |
| + return interfaceMember; |
| + } |
| + |
| FunctionType getCalleeFunctionType(Member interfaceMember, |
| DartType receiverType, Name methodName, bool followCall) { |
| var type = getCalleeType(interfaceMember, receiverType, methodName); |
| @@ -411,6 +455,155 @@ abstract class TypeInferrerImpl extends TypeInferrer { |
| closureContext = null; |
| } |
| + /// Performs type inference for a (possibly compound) assignment whose LHS is |
| + /// an index expression. |
| + DartType inferIndexAssign( |
| + Expression expression, DartType typeContext, bool typeNeeded) { |
| + typeNeeded = |
| + listener.indexAssignEnter(expression, typeContext) || typeNeeded; |
| + |
| + // Decompose an expression like `a[b] += c` into subexpressions and record |
| + // the variables holding those subexpressions (if any): |
| + // receiver: a |
| + // index: b |
| + // read: a[b] |
| + // rhs: c |
| + // value: a[b] + c |
| + // write: a[b] = a[b] + c |
| + Expression receiver; |
| + VariableDeclaration receiverVariable; |
| + Expression index; |
| + VariableDeclaration indexVariable; |
| + Expression read; |
| + VariableDeclaration readVariable; |
| + Expression rhs; |
| + VariableDeclaration rhsVariable; |
| + Expression value; |
| + VariableDeclaration valueVariable; |
| + MethodInvocation write; |
| + KernelConditionalExpression nullAwareCombiner; |
| + MethodInvocation combiner; |
| + List<VariableDeclaration> syntheticVariables = []; |
| + VariableGet finalValue; |
| + bool isPostIncDec = false; |
| + |
| + // Dig in to the tree to find the expression with the main effect. |
| + Expression e = expression; |
| + while (true) { |
| + if (e is Let) { |
| + Let let = e; |
| + var variable = let.variable; |
| + syntheticVariables.add(variable); |
| + if (KernelVariableDeclaration.isDiscarding(variable)) { |
| + finalValue = let.body; |
| + e = variable.initializer; |
|
ahe
2017/06/08 14:16:33
I don't understand this. Isn't [e] supposed to be
Paul Berry
2017/06/08 16:17:04
Again considering the example of `var x = a[b] = c
|
| + } else { |
| + e = let.body; |
| + } |
| + } else if (e is KernelConditionalExpression && |
| + KernelConditionalExpression.isNullAwareCombiner(e)) { |
| + KernelConditionalExpression conditional = e; |
| + nullAwareCombiner = conditional; |
| + e = conditional.then; |
| + } else { |
| + break; |
| + } |
| + } |
| + |
| + Expression deref(Expression e, void callback(VariableDeclaration v)) { |
| + if (e is VariableGet) { |
| + var variable = e.variable; |
| + if (syntheticVariables.contains(variable)) { |
| + callback(variable); |
| + return variable.initializer; |
| + } |
| + } |
| + return e; |
| + } |
| + |
| + if (e is KernelMethodInvocation && identical(e.name.name, '[]=')) { |
| + write = e; |
| + receiver = deref(e.receiver, (v) => receiverVariable = v); |
| + index = deref(e.arguments.positional[0], (v) => indexVariable = v); |
| + value = deref(e.arguments.positional[1], (v) => valueVariable = v); |
| + if (value is KernelMethodInvocation && |
| + KernelMethodInvocation.isCombiner(value)) { |
| + combiner = value; |
| + read = deref(value.receiver, (v) => readVariable = v); |
| + rhs = deref(value.arguments.positional[0], (v) => rhsVariable = v); |
| + isPostIncDec = |
| + finalValue is VariableGet && finalValue.variable == readVariable; |
| + } else { |
| + rhs = value; |
| + if (nullAwareCombiner != null) { |
| + MethodInvocation equalsNullCheck = nullAwareCombiner.condition; |
| + read = deref(equalsNullCheck.receiver, (v) => readVariable = v); |
| + } |
| + } |
| + } else { |
| + internalError('Unexpected expression type: $e'); |
| + } |
| + |
| + var receiverType = inferExpression(receiver, null, true); |
| + receiverVariable?.type = receiverType; |
| + if (read != null) { |
| + var readMember = |
| + findMethodInvocationMember(receiverType, read, silent: true); |
| + if (readVariable != null) { |
| + var calleeType = getCalleeType(readMember, receiverType, indexGetName); |
| + if (calleeType is FunctionType) { |
| + readVariable.type = calleeType.returnType; |
| + } |
| + } |
| + } |
| + var writeMember = findMethodInvocationMember(receiverType, write); |
| + // To replicate analyzer behavior, we base type inference on the write |
| + // member. TODO(paulberry): would it be better to use the read member |
| + // when doing compound assignment? |
| + var calleeType = getCalleeType(writeMember, receiverType, indexSetName); |
| + DartType indexContext; |
| + DartType rhsContext; |
| + DartType inferredType; |
| + if (calleeType is FunctionType && |
| + calleeType.positionalParameters.length >= 2) { |
| + // TODO(paulberry): we ought to get a context for the index expression |
| + // from the index formal parameter, but analyzer doesn't so for now we |
| + // replicate its behavior. |
| + indexContext = null; |
| + rhsContext = calleeType.positionalParameters[1]; |
| + if (combiner != null) { |
| + var combinerMember = |
| + findMethodInvocationMember(rhsContext, combiner, silent: true); |
| + if (isPostIncDec) { |
| + inferredType = rhsContext; |
| + } else { |
| + inferredType = getCalleeFunctionType( |
| + combinerMember, rhsContext, combiner.name, false) |
| + .returnType; |
| + } |
| + // Analyzer uses a null context for the RHS here. |
| + // TODO(paulberry): improve on this. |
| + rhsContext = null; |
| + } else { |
| + inferredType = rhsContext; |
| + } |
| + } else { |
| + inferredType = const DynamicType(); |
| + } |
| + var indexType = inferExpression(index, indexContext, true); |
| + indexVariable?.type = indexType; |
| + var rhsType = inferExpression(rhs, rhsContext, true); |
| + rhsVariable?.type = rhsType; |
| + valueVariable?.type = rhsContext ?? const DynamicType(); |
| + if (nullAwareCombiner != null) { |
| + MethodInvocation equalsInvocation = nullAwareCombiner.condition; |
| + findMethodInvocationMember(rhsContext, equalsInvocation); |
| + nullAwareCombiner.staticType = inferredType; |
| + } |
| + listener.indexAssignExit(expression, inferredType); |
| + return inferredType; |
| + } |
| + |
| /// 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, |