| // 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 'package:kernel/ast.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/type_environment.dart'; |
| import 'package:front_end/src/api_unstable/vm.dart' show isExtensionTypeThis; |
| import 'package:front_end/src/api_prototype/record_use.dart' as RecordUse; |
| |
| import 'analysis.dart'; |
| import 'table_selector_assigner.dart'; |
| import 'types.dart'; |
| import 'utils.dart'; |
| import '../../metadata/procedure_attributes.dart'; |
| |
| /// Transform parameters from optional to required when they are always passed, |
| /// and remove parameters which are never passed or never used. |
| /// |
| /// Signatures of static functions are considered on their own. Instance methods |
| /// are grouped by table selector ID and thus can cover several implementations. |
| /// |
| /// Definitions: |
| /// - A parameter is checked if it requires a runtime check, either because it |
| /// is covariant or it is non-nullable and sound null safety is not enabled. |
| /// If sound null safety is not enabled, the analysis currently conservatively |
| /// assumes that null assertions are enabled. If we add a flag for null |
| /// assertions to gen_kernel, this flag can form part of the definition. |
| /// - A parameter is used if it is mentioned in the body (or, for constructors, |
| /// an initializer) of any implementation, or it is checked. An occurrence as |
| /// an argument to a call (a use dependency) is only considered a use if the |
| /// corresponding target parameter is used. |
| /// - A parameter can be eliminated if either: |
| /// 1. it is not used, |
| /// 2. it is never passed and is not written to in any implementation, or |
| /// 3. it is a constant (though not necessarily the same constant) in every |
| /// implementation and is neither written to nor checked. |
| /// - A function is eligible if it is not external and is guaranteed to not be |
| /// called with an unknown call signature. |
| /// |
| /// All eligible signatures are transformed such that they contain, in order: |
| /// 1. All positional parameters that are always passed and can't be |
| /// eliminated, as required positional parameters. |
| /// 2. All named parameters that are always passed and can't be eliminated, |
| /// as required positional parameters, alphabetically by name. |
| /// 3. All positional parameters that are not always passed and can't be |
| /// eliminated, as positional parameters, each one required iff it was |
| /// originally required. |
| /// 4. All named parameters that are not always passed and can't be |
| /// eliminated, as named parameters in alphabetical order. |
| class SignatureShaker { |
| final TypeFlowAnalysis typeFlowAnalysis; |
| final TableSelectorAssigner tableSelectorAssigner; |
| |
| final Map<Member, _ProcedureInfo> _memberInfo = {}; |
| final Map<int, _ProcedureInfo> _selectorInfo = {}; |
| |
| SignatureShaker(this.typeFlowAnalysis, this.tableSelectorAssigner); |
| |
| _ProcedureInfo? _infoForMember(Member member) { |
| if (!(member is Procedure && |
| (member.kind == ProcedureKind.Method || |
| member.kind == ProcedureKind.Factory) || |
| member is Constructor)) { |
| return null; |
| } |
| if (member.isInstanceMember) { |
| final selectorId = tableSelectorAssigner.methodOrSetterSelectorId(member); |
| assert(selectorId != ProcedureAttributesMetadata.kInvalidSelectorId); |
| return _selectorInfo.putIfAbsent(selectorId, () => _ProcedureInfo()); |
| } else { |
| return _memberInfo.putIfAbsent(member, () => _ProcedureInfo()); |
| } |
| } |
| |
| void transformComponent(Component component) { |
| _Collect(this).visitComponent(component); |
| _resolveUseDependencies(); |
| _Transform(this).visitComponent(component); |
| } |
| |
| void _resolveUseDependencies() { |
| List<_ParameterInfo> worklist = []; |
| for (_ProcedureInfo info in _memberInfo.values) { |
| _addUseDependenciesForProcedure(info, worklist); |
| } |
| for (_ProcedureInfo info in _selectorInfo.values) { |
| _addUseDependenciesForProcedure(info, worklist); |
| } |
| while (worklist.isNotEmpty) { |
| _ParameterInfo param = worklist.removeLast(); |
| for (_ParameterInfo dependencyParam in param.useDependencies!) { |
| if (!dependencyParam.isRead) { |
| dependencyParam.isRead = true; |
| if (dependencyParam.useDependencies != null) { |
| worklist.add(dependencyParam); |
| } |
| } |
| } |
| } |
| } |
| |
| void _addUseDependenciesForProcedure( |
| _ProcedureInfo info, List<_ParameterInfo> worklist) { |
| for (_ParameterInfo param in info.positional) { |
| _addUseDependenciesForParameter(param, worklist); |
| } |
| for (_ParameterInfo param in info.named.values) { |
| _addUseDependenciesForParameter(param, worklist); |
| } |
| } |
| |
| void _addUseDependenciesForParameter( |
| _ParameterInfo param, List<_ParameterInfo> worklist) { |
| if ((param.isUsed || !param.info.eligible) && |
| param.useDependencies != null) { |
| worklist.add(param); |
| } |
| } |
| } |
| |
| class _ProcedureInfo { |
| final List<_ParameterInfo> positional = []; |
| final Map<String, _ParameterInfo> named = {}; |
| int callCount = 0; |
| bool eligible = true; |
| |
| _ParameterInfo ensurePositional(int i) { |
| if (positional.length <= i) { |
| assert(positional.length == i); |
| positional.add(_ParameterInfo(this, i)); |
| } |
| return positional[i]; |
| } |
| |
| _ParameterInfo ensureNamed(String name) { |
| return named.putIfAbsent(name, () => _ParameterInfo(this, null)); |
| } |
| |
| bool transformNeeded(FunctionNode function) { |
| return positional.any((param) => |
| param.canBeEliminated || |
| (param.isAlwaysPassed && |
| param.index! >= function.requiredParameterCount)) || |
| named.values |
| .any((param) => param.canBeEliminated || param.isAlwaysPassed); |
| } |
| } |
| |
| class _ParameterInfo { |
| final _ProcedureInfo info; |
| final int? index; |
| |
| int passCount = 0; |
| bool isRead = false; |
| bool isWritten = false; |
| bool isChecked = false; |
| bool isConstant = true; |
| |
| /// List of parameter variables which were passed as arguments via this |
| /// parameter. When this parameter is considered used, all [useDependencies] |
| /// parameters should be transitively marked as read. |
| List<_ParameterInfo>? useDependencies = null; |
| |
| _ParameterInfo(this.info, this.index); |
| |
| bool get isNamed => index == null; |
| |
| bool get isUsed => isRead || isWritten || isChecked; |
| |
| bool get isAlwaysPassed => passCount == info.callCount; |
| |
| bool get isNeverPassed => passCount == 0; |
| |
| bool get canBeEliminated => |
| (!isUsed || (isNeverPassed || isConstant && !isChecked) && !isWritten); |
| |
| void observeParameter( |
| Member member, VariableDeclaration param, SignatureShaker shaker) { |
| final Type? type = shaker.typeFlowAnalysis.argumentType(member, param); |
| |
| // A parameter is considered constant if the TFA has inferred it to have a |
| // constant value in every implementation. The constant value inferred does |
| // not have to be the same across implementations, as it is specialized in |
| // each implementation individually. |
| if (!(type is ConcreteType && type.attributes?.constant != null || |
| type is NullableType && type.baseType is EmptyType)) { |
| isConstant = false; |
| } |
| |
| // Covariant parameters have implicit type checks, which count as reads. |
| if (param.isCovariantByDeclaration || param.isCovariantByClass) { |
| isChecked = true; |
| } |
| |
| // Avoid elimination of extension type declaration 'this' parameter |
| // as it affects equality of tear-off closures. |
| // |
| // The kernel encoding of a `extension type I(J it) { foo() {} }` is |
| // |
| // static method I|foo(lowered final test::J #this) → void {} |
| // static method I|get#foo(lowered final test::J #this) → () → void |
| // return () → void => test::I|foo(#this); |
| // |
| // So by not removing `#this` in kernel, the VM scope builder will mark |
| // it as captured and create a context for the closure. This |
| // would make distinct tear-off closure instances not equal, |
| // as required by the spec. |
| if (member.isExtensionTypeMember && isExtensionTypeThis(param)) { |
| isChecked = true; |
| } |
| |
| /// Disable signature shaking for annotated methods, to prevent removal of |
| /// parameters. The consumers of recorded_usages.json expect constant |
| /// argument values to be present for all parameters. |
| if (member is Procedure && |
| RecordUse.findRecordUseAnnotation(member).isNotEmpty) { |
| isChecked = true; |
| } |
| } |
| } |
| |
| class _Collect extends RecursiveVisitor { |
| final SignatureShaker shaker; |
| final CoreTypes coreTypes; |
| |
| /// Parameters of the current function. |
| final Map<VariableDeclaration, _ParameterInfo> localParameters = {}; |
| |
| /// Set of [VariableGet] nodes corresponding to parameters in the current |
| /// function which are passed as arguments to eligible calls. They are tracked |
| /// via [_ParameterInfo.useDependencies] and not marked as read immediately. |
| final Set<VariableGet> useDependencies = {}; |
| |
| _Collect(this.shaker) |
| : coreTypes = shaker.typeFlowAnalysis.environment.coreTypes; |
| |
| void enterFunction(Member member) { |
| final _ProcedureInfo? info = shaker._infoForMember(member); |
| if (info == null) return; |
| |
| localParameters.clear(); |
| useDependencies.clear(); |
| final FunctionNode fun = member.function!; |
| for (int i = 0; i < fun.positionalParameters.length; i++) { |
| final VariableDeclaration param = fun.positionalParameters[i]; |
| localParameters[param] = info.ensurePositional(i) |
| ..observeParameter(member, param, shaker); |
| } |
| for (VariableDeclaration param in fun.namedParameters) { |
| localParameters[param] = info.ensureNamed(param.name!) |
| ..observeParameter(member, param, shaker); |
| } |
| |
| if (shaker.typeFlowAnalysis.isCalledDynamically(member) || |
| shaker.typeFlowAnalysis.isTearOffTaken(member) || |
| shaker.typeFlowAnalysis.nativeCodeOracle |
| .isMemberReferencedFromNativeCode(member) || |
| shaker.typeFlowAnalysis.nativeCodeOracle.isRecognized(member) || |
| member.isExternal || |
| member.name.text == '==') { |
| info.eligible = false; |
| } |
| } |
| |
| @override |
| void visitProcedure(Procedure node) { |
| enterFunction(node); |
| super.visitProcedure(node); |
| } |
| |
| @override |
| void visitConstructor(Constructor node) { |
| enterFunction(node); |
| super.visitConstructor(node); |
| } |
| |
| @override |
| void visitVariableGet(VariableGet node) { |
| // Variable reads marked as use dependencies are not considered reads |
| // immediately. Their status as a read or not will be computed after all use |
| // dependencies have been collected. |
| localParameters[node.variable]?.isRead |= !useDependencies.contains(node); |
| super.visitVariableGet(node); |
| } |
| |
| @override |
| void visitVariableSet(VariableSet node) { |
| localParameters[node.variable]?.isWritten = true; |
| super.visitVariableSet(node); |
| } |
| |
| void addUseDependency(Expression arg, _ParameterInfo param) { |
| if (arg is VariableGet) { |
| _ParameterInfo? localParam = localParameters[arg.variable]; |
| if (localParam != null && !localParam.isUsed) { |
| // This is a parameter passed as an argument. Mark it as a use |
| // dependency. |
| var paramUseDependencies = param.useDependencies; |
| if (paramUseDependencies == null) { |
| param.useDependencies = paramUseDependencies = []; |
| } |
| paramUseDependencies.add(localParam); |
| useDependencies.add(arg); |
| } |
| } |
| } |
| |
| void collectCall(Member member, Arguments args) { |
| final _ProcedureInfo? info = shaker._infoForMember(member); |
| if (info == null) return; |
| |
| for (int i = 0; i < args.positional.length; i++) { |
| _ParameterInfo param = info.ensurePositional(i); |
| param.passCount++; |
| addUseDependency(args.positional[i], param); |
| } |
| for (NamedExpression named in args.named) { |
| _ParameterInfo param = info.ensureNamed(named.name); |
| param.passCount++; |
| addUseDependency(named.value, param); |
| } |
| info.callCount++; |
| } |
| |
| @override |
| void visitInstanceInvocation(InstanceInvocation node) { |
| collectCall(node.interfaceTarget, node.arguments); |
| super.visitInstanceInvocation(node); |
| } |
| |
| @override |
| void visitSuperMethodInvocation(SuperMethodInvocation node) { |
| collectCall(node.interfaceTarget, node.arguments); |
| super.visitSuperMethodInvocation(node); |
| } |
| |
| @override |
| void visitStaticInvocation(StaticInvocation node) { |
| collectCall(node.target, node.arguments); |
| super.visitStaticInvocation(node); |
| } |
| |
| @override |
| void visitConstructorInvocation(ConstructorInvocation node) { |
| collectCall(node.target, node.arguments); |
| super.visitConstructorInvocation(node); |
| } |
| |
| @override |
| void visitRedirectingInitializer(RedirectingInitializer node) { |
| collectCall(node.target, node.arguments); |
| super.visitRedirectingInitializer(node); |
| } |
| |
| @override |
| void visitSuperInitializer(SuperInitializer node) { |
| collectCall(node.target, node.arguments); |
| super.visitSuperInitializer(node); |
| } |
| } |
| |
| class _Transform extends RecursiveVisitor { |
| final SignatureShaker shaker; |
| |
| late StaticTypeContext typeContext; |
| final Map<VariableDeclaration, Constant> eliminatedParams = {}; |
| final Set<VariableDeclaration> unusedParams = {}; |
| final List<LocalInitializer> addedInitializers = []; |
| |
| _Transform(this.shaker); |
| |
| void eliminateUsedParameter( |
| Member member, _ParameterInfo param, VariableDeclaration variable) { |
| Constant value; |
| if (param.isConstant) { |
| Type type = shaker.typeFlowAnalysis.argumentType(member, variable)!; |
| if (type is ConcreteType) { |
| value = type.attributes!.constant!; |
| } else { |
| assert(type is NullableType && type.baseType is EmptyType); |
| value = NullConstant(); |
| } |
| } else { |
| value = (variable.initializer as ConstantExpression?)?.constant ?? |
| NullConstant(); |
| } |
| eliminatedParams[variable] = value; |
| } |
| |
| void transformMemberSignature(Member member) { |
| typeContext = |
| StaticTypeContext(member, shaker.typeFlowAnalysis.environment); |
| eliminatedParams.clear(); |
| unusedParams.clear(); |
| |
| final _ProcedureInfo? info = shaker._infoForMember(member); |
| if (info == null || !info.eligible || info.callCount == 0) return; |
| |
| final FunctionNode function = member.function!; |
| |
| if (!info.transformNeeded(function)) return; |
| |
| final List<VariableDeclaration> positional = []; |
| final List<VariableDeclaration> named = []; |
| // 1. All positional parameters that are always passed and can't be |
| // eliminated, as required positional parameters. |
| int firstNotAlwaysPassed = function.positionalParameters.length; |
| for (int i = 0; i < function.positionalParameters.length; i++) { |
| final _ParameterInfo param = info.positional[i]; |
| if (!param.isAlwaysPassed) { |
| firstNotAlwaysPassed = i; |
| break; |
| } |
| final VariableDeclaration variable = function.positionalParameters[i]; |
| if (param.isUsed) { |
| if (param.canBeEliminated) { |
| eliminateUsedParameter(member, param, variable); |
| } else { |
| positional.add(variable); |
| variable.initializer = null; |
| variable.hasDeclaredInitializer = false; |
| } |
| } else { |
| unusedParams.add(variable); |
| } |
| } |
| // 2. All named parameters that are always passed and can't be eliminated, |
| // as required positional parameters, alphabetically by name. |
| final List<VariableDeclaration> sortedNamed = function.namedParameters |
| .toList() |
| ..sort((var1, var2) => var1.name!.compareTo(var2.name!)); |
| for (VariableDeclaration variable in sortedNamed) { |
| final _ParameterInfo param = info.named[variable.name!]!; |
| if (param.isAlwaysPassed) { |
| if (param.isUsed) { |
| if (param.canBeEliminated) { |
| eliminateUsedParameter(member, param, variable); |
| } else { |
| variable.initializer = null; |
| variable.hasDeclaredInitializer = false; |
| variable.isRequired = false; |
| positional.add(variable); |
| } |
| } else { |
| unusedParams.add(variable); |
| } |
| } |
| } |
| int requiredParameterCount = positional.length; |
| // 3. All positional parameters that are not always passed and can't be |
| // eliminated, as positional parameters, each one required iff it was |
| // originally required. |
| for (int i = firstNotAlwaysPassed; |
| i < function.positionalParameters.length; |
| i++) { |
| final _ParameterInfo param = info.positional[i]; |
| assert(!param.isAlwaysPassed); |
| final VariableDeclaration variable = function.positionalParameters[i]; |
| if (param.isUsed) { |
| if (param.canBeEliminated) { |
| eliminateUsedParameter(member, param, variable); |
| } else if (!param.isAlwaysPassed) { |
| positional.add(variable); |
| if (i < function.requiredParameterCount) { |
| // The parameter is required, but it is not always passed. This is |
| // possible if the method is overridden by a method which makes the |
| // parameter optional. |
| assert(variable.initializer == null); |
| requiredParameterCount++; |
| } |
| } |
| } else { |
| unusedParams.add(variable); |
| } |
| } |
| // 4. All named parameters that are not always passed and can't be |
| // eliminated, as named parameters in alphabetical order. |
| for (VariableDeclaration variable in sortedNamed) { |
| final _ParameterInfo param = info.named[variable.name!]!; |
| if (!param.isAlwaysPassed) { |
| if (param.isUsed) { |
| if (param.canBeEliminated) { |
| eliminateUsedParameter(member, param, variable); |
| } else { |
| named.add(variable); |
| } |
| } else { |
| unusedParams.add(variable); |
| } |
| } |
| } |
| |
| assert(requiredParameterCount <= positional.length); |
| function.requiredParameterCount = requiredParameterCount; |
| function.positionalParameters = positional; |
| function.namedParameters = named; |
| |
| shaker.typeFlowAnalysis.adjustFunctionParameters(member); |
| } |
| |
| @override |
| void visitVariableGet(VariableGet node) { |
| Constant? constantValue = eliminatedParams[node.variable]; |
| if (constantValue != null) { |
| node.replaceWith(ConstantExpression( |
| constantValue, constantValue.getType(typeContext))); |
| } |
| } |
| |
| @override |
| void visitConstructor(Constructor node) { |
| transformMemberSignature(node); |
| super.visitConstructor(node); |
| if (addedInitializers.isNotEmpty) { |
| // Insert hoisted constructor arguments before this/super initializer. |
| assert(node.initializers.last is RedirectingInitializer || |
| node.initializers.last is SuperInitializer); |
| node.initializers |
| .insertAll(node.initializers.length - 1, addedInitializers.reversed); |
| addedInitializers.clear(); |
| } |
| } |
| |
| @override |
| void visitProcedure(Procedure node) { |
| transformMemberSignature(node); |
| super.visitProcedure(node); |
| } |
| |
| @override |
| void visitField(Field node) { |
| typeContext = StaticTypeContext(node, shaker.typeFlowAnalysis.environment); |
| super.visitField(node); |
| } |
| |
| static void forEachArgumentRev(Arguments args, _ProcedureInfo info, |
| void Function(Expression, _ParameterInfo) fun) { |
| for (int i = args.named.length - 1; i >= 0; i--) { |
| final NamedExpression namedExp = args.named[i]; |
| fun(namedExp.value, info.named[namedExp.name]!); |
| } |
| for (int i = args.positional.length - 1; i >= 0; i--) { |
| fun(args.positional[i], info.positional[i]); |
| } |
| } |
| |
| void transformCall( |
| Member target, TreeNode call, Expression? receiver, Arguments args) { |
| final _ProcedureInfo? info = shaker._infoForMember(target); |
| if (info == null || !info.eligible) return; |
| |
| bool transformNeeded = false; |
| bool hoistingNeeded = false; |
| forEachArgumentRev(args, info, (Expression arg, _ParameterInfo param) { |
| assert(!param.isNeverPassed); |
| if (param.canBeEliminated || param.isNamed && param.isAlwaysPassed) { |
| transformNeeded = true; |
| if (mayHaveSideEffects(arg)) { |
| hoistingNeeded = true; |
| } |
| } |
| }); |
| |
| if (!transformNeeded) return; |
| |
| bool isUnusedParam(Expression exp) { |
| return exp is VariableGet && unusedParams.contains(exp.variable); |
| } |
| |
| Map<Expression, VariableDeclaration> hoisted = {}; |
| if (hoistingNeeded) { |
| if (call is Initializer) { |
| final Constructor constructor = call.parent as Constructor; |
| forEachArgumentRev(args, info, (Expression arg, _ParameterInfo param) { |
| if (mayHaveOrSeeSideEffects(arg) && !isUnusedParam(arg)) { |
| VariableDeclaration argVar = VariableDeclaration(null, |
| initializer: arg, |
| type: arg.getStaticType(typeContext), |
| isFinal: true, |
| isSynthesized: true); |
| addedInitializers |
| .add(LocalInitializer(argVar)..parent = constructor); |
| hoisted[arg] = argVar; |
| } |
| }); |
| } else { |
| final TreeNode parent = call.parent!; |
| Expression current = call as Expression; |
| forEachArgumentRev(args, info, (Expression arg, _ParameterInfo param) { |
| if (mayHaveOrSeeSideEffects(arg) && !isUnusedParam(arg)) { |
| VariableDeclaration argVar = VariableDeclaration(null, |
| initializer: arg, |
| type: arg.getStaticType(typeContext), |
| isFinal: true, |
| isSynthesized: true); |
| current = Let(argVar, current); |
| hoisted[arg] = argVar; |
| } |
| }); |
| if (receiver != null && mayHaveOrSeeSideEffects(receiver)) { |
| assert(!isUnusedParam(receiver)); |
| assert(receiver.parent == call); |
| final VariableDeclaration receiverVar = VariableDeclaration(null, |
| initializer: receiver, |
| type: receiver.getStaticType(typeContext), |
| isFinal: true, |
| isSynthesized: true); |
| current = Let(receiverVar, current); |
| call.replaceChild(receiver, VariableGet(receiverVar)); |
| } |
| |
| parent.replaceChild(call, current); |
| } |
| } |
| |
| Expression getMaybeHoistedArg(Expression arg) { |
| final variable = hoisted[arg]; |
| if (variable == null) return arg; |
| return VariableGet(variable); |
| } |
| |
| final List<Expression> positional = []; |
| final List<NamedExpression> named = []; |
| // 1. All positional parameters that are always passed and can't be |
| // eliminated, as required positional parameters. |
| for (int i = 0; i < args.positional.length; i++) { |
| final _ParameterInfo param = info.positional[i]; |
| final Expression arg = args.positional[i]; |
| if (param.isAlwaysPassed && !param.canBeEliminated) { |
| positional.add(getMaybeHoistedArg(arg)); |
| } |
| } |
| // 2. All named parameters that are always passed and can't be eliminated, |
| // as required positional parameters, alphabetically by name. |
| final List<NamedExpression> sortedNamed = args.named.toList() |
| ..sort((var1, var2) => var1.name.compareTo(var2.name)); |
| for (NamedExpression arg in sortedNamed) { |
| final _ParameterInfo param = info.named[arg.name]!; |
| if (param.isAlwaysPassed && !param.canBeEliminated) { |
| positional.add(getMaybeHoistedArg(arg.value)); |
| } |
| } |
| // 3. All positional parameters that are not always passed and can't be |
| // eliminated, as positional parameters, each one required iff it was |
| // originally required. |
| for (int i = 0; i < args.positional.length; i++) { |
| final _ParameterInfo param = info.positional[i]; |
| final Expression arg = args.positional[i]; |
| if (!param.isAlwaysPassed && !param.canBeEliminated) { |
| positional.add(getMaybeHoistedArg(arg)); |
| } |
| } |
| // 4. All named parameters that are not always passed and can't be |
| // eliminated, as named parameters in alphabetical order. |
| // (Arguments are kept in original order.) |
| for (NamedExpression arg in args.named) { |
| final _ParameterInfo param = info.named[arg.name]!; |
| if (!param.isAlwaysPassed && !param.canBeEliminated) { |
| arg.value = getMaybeHoistedArg(arg.value)..parent = arg; |
| named.add(arg); |
| } |
| } |
| |
| args.replaceWith(Arguments(positional, named: named, types: args.types)); |
| } |
| |
| @override |
| void visitInstanceInvocation(InstanceInvocation node) { |
| super.visitInstanceInvocation(node); |
| transformCall(node.interfaceTarget, node, node.receiver, node.arguments); |
| } |
| |
| @override |
| void visitSuperMethodInvocation(SuperMethodInvocation node) { |
| super.visitSuperMethodInvocation(node); |
| transformCall(node.interfaceTarget, node, null, node.arguments); |
| } |
| |
| @override |
| void visitStaticInvocation(StaticInvocation node) { |
| super.visitStaticInvocation(node); |
| transformCall(node.target, node, null, node.arguments); |
| } |
| |
| @override |
| void visitConstructorInvocation(ConstructorInvocation node) { |
| super.visitConstructorInvocation(node); |
| transformCall(node.target, node, null, node.arguments); |
| } |
| |
| @override |
| void visitRedirectingInitializer(RedirectingInitializer node) { |
| super.visitRedirectingInitializer(node); |
| transformCall(node.target, node, null, node.arguments); |
| } |
| |
| @override |
| void visitSuperInitializer(SuperInitializer node) { |
| super.visitSuperInitializer(node); |
| transformCall(node.target, node, null, node.arguments); |
| } |
| } |