| // 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 file. |
| |
| /// Declares classes which describe a call: selectors and arguments. |
| library; |
| |
| import 'dart:core' hide Type; |
| |
| import 'package:kernel/ast.dart'; |
| |
| import 'types.dart'; |
| import 'utils.dart'; |
| |
| enum CallKind { |
| Method, // x.foo(..) or foo() |
| PropertyGet, // ... x.foo ... |
| PropertySet, // x.foo = ... |
| FieldInitializer, // run initializer of a field |
| SetFieldInConstructor, // foo = ... in initializer list in a constructor |
| } |
| |
| /// [Selector] encapsulates the way of calling (at the call site). |
| abstract class Selector { |
| /// Call kind: how call is performed? |
| final CallKind callKind; |
| |
| Selector(this.callKind); |
| |
| /// Interface or concrete target, may be null. |
| Member? get member; |
| |
| /// Selector name. |
| Name get name => member!.name; |
| |
| bool get isSetter => (callKind == CallKind.PropertySet); |
| |
| @override |
| int get hashCode => callKind.hashCode; |
| |
| @override |
| bool operator ==(other) => |
| identical(this, other) || other is Selector && other.callKind == callKind; |
| |
| /// Static approximation of Dart return type. |
| DartType get staticReturnType { |
| final member = this.member; |
| if (member == null) { |
| return const DynamicType(); |
| } |
| switch (callKind) { |
| case CallKind.Method: |
| return (member is Procedure) |
| ? member.function.returnType |
| : const NeverType.nonNullable(); |
| case CallKind.PropertyGet: |
| return member.getterType; |
| case CallKind.PropertySet: |
| case CallKind.FieldInitializer: |
| case CallKind.SetFieldInConstructor: |
| return const NeverType.nonNullable(); |
| } |
| } |
| |
| bool memberAgreesToCallKind(Member member) { |
| switch (callKind) { |
| case CallKind.Method: |
| return ((member is Procedure) && |
| !member.isGetter && |
| !member.isSetter) || |
| (member is Constructor); |
| case CallKind.PropertyGet: |
| return (member is Field) || ((member is Procedure) && member.isGetter); |
| case CallKind.PropertySet: |
| return (member is Field) || ((member is Procedure) && member.isSetter); |
| case CallKind.FieldInitializer: |
| case CallKind.SetFieldInConstructor: |
| return member is Field; |
| } |
| } |
| |
| String get _callKindPrefix { |
| switch (callKind) { |
| case CallKind.Method: |
| return ''; |
| case CallKind.PropertyGet: |
| return 'get '; |
| case CallKind.PropertySet: |
| case CallKind.SetFieldInConstructor: |
| return 'set '; |
| case CallKind.FieldInitializer: |
| return 'init '; |
| } |
| } |
| } |
| |
| /// Direct call to [member]. |
| class DirectSelector extends Selector { |
| final Member member; |
| |
| DirectSelector(this.member, {CallKind callKind = CallKind.Method}) |
| : super(callKind) { |
| assert((callKind == CallKind.Method) || |
| (callKind == CallKind.PropertyGet) || |
| memberAgreesToCallKind(member)); |
| } |
| |
| @override |
| int get hashCode => combineHashes(super.hashCode, member.hashCode); |
| |
| @override |
| bool operator ==(other) => |
| identical(this, other) || |
| other is DirectSelector && super == (other) && other.member == member; |
| |
| @override |
| String toString() => 'direct ${_callKindPrefix}' |
| '[${nodeToText(member)}]'; |
| } |
| |
| /// Interface call via known interface target [member]. |
| class InterfaceSelector extends Selector { |
| final Member member; |
| |
| InterfaceSelector(this.member, {CallKind callKind = CallKind.Method}) |
| : super(callKind); |
| |
| @override |
| int get hashCode => combineHashes(super.hashCode, member.hashCode); |
| |
| @override |
| bool operator ==(other) => |
| identical(this, other) || |
| other is InterfaceSelector && super == (other) && other.member == member; |
| |
| @override |
| String toString() => '${_callKindPrefix}' |
| '[${nodeToText(member)}]'; |
| } |
| |
| /// Virtual call (using 'this' as a receiver). |
| class VirtualSelector extends InterfaceSelector { |
| VirtualSelector(Member member, {CallKind callKind = CallKind.Method}) |
| : super(member, callKind: callKind); |
| |
| @override |
| int get hashCode { |
| const int seed = 37; |
| return combineHashes(seed, super.hashCode); |
| } |
| |
| @override |
| bool operator ==(other) => |
| identical(this, other) || other is VirtualSelector && super == (other); |
| |
| @override |
| String toString() => 'virtual ${_callKindPrefix}' |
| '[${nodeToText(member)}]'; |
| } |
| |
| /// Dynamic call. |
| class DynamicSelector extends Selector { |
| @override |
| final Name name; |
| |
| static final kCall = DynamicSelector(CallKind.Method, Name.callName); |
| |
| DynamicSelector(CallKind callKind, this.name) : super(callKind); |
| |
| @override |
| Member? get member => null; |
| |
| @override |
| int get hashCode => combineHashes(super.hashCode, name.hashCode); |
| |
| @override |
| bool operator ==(other) => |
| identical(this, other) || |
| other is DynamicSelector && super == (other) && other.name == name; |
| |
| @override |
| String toString() => 'dynamic ${_callKindPrefix}[${nodeToText(name)}]'; |
| } |
| |
| /// Function call with known function type. |
| class FunctionSelector extends Selector { |
| final Type staticResultType; |
| |
| @override |
| Name get name => Name.callName; |
| |
| FunctionSelector(this.staticResultType) : super(CallKind.Method); |
| |
| @override |
| Member? get member => null; |
| |
| @override |
| int get hashCode => combineHashes(super.hashCode, staticResultType.hashCode); |
| |
| @override |
| bool operator ==(other) => |
| identical(this, other) || |
| other is FunctionSelector && |
| super == (other) && |
| other.staticResultType == staticResultType; |
| |
| @override |
| String toString() => 'function [=> ${staticResultType}]'; |
| } |
| |
| /// Arguments passed to a call, including implicit receiver argument. |
| // TODO(alexmarkov): take type arguments into account |
| class Args<T extends TypeExpr> { |
| final List<T> values; |
| final List<String> names; |
| |
| @override |
| late final int hashCode = _computeHashCode(); |
| |
| Args(this.values, {this.names = const <String>[]}) { |
| assert(isSorted(names)); |
| } |
| |
| Args.withReceiver(Args<T> args, T receiver) |
| : values = new List.from(args.values), |
| names = args.names { |
| values[0] = receiver; |
| } |
| |
| int get positionalCount => values.length - names.length; |
| int get namedCount => names.length; |
| |
| T get receiver => values[0]; |
| |
| int _computeHashCode() { |
| int hash = 1231; |
| for (var i = 0; i < values.length; i++) { |
| hash = combineHashes(hash, values[i].hashCode); |
| } |
| for (var i = 0; i < names.length; i++) { |
| hash = combineHashes(hash, names[i].hashCode); |
| } |
| return hash; |
| } |
| |
| @override |
| bool operator ==(other) { |
| if (identical(this, other)) return true; |
| if (other is Args<T> && |
| (this.values.length == other.values.length) && |
| (this.names.length == other.names.length)) { |
| for (int i = 0; i < values.length; i++) { |
| if (values[i] != other.values[i]) { |
| return false; |
| } |
| } |
| for (int i = 0; i < names.length; i++) { |
| if (names[i] != other.names[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| @override |
| String toString() { |
| StringBuffer buf = new StringBuffer(); |
| buf.write("("); |
| for (int i = 0; i < positionalCount; i++) { |
| if (i != 0) { |
| buf.write(", "); |
| } |
| buf.write(values[i]); |
| } |
| for (int i = 0; i < names.length; i++) { |
| if (positionalCount + i != 0) { |
| buf.write(", "); |
| } |
| buf.write(names[i]); |
| buf.write(': '); |
| buf.write(values[positionalCount + i]); |
| } |
| buf.write(")"); |
| return buf.toString(); |
| } |
| } |