| // 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. |
| |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/src/types.dart'; |
| import 'package:kernel/type_algebra.dart'; |
| |
| import '../../base/messages.dart'; |
| import '../../builder/declaration_builders.dart'; |
| import '../../builder/library_builder.dart'; |
| import '../../source/source_class_builder.dart'; |
| import '../../source/source_extension_type_declaration_builder.dart'; |
| import 'class_member.dart'; |
| import 'members_builder.dart'; |
| import 'members_node.dart'; |
| |
| abstract class DelayedCheck { |
| void check(ClassMembersBuilder membersBuilder); |
| } |
| |
| class DelayedOverrideCheck implements DelayedCheck { |
| final SourceClassBuilder _classBuilder; |
| final ClassMember _declaredMember; |
| final Set<ClassMember> _overriddenMembers; |
| final ClassMember? _localMember; |
| |
| DelayedOverrideCheck( |
| this._classBuilder, |
| this._declaredMember, |
| this._overriddenMembers, { |
| required ClassMember? localMember, |
| }) : this._localMember = localMember; |
| |
| @override |
| void check(ClassMembersBuilder membersBuilder) { |
| Member declaredMember = _declaredMember.getMember(membersBuilder); |
| Member? localMember = _localMember?.getMember(membersBuilder); |
| |
| // If the local [ClassMember] didn't produce a local [Member], don't mark |
| // any member as erroneous. |
| if (localMember?.enclosingTypeDeclaration != _classBuilder.cls) { |
| localMember = null; |
| } |
| |
| /// If [_declaredMember] is a class member that is declared in an opt-in |
| /// library but inherited to [_classBuilder] through an opt-out class then |
| /// we need to apply legacy erasure to the declared type to get the |
| /// inherited type. |
| /// |
| /// For interface members this is handled by member signatures but since |
| /// these are abstract they will never be the inherited class member. |
| /// |
| /// For instance: |
| /// |
| /// // Opt in: |
| /// class Super { |
| /// int extendedMethod(int i, {required int j}) => i; |
| /// } |
| /// class Mixin { |
| /// int mixedInMethod(int i, {required int j}) => i; |
| /// } |
| /// // Opt out: |
| /// class Legacy extends Super with Mixin {} |
| /// // Opt in: |
| /// class Class extends Legacy { |
| /// // Valid overrides since the type of `Legacy.extendedMethod` is |
| /// // `int* Function(int*, {int* j})`. |
| /// int? extendedMethod(int? i, {int? j}) => i; |
| /// // Valid overrides since the type of `Legacy.mixedInMethod` is |
| /// // `int* Function(int*, {int* j})`. |
| /// int? mixedInMethod(int? i, {int? j}) => i; |
| /// } |
| /// |
| |
| void callback(Member interfaceMember, bool isSetter) { |
| _classBuilder.checkOverride( |
| membersBuilder.hierarchyBuilder.types, |
| membersBuilder, |
| declaredMember, |
| interfaceMember, |
| isSetter, |
| callback, |
| isInterfaceCheck: !_classBuilder.isMixinApplication, |
| localMember: localMember, |
| ); |
| } |
| |
| for (ClassMember overriddenMember in _overriddenMembers) { |
| callback( |
| overriddenMember.getMember(membersBuilder), |
| _declaredMember.isSetter, |
| ); |
| } |
| } |
| } |
| |
| abstract class DelayedGetterSetterCheck implements DelayedCheck { |
| DeclarationBuilder get declarationBuilder; |
| |
| LibraryBuilder get libraryBuilder => declarationBuilder.libraryBuilder; |
| |
| int get declarationOffset => declarationBuilder.fileOffset; |
| |
| Uri get declarationUri => declarationBuilder.fileUri; |
| |
| void _checkGetterSetter({ |
| required Types types, |
| required Name name, |
| required DartType getterType, |
| required String getterFullName, |
| required int getterOffset, |
| required Uri getterUri, |
| required bool getterIsDeclared, |
| required bool getterIsField, |
| required DartType setterType, |
| required String setterFullName, |
| required int setterOffset, |
| required Uri setterUri, |
| required bool setterIsDeclared, |
| }) { |
| if (getterType is InvalidType || setterType is InvalidType) { |
| // Don't report a problem as something else is wrong that has already |
| // been reported. |
| } else { |
| bool isValid = types.isSubtypeOf(getterType, setterType); |
| if (!isValid) { |
| if (getterIsDeclared && setterIsDeclared) { |
| libraryBuilder.addProblem( |
| codeInvalidGetterSetterType.withArguments( |
| getterType, |
| getterFullName, |
| setterType, |
| setterFullName, |
| ), |
| getterOffset, |
| name.text.length, |
| getterUri, |
| context: [ |
| codeInvalidGetterSetterTypeSetterContext |
| .withArguments(setterFullName) |
| .withLocation(setterUri, setterOffset, name.text.length), |
| ], |
| ); |
| } else if (getterIsDeclared) { |
| Template<Message Function(DartType, String, DartType, String)> |
| template = codeInvalidGetterSetterTypeSetterInheritedGetter; |
| if (getterIsField) { |
| template = codeInvalidGetterSetterTypeSetterInheritedField; |
| } |
| libraryBuilder.addProblem( |
| template.withArguments( |
| getterType, |
| getterFullName, |
| setterType, |
| setterFullName, |
| ), |
| getterOffset, |
| name.text.length, |
| getterUri, |
| context: [ |
| codeInvalidGetterSetterTypeSetterContext |
| .withArguments(setterFullName) |
| .withLocation(setterUri, setterOffset, name.text.length), |
| ], |
| ); |
| } else if (setterIsDeclared) { |
| Template<Message Function(DartType, String, DartType, String)> |
| template = codeInvalidGetterSetterTypeGetterInherited; |
| Template<Message Function(String)> context = |
| codeInvalidGetterSetterTypeGetterContext; |
| if (getterIsField) { |
| template = codeInvalidGetterSetterTypeFieldInherited; |
| context = codeInvalidGetterSetterTypeFieldContext; |
| } |
| libraryBuilder.addProblem( |
| template.withArguments( |
| getterType, |
| getterFullName, |
| setterType, |
| setterFullName, |
| ), |
| setterOffset, |
| name.text.length, |
| setterUri, |
| context: [ |
| context |
| .withArguments(getterFullName) |
| .withLocation(getterUri, getterOffset, name.text.length), |
| ], |
| ); |
| } else { |
| Template<Message Function(DartType, String, DartType, String)> |
| template = codeInvalidGetterSetterTypeBothInheritedGetter; |
| Template<Message Function(String)> context = |
| codeInvalidGetterSetterTypeGetterContext; |
| if (getterIsField) { |
| template = codeInvalidGetterSetterTypeBothInheritedField; |
| context = codeInvalidGetterSetterTypeFieldContext; |
| } |
| libraryBuilder.addProblem( |
| template.withArguments( |
| getterType, |
| getterFullName, |
| setterType, |
| setterFullName, |
| ), |
| declarationOffset, |
| noLength, |
| declarationUri, |
| context: [ |
| context |
| .withArguments(getterFullName) |
| .withLocation(getterUri, getterOffset, name.text.length), |
| codeInvalidGetterSetterTypeSetterContext |
| .withArguments(setterFullName) |
| .withLocation(setterUri, setterOffset, name.text.length), |
| ], |
| ); |
| } |
| } |
| } |
| } |
| } |
| |
| class DelayedClassGetterSetterCheck extends DelayedGetterSetterCheck { |
| final SourceClassBuilder classBuilder; |
| final Name name; |
| final ClassMember getable; |
| final ClassMember setable; |
| |
| DelayedClassGetterSetterCheck( |
| this.classBuilder, |
| this.name, |
| this.getable, |
| this.setable, |
| ); |
| |
| @override |
| DeclarationBuilder get declarationBuilder => classBuilder; |
| |
| @override |
| void check(ClassMembersBuilder membersBuilder) { |
| Class cls = classBuilder.cls; |
| Member getter = getable.getMember(membersBuilder); |
| Member setter = setable.getMember(membersBuilder); |
| if (getter == setter) { |
| return; |
| } |
| if (cls != getter.enclosingClass && |
| getter.enclosingClass == setter.enclosingClass) { |
| return; |
| } |
| |
| Types types = membersBuilder.hierarchyBuilder.types; |
| InterfaceType thisType = classBuilder.thisType; |
| DartType getterType = getter.getterType; |
| if (getter.enclosingClass!.typeParameters.isNotEmpty) { |
| getterType = Substitution.fromPairs( |
| getter.enclosingClass!.typeParameters, |
| types.hierarchy.getTypeArgumentsAsInstanceOf( |
| thisType, |
| getter.enclosingClass!, |
| )!, |
| ).substituteType(getterType); |
| } |
| |
| DartType setterType = setter.setterType; |
| if (setter.enclosingClass!.typeParameters.isNotEmpty) { |
| setterType = Substitution.fromPairs( |
| setter.enclosingClass!.typeParameters, |
| types.hierarchy.getTypeArgumentsAsInstanceOf( |
| thisType, |
| setter.enclosingClass!, |
| )!, |
| ).substituteType(setterType); |
| } |
| Member getterOrigin = getter.memberSignatureOrigin ?? getter; |
| Member setterOrigin = setter.memberSignatureOrigin ?? setter; |
| String getterMemberName = |
| '${getterOrigin.enclosingClass!.name}' |
| '.${getterOrigin.name.text}'; |
| String setterMemberName = |
| '${setterOrigin.enclosingClass!.name}' |
| '.${setterOrigin.name.text}'; |
| |
| _checkGetterSetter( |
| types: types, |
| name: name, |
| getterType: getterType, |
| getterFullName: getterMemberName, |
| getterOffset: getterOrigin.fileOffset, |
| getterUri: getterOrigin.fileUri, |
| getterIsDeclared: getterOrigin.enclosingClass == cls, |
| getterIsField: getterOrigin is Field, |
| setterType: setterType, |
| setterFullName: setterMemberName, |
| setterOffset: setterOrigin.fileOffset, |
| setterUri: setterOrigin.fileUri, |
| setterIsDeclared: setterOrigin.enclosingClass == cls, |
| ); |
| } |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| class DelayedExtensionTypeGetterSetterCheck extends DelayedGetterSetterCheck { |
| final SourceExtensionTypeDeclarationBuilder extensionTypeDeclarationBuilder; |
| final Name name; |
| final ClassMember getable; |
| final ClassMember setable; |
| |
| DelayedExtensionTypeGetterSetterCheck( |
| this.extensionTypeDeclarationBuilder, |
| this.name, |
| this.getable, |
| this.setable, |
| ); |
| |
| @override |
| DeclarationBuilder get declarationBuilder => extensionTypeDeclarationBuilder; |
| |
| @override |
| void check(ClassMembersBuilder membersBuilder) { |
| if (getable.isSameDeclaration(setable)) { |
| return; |
| } |
| |
| MemberResult getterResult = getable.getMemberResult(membersBuilder); |
| MemberResult setterResult = setable.getMemberResult(membersBuilder); |
| |
| Types types = membersBuilder.hierarchyBuilder.types; |
| ExtensionType thisType = types.coreTypes.thisExtensionType( |
| extensionTypeDeclarationBuilder.extensionTypeDeclaration, |
| Nullability.nonNullable, |
| ); |
| |
| DartType getterType = getterResult.getMemberType(membersBuilder, thisType); |
| |
| DartType setterType = setterResult.getMemberType(membersBuilder, thisType); |
| |
| _checkGetterSetter( |
| types: types, |
| name: name, |
| getterType: getterType, |
| getterFullName: getterResult.fullName, |
| getterOffset: getterResult.fileOffset, |
| getterUri: getterResult.fileUri, |
| getterIsDeclared: |
| getable.isSourceDeclaration && |
| getable.declarationBuilder == declarationBuilder, |
| getterIsField: getterResult.isDeclaredAsField, |
| setterType: setterType, |
| setterFullName: setterResult.fullName, |
| setterOffset: setterResult.fileOffset, |
| setterUri: setterResult.fileUri, |
| setterIsDeclared: |
| setable.isSourceDeclaration && |
| setable.declarationBuilder == declarationBuilder, |
| ); |
| } |
| } |
| |
| class DelayedTypeComputation { |
| final ClassMembersNodeBuilder builder; |
| final ClassMember declaredMember; |
| final Set<ClassMember> overriddenMembers; |
| bool _computed = false; |
| |
| DelayedTypeComputation( |
| this.builder, |
| this.declaredMember, |
| this.overriddenMembers, |
| ) : assert(declaredMember.isSourceDeclaration); |
| |
| void compute(ClassMembersBuilder membersBuilder) { |
| if (_computed) return; |
| declaredMember.inferType(membersBuilder); |
| _computed = true; |
| if (declaredMember.isProperty) { |
| builder.inferPropertySignature( |
| membersBuilder, |
| declaredMember, |
| overriddenMembers, |
| ); |
| } else { |
| builder.inferMethodSignature( |
| membersBuilder, |
| declaredMember, |
| overriddenMembers, |
| ); |
| } |
| } |
| |
| @override |
| String toString() => |
| 'DelayedTypeComputation(' |
| '${builder.classBuilder.name},$declaredMember,$overriddenMembers)'; |
| } |