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, |