| // Copyright (c) 2021, 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. |
| |
| library fasta.class_hierarchy_builder; |
| |
| import 'package:kernel/ast.dart'; |
| |
| import '../../builder/class_builder.dart'; |
| import '../../messages.dart' |
| show |
| LocatedMessage, |
| messageDeclaredMemberConflictsWithOverriddenMembersCause, |
| templateCombinedMemberSignatureFailed; |
| import '../../source/source_class_builder.dart'; |
| import '../../source/source_library_builder.dart' show SourceLibraryBuilder; |
| import '../combined_member_signature.dart'; |
| import '../forwarding_node.dart' show ForwardingNode; |
| import '../member_covariance.dart'; |
| import 'members_builder.dart'; |
| |
| abstract class ClassMember { |
| Name get name; |
| bool get isStatic; |
| bool get isField; |
| bool get isAssignable; |
| bool get isSetter; |
| bool get isGetter; |
| bool get isFinal; |
| bool get isConst; |
| bool get forSetter; |
| |
| /// Returns `true` if this member corresponds to a declaration in the source |
| /// code. |
| bool get isSourceDeclaration; |
| |
| /// Returns `true` if this member is a field, getter or setter. |
| bool get isProperty; |
| |
| /// Computes the [Member] node resulting from this class member. |
| Member getMember(ClassMembersBuilder membersBuilder); |
| |
| /// Returns the member [Covariance] for this class member. |
| Covariance getCovariance(ClassMembersBuilder membersBuilder); |
| |
| bool get isDuplicate; |
| String get fullName; |
| String get fullNameForErrors; |
| ClassBuilder get classBuilder; |
| |
| /// Returns `true` if this class member is declared in Object from dart:core. |
| bool isObjectMember(ClassBuilder objectClass); |
| Uri get fileUri; |
| int get charOffset; |
| |
| /// Returns `true` if this class member is an interface member. |
| bool get isAbstract; |
| |
| /// Returns `true` if this member doesn't corresponds to a declaration in the |
| /// source code. |
| bool get isSynthesized; |
| |
| // If `true` this member is not part of the interface but only part of the |
| // class members. |
| // |
| // This is `true` for instance for synthesized fields added for the late |
| // lowering. |
| bool get isInternalImplementation; |
| |
| /// Returns `true` if this member is composed from a list of class members |
| /// accessible through [declarations]. |
| bool get hasDeclarations; |
| |
| /// If [hasDeclaration] is `true`, this returns the list of class members |
| /// from which this class member is composed. |
| /// |
| /// This is used in [unfoldDeclarations] to retrieve all underlying member |
| /// source declarations, and in [toSet] to retrieve all members used for |
| /// this class member wrt. certain level of the hierarchy. |
| /// TODO(johnniwinther): Can the use of [toSet] be replaced with a direct |
| /// use of [declarations]? |
| List<ClassMember> get declarations; |
| |
| /// The interface member corresponding to this member. |
| /// |
| /// If this member is declared on the source, the interface member is |
| /// the member itself. For instance |
| /// |
| /// abstract class Class { |
| /// void concreteMethod() {} |
| /// void abstractMethod(); |
| /// } |
| /// |
| /// the interface members for `concreteMethod` and `abstractMethod` are the |
| /// members themselves. |
| /// |
| /// If this member is a synthesized interface member, the |
| /// interface member is the member itself. For instance |
| /// |
| /// abstract class Interface1 { |
| /// void method() {} |
| /// } |
| /// abstract class Interface2 { |
| /// void method() {} |
| /// } |
| /// abstract class Class implements Interface1, Interface2 {} |
| /// |
| /// the interface member for `method` in `Class` is the synthesized interface |
| /// member created for the implemented members `Interface1.method` and |
| /// `Interface2.method`. |
| /// |
| /// If this member is a concrete member that implements an interface member, |
| /// the interface member is the implemented interface member. For instance |
| /// |
| /// class Super { |
| /// void method() {} |
| /// } |
| /// class Interface { |
| /// void method() {} |
| /// } |
| /// class Class extends Super implements Interface {} |
| /// |
| /// the interface member for `Super.method` implementing `method` in `Class` |
| /// is the synthesized interface member created for the implemented members |
| /// `Super.method` and `Interface.method`. |
| ClassMember get interfaceMember; |
| |
| void inferType(ClassMembersBuilder membersBuilder); |
| void registerOverrideDependency(Set<ClassMember> overriddenMembers); |
| |
| /// Returns `true` if this has the same underlying declaration as [other]. |
| /// |
| /// This is used for avoiding unnecessary checks and can this trivially |
| /// return `false`. |
| bool isSameDeclaration(ClassMember other); |
| } |
| |
| abstract class SynthesizedMember extends ClassMember { |
| @override |
| final ClassBuilder classBuilder; |
| |
| @override |
| final Name name; |
| |
| @override |
| final bool forSetter; |
| |
| @override |
| final bool isProperty; |
| |
| SynthesizedMember(this.classBuilder, this.name, |
| {required this.forSetter, required this.isProperty}) |
| // ignore: unnecessary_null_comparison |
| : assert(forSetter != null), |
| // ignore: unnecessary_null_comparison |
| assert(isProperty != null); |
| |
| @override |
| List<ClassMember> get declarations => throw new UnimplementedError(); |
| |
| @override |
| void inferType(ClassMembersBuilder membersBuilder) {} |
| |
| @override |
| bool get isAssignable => throw new UnimplementedError(); |
| |
| @override |
| bool get isConst => throw new UnimplementedError(); |
| |
| @override |
| bool get isDuplicate => false; |
| |
| @override |
| bool get isField => throw new UnimplementedError(); |
| |
| @override |
| bool get isFinal => throw new UnimplementedError(); |
| |
| @override |
| bool get isGetter => throw new UnimplementedError(); |
| |
| @override |
| bool get isInternalImplementation => false; |
| |
| @override |
| bool get isSetter => forSetter; |
| |
| @override |
| bool get isSourceDeclaration => false; |
| |
| @override |
| bool get isStatic => false; |
| |
| @override |
| bool get isSynthesized => true; |
| |
| @override |
| void registerOverrideDependency(Set<ClassMember> overriddenMembers) {} |
| } |
| |
| /// Class member for a set of interface members. |
| /// |
| /// This is used to compute combined member signature of a set of interface |
| /// members inherited into the same class, and to insert forwarding stubs, |
| /// mixin stubs, and member signatures where needed. |
| class SynthesizedInterfaceMember extends SynthesizedMember { |
| @override |
| final List<ClassMember> declarations; |
| |
| /// The concrete member in the super class overridden by [declarations], if |
| /// any. |
| /// |
| /// This is used to as the target when creating concrete forwarding and mixin |
| /// stub. For instance: |
| /// |
| /// class Super { |
| /// method(int i) {} |
| /// } |
| /// class Interface { |
| /// method(covariant int i) {} |
| /// } |
| /// class Class extends Super implements Interface { |
| /// // Concrete forwarding stub calling [_superClassMember]: |
| /// method(covariant int i) => super.method(i); |
| /// |
| final ClassMember? _superClassMember; |
| |
| /// The canonical member of the combined member signature if it is known by |
| /// construction. The canonical member defines the type of combined member |
| /// signature. |
| /// |
| /// This is used when a declared member is part of a set of implemented |
| /// members. For instance |
| /// |
| /// class Super { |
| /// method(int i) {} |
| /// } |
| /// class Interface { |
| /// method(covariant num i) {} |
| /// } |
| /// class Class implements Interface { |
| /// // This member is updated to be a concrete forwarding stub with an |
| /// // covariant parameter but with its declared parameter type: |
| /// // method(covariant int i) => super.method(i); |
| /// method(int i); |
| /// } |
| final ClassMember? _canonicalMember; |
| |
| /// The member in [declarations] that is mixed in, if any. |
| /// |
| /// This is used to create mixin stubs. If the mixed in member is abstract, |
| /// an abstract mixin stub is created: |
| /// |
| /// class Super { |
| /// void method() {} |
| /// } |
| /// class Mixin { |
| /// void method(); |
| /// } |
| /// // Abstract mixin stub with `Mixin.method` as target inserted: |
| /// // void method(); |
| /// class Class = Super with Mixin; |
| /// |
| /// If the mixed in member is concrete, a concrete mixin member is created: |
| /// |
| /// class Super { |
| /// void method() {} |
| /// } |
| /// class Mixin { |
| /// void method() {} |
| /// } |
| /// // Concrete mixin stub with `Mixin.method` as target inserted: |
| /// // void method() => super.method(); |
| /// class Class = Super with Mixin; |
| /// |
| /// If a forwarding stub is needed, the created stub will be a possibly |
| /// concrete forwarding stub: |
| /// |
| /// class Super { |
| /// void method(int i) {} |
| /// } |
| /// class Interface { |
| /// void method(covariant num i) {} |
| /// } |
| /// class Mixin { |
| /// void method(int i); |
| /// } |
| /// // Concrete forwarding stub with `Super.method` as target inserted: |
| /// // void method(covariant int i) => super.method(i); |
| /// class Class = Super with Mixin implements Interface; |
| /// |
| final ClassMember? _mixedInMember; |
| |
| /// If `true`, a stub should be inserted, if needed. |
| final bool _shouldModifyKernel; |
| |
| Member? _member; |
| Covariance? _covariance; |
| |
| SynthesizedInterfaceMember( |
| ClassBuilder classBuilder, Name name, this.declarations, |
| {ClassMember? superClassMember, |
| ClassMember? canonicalMember, |
| ClassMember? mixedInMember, |
| required bool isProperty, |
| required bool forSetter, |
| required bool shouldModifyKernel}) |
| : this._superClassMember = superClassMember, |
| this._canonicalMember = canonicalMember, |
| this._mixedInMember = mixedInMember, |
| this._shouldModifyKernel = shouldModifyKernel, |
| super(classBuilder, name, isProperty: isProperty, forSetter: forSetter); |
| |
| @override |
| bool get hasDeclarations => true; |
| |
| void _ensureMemberAndCovariance(ClassMembersBuilder membersBuilder) { |
| if (_member != null) { |
| return; |
| } |
| if (classBuilder.library is! SourceLibraryBuilder) { |
| if (_canonicalMember != null) { |
| _member = _canonicalMember!.getMember(membersBuilder); |
| _covariance = _canonicalMember!.getCovariance(membersBuilder); |
| } else { |
| _member = declarations.first.getMember(membersBuilder); |
| _covariance = declarations.first.getCovariance(membersBuilder); |
| } |
| return; |
| } |
| CombinedClassMemberSignature combinedMemberSignature; |
| if (_canonicalMember != null) { |
| combinedMemberSignature = new CombinedClassMemberSignature.internal( |
| membersBuilder, |
| classBuilder as SourceClassBuilder, |
| declarations.indexOf(_canonicalMember!), |
| declarations, |
| forSetter: isSetter); |
| } else { |
| combinedMemberSignature = new CombinedClassMemberSignature( |
| membersBuilder, classBuilder as SourceClassBuilder, declarations, |
| forSetter: isSetter); |
| |
| if (combinedMemberSignature.canonicalMember == null) { |
| String name = classBuilder.fullNameForErrors; |
| int length = classBuilder.isAnonymousMixinApplication ? 1 : name.length; |
| List<LocatedMessage> context = declarations.map((ClassMember d) { |
| return messageDeclaredMemberConflictsWithOverriddenMembersCause |
| .withLocation( |
| d.fileUri, d.charOffset, d.fullNameForErrors.length); |
| }).toList(); |
| |
| classBuilder.addProblem( |
| templateCombinedMemberSignatureFailed.withArguments( |
| classBuilder.fullNameForErrors, |
| declarations.first.fullNameForErrors), |
| classBuilder.charOffset, |
| length, |
| context: context); |
| // TODO(johnniwinther): Maybe we should have an invalid marker to avoid |
| // cascading errors. |
| _member = declarations.first.getMember(membersBuilder); |
| _covariance = declarations.first.getCovariance(membersBuilder); |
| return; |
| } |
| } |
| |
| if (_shouldModifyKernel) { |
| ProcedureKind kind = ProcedureKind.Method; |
| Member canonicalMember = |
| combinedMemberSignature.canonicalMember!.getMember(membersBuilder); |
| if (combinedMemberSignature.canonicalMember!.isProperty) { |
| kind = isSetter ? ProcedureKind.Setter : ProcedureKind.Getter; |
| } else if (canonicalMember is Procedure && |
| canonicalMember.kind == ProcedureKind.Operator) { |
| kind = ProcedureKind.Operator; |
| } |
| |
| Procedure? stub = new ForwardingNode( |
| combinedMemberSignature, kind, _superClassMember, _mixedInMember) |
| .finalize(); |
| if (stub != null) { |
| assert(classBuilder.cls == stub.enclosingClass); |
| assert(stub != canonicalMember); |
| classBuilder.cls.addProcedure(stub); |
| SourceLibraryBuilder library = |
| classBuilder.library as SourceLibraryBuilder; |
| if (canonicalMember is Procedure) { |
| library.forwardersOrigins |
| ..add(stub) |
| ..add(canonicalMember); |
| } |
| _member = stub; |
| _covariance = combinedMemberSignature.combinedMemberSignatureCovariance; |
| assert( |
| _covariance == |
| new Covariance.fromMember(_member!, forSetter: forSetter), |
| "Unexpected covariance for combined members signature " |
| "$_member. Found $_covariance, expected " |
| "${new Covariance.fromMember(_member!, forSetter: forSetter)}."); |
| return; |
| } |
| } |
| |
| _member = |
| combinedMemberSignature.canonicalMember!.getMember(membersBuilder); |
| _covariance = combinedMemberSignature.combinedMemberSignatureCovariance; |
| } |
| |
| @override |
| Member getMember(ClassMembersBuilder membersBuilder) { |
| _ensureMemberAndCovariance(membersBuilder); |
| return _member!; |
| } |
| |
| @override |
| Covariance getCovariance(ClassMembersBuilder membersBuilder) { |
| _ensureMemberAndCovariance(membersBuilder); |
| return _covariance!; |
| } |
| |
| @override |
| ClassMember get interfaceMember => this; |
| |
| @override |
| bool isObjectMember(ClassBuilder objectClass) { |
| return false; |
| } |
| |
| @override |
| bool isSameDeclaration(ClassMember other) { |
| // TODO(johnniwinther): Optimize this. |
| return false; |
| } |
| |
| @override |
| int get charOffset => declarations.first.charOffset; |
| |
| @override |
| Uri get fileUri => declarations.first.fileUri; |
| |
| @override |
| bool get isAbstract => true; |
| |
| @override |
| String get fullNameForErrors => |
| declarations.map((ClassMember m) => m.fullName).join("%"); |
| |
| @override |
| String get fullName { |
| String suffix = isSetter ? "=" : ""; |
| return "${fullNameForErrors}$suffix"; |
| } |
| |
| @override |
| String toString() => 'SynthesizedInterfaceMember($classBuilder,$name,' |
| '$declarations,forSetter=$forSetter)'; |
| } |
| |
| /// Class member for an inherited concrete member that implements an interface |
| /// member. |
| /// |
| /// This is used to ensure that both the inherited concrete member and the |
| /// interface member is taken into account when computing the resulting [Member] |
| /// node. |
| /// |
| /// This is needed because an interface member, though initially abstract, can |
| /// result in a concrete stub that overrides the concrete member. For instance |
| /// |
| /// class Super { |
| /// method(int i) {} |
| /// } |
| /// class Interface { |
| /// method(covariant int i) {} |
| /// } |
| /// class Class extends Super implements Interface { |
| /// // A concrete forwarding stub is inserted: |
| /// method(covariant int i) => super.method(i); |
| /// } |
| /// class Sub extends Class implements Interface { |
| /// // No forwarding stub should be inserted since `Class.method` is |
| /// // adequate. |
| /// } |
| /// |
| /// |
| /// Here the create stub `Class.method` overrides `Super.method` and should |
| /// be used to determine whether to insert a forwarding stub in subclasses. |
| class InheritedClassMemberImplementsInterface extends SynthesizedMember { |
| final ClassMember inheritedClassMember; |
| final ClassMember implementedInterfaceMember; |
| |
| Member? _member; |
| Covariance? _covariance; |
| |
| InheritedClassMemberImplementsInterface(ClassBuilder classBuilder, Name name, |
| {required this.inheritedClassMember, |
| required this.implementedInterfaceMember, |
| required bool isProperty, |
| required bool forSetter}) |
| // ignore: unnecessary_null_comparison |
| : assert(inheritedClassMember != null), |
| // ignore: unnecessary_null_comparison |
| assert(implementedInterfaceMember != null), |
| super(classBuilder, name, isProperty: isProperty, forSetter: forSetter); |
| |
| void _ensureMemberAndCovariance(ClassMembersBuilder membersBuilder) { |
| if (_member == null) { |
| Member classMember = inheritedClassMember.getMember(membersBuilder); |
| Member interfaceMember = |
| implementedInterfaceMember.getMember(membersBuilder); |
| if (!interfaceMember.isAbstract && |
| interfaceMember.enclosingClass == classBuilder.cls) { |
| /// The interface member resulted in a concrete stub being inserted. |
| /// For instance for `method1` but _not_ for `method2` here: |
| /// |
| /// class Super { |
| /// method1(int i) {} |
| /// method2(covariant int i) {} |
| /// } |
| /// class Interface { |
| /// method1(covariant int i) {} |
| /// method2(int i) {} |
| /// } |
| /// class Class extends Super implements Interface { |
| /// // A concrete forwarding stub is inserted for `method1` since |
| /// // the parameter on `Super.method1` is _not_ marked as |
| /// // covariant: |
| /// method1(covariant int i) => super.method(i); |
| /// // No concrete forwarding stub is inserted for `method2` since |
| /// // the parameter on `Super.method2` is already marked as |
| /// // covariant. |
| /// } |
| /// |
| /// The inserted stub should be used as the resulting member. |
| _member = interfaceMember; |
| _covariance = implementedInterfaceMember.getCovariance(membersBuilder); |
| } else { |
| /// The interface member did not result in an inserted stub or the |
| /// inserted stub was abstract. For instance: |
| /// |
| /// // Opt-in: |
| /// class Super { |
| /// method(int? i) {} |
| /// } |
| /// // Opt-out: |
| /// class Class extends Super { |
| /// // An abstract member signature stub is inserted: |
| /// method(int* i); |
| /// } |
| /// |
| /// The inserted stub should _not_ be used as the resulting member |
| /// since it is abstract and therefore not a class member. |
| _member = classMember; |
| _covariance = inheritedClassMember.getCovariance(membersBuilder); |
| } |
| } |
| } |
| |
| @override |
| Member getMember(ClassMembersBuilder membersBuilder) { |
| _ensureMemberAndCovariance(membersBuilder); |
| return _member!; |
| } |
| |
| @override |
| Covariance getCovariance(ClassMembersBuilder membersBuilder) { |
| _ensureMemberAndCovariance(membersBuilder); |
| return _covariance!; |
| } |
| |
| @override |
| ClassMember get interfaceMember => implementedInterfaceMember; |
| |
| @override |
| bool isObjectMember(ClassBuilder objectClass) { |
| return inheritedClassMember.isObjectMember(objectClass); |
| } |
| |
| @override |
| bool isSameDeclaration(ClassMember other) { |
| // TODO(johnniwinther): Optimize this. |
| return false; |
| } |
| |
| @override |
| int get charOffset => inheritedClassMember.charOffset; |
| |
| @override |
| Uri get fileUri => inheritedClassMember.fileUri; |
| |
| @override |
| bool get hasDeclarations => false; |
| |
| @override |
| bool get isAbstract => false; |
| |
| @override |
| String get fullNameForErrors => inheritedClassMember.fullNameForErrors; |
| |
| @override |
| String get fullName => inheritedClassMember.fullName; |
| |
| @override |
| String toString() => |
| 'InheritedClassMemberImplementsInterface($classBuilder,$name,' |
| 'inheritedClassMember=$inheritedClassMember,' |
| 'implementedInterfaceMember=$implementedInterfaceMember,' |
| 'forSetter=$forSetter)'; |
| } |