| // Copyright (c) 2016, 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.source_class_builder; |
| |
| import 'package:kernel/ast.dart' |
| show Class, Constructor, Member, Supertype, TreeNode; |
| |
| import '../dill/dill_member_builder.dart' show DillMemberBuilder; |
| |
| import '../fasta_codes.dart' |
| show |
| Message, |
| noLength, |
| templateBadTypeVariableInSupertype, |
| templateConflictsWithConstructor, |
| templateConflictsWithFactory, |
| templateConflictsWithMember, |
| templateConflictsWithMemberWarning, |
| templateConflictsWithSetter, |
| templateConflictsWithSetterWarning, |
| templateSupertypeIsIllegal; |
| |
| import '../kernel/kernel_builder.dart' |
| show |
| ClassBuilder, |
| ConstructorReferenceBuilder, |
| Declaration, |
| KernelClassBuilder, |
| KernelFieldBuilder, |
| KernelFunctionBuilder, |
| KernelInvalidTypeBuilder, |
| KernelLibraryBuilder, |
| KernelNamedTypeBuilder, |
| KernelTypeBuilder, |
| KernelTypeVariableBuilder, |
| LibraryBuilder, |
| MetadataBuilder, |
| Scope, |
| TypeVariableBuilder, |
| compareProcedures; |
| |
| import '../kernel/kernel_shadow_ast.dart' show ShadowClass; |
| |
| import '../kernel/type_algorithms.dart' show Variance, computeVariance; |
| |
| import '../problems.dart' show unexpected, unhandled; |
| |
| ShadowClass initializeClass( |
| ShadowClass cls, |
| List<TypeVariableBuilder> typeVariables, |
| String name, |
| KernelLibraryBuilder parent, |
| int startCharOffset, |
| int charOffset, |
| int charEndOffset) { |
| cls ??= new ShadowClass( |
| name: name, |
| typeParameters: |
| KernelTypeVariableBuilder.kernelTypeParametersFromBuilders( |
| typeVariables)); |
| cls.fileUri ??= parent.fileUri; |
| if (cls.startFileOffset == TreeNode.noOffset) { |
| cls.startFileOffset = startCharOffset; |
| } |
| if (cls.fileOffset == TreeNode.noOffset) { |
| cls.fileOffset = charOffset; |
| } |
| if (cls.fileEndOffset == TreeNode.noOffset) { |
| cls.fileEndOffset = charEndOffset; |
| } |
| |
| return cls; |
| } |
| |
| class SourceClassBuilder extends KernelClassBuilder |
| implements Comparable<SourceClassBuilder> { |
| @override |
| final Class actualCls; |
| |
| final List<ConstructorReferenceBuilder> constructorReferences; |
| |
| KernelTypeBuilder mixedInType; |
| |
| bool isMixinDeclaration; |
| |
| SourceClassBuilder( |
| List<MetadataBuilder> metadata, |
| int modifiers, |
| String name, |
| List<TypeVariableBuilder> typeVariables, |
| KernelTypeBuilder supertype, |
| List<KernelTypeBuilder> interfaces, |
| Scope scope, |
| Scope constructors, |
| LibraryBuilder parent, |
| this.constructorReferences, |
| int startCharOffset, |
| int charOffset, |
| int charEndOffset, |
| {Class cls, |
| this.mixedInType, |
| this.isMixinDeclaration = false}) |
| : actualCls = initializeClass(cls, typeVariables, name, parent, |
| startCharOffset, charOffset, charEndOffset), |
| super(metadata, modifiers, name, typeVariables, supertype, interfaces, |
| scope, constructors, parent, charOffset) { |
| ShadowClass.setBuilder(this.cls, this); |
| } |
| |
| @override |
| ShadowClass get cls => origin.actualCls; |
| |
| @override |
| KernelLibraryBuilder get library => super.library; |
| |
| Class build(KernelLibraryBuilder library, LibraryBuilder coreLibrary) { |
| void buildBuilders(String name, Declaration declaration) { |
| do { |
| if (declaration.parent != this) { |
| if (fileUri != declaration.parent.fileUri) { |
| unexpected("$fileUri", "${declaration.parent.fileUri}", charOffset, |
| fileUri); |
| } else { |
| unexpected(fullNameForErrors, declaration.parent?.fullNameForErrors, |
| charOffset, fileUri); |
| } |
| } else if (declaration is KernelFieldBuilder) { |
| // TODO(ahe): It would be nice to have a common interface for the |
| // build method to avoid duplicating these two cases. |
| Member field = declaration.build(library); |
| if (!declaration.isPatch && declaration.next == null) { |
| cls.addMember(field); |
| } |
| } else if (declaration is KernelFunctionBuilder) { |
| Member function = declaration.build(library); |
| function.parent = cls; |
| if (!declaration.isPatch && declaration.next == null) { |
| cls.addMember(function); |
| } |
| } else { |
| unhandled("${declaration.runtimeType}", "buildBuilders", |
| declaration.charOffset, declaration.fileUri); |
| } |
| declaration = declaration.next; |
| } while (declaration != null); |
| } |
| |
| scope.forEach(buildBuilders); |
| constructors.forEach(buildBuilders); |
| supertype = checkSupertype(supertype); |
| actualCls.supertype = |
| supertype?.buildSupertype(library, charOffset, fileUri); |
| if (!isMixinDeclaration && |
| actualCls.supertype != null && |
| actualCls.superclass.isMixinDeclaration) { |
| // Declared mixins have interfaces that can be implemented, but they |
| // cannot be extended. However, a mixin declaration with a single |
| // superclass constraint is encoded with the constraint as the supertype, |
| // and that is allowed to be a mixin's interface. |
| library.addProblem( |
| templateSupertypeIsIllegal.withArguments(actualCls.superclass.name), |
| charOffset, |
| noLength, |
| fileUri); |
| actualCls.supertype = null; |
| } |
| if (actualCls.supertype == null && supertype is! KernelNamedTypeBuilder) { |
| supertype = null; |
| } |
| mixedInType = checkSupertype(mixedInType); |
| actualCls.mixedInType = |
| mixedInType?.buildMixedInType(library, charOffset, fileUri); |
| if (actualCls.mixedInType == null && |
| mixedInType is! KernelNamedTypeBuilder) { |
| mixedInType = null; |
| } |
| actualCls.isMixinDeclaration = isMixinDeclaration; |
| // TODO(ahe): If `cls.supertype` is null, and this isn't Object, report a |
| // compile-time error. |
| cls.isAbstract = isAbstract; |
| if (interfaces != null) { |
| for (int i = 0; i < interfaces.length; ++i) { |
| interfaces[i] = checkSupertype(interfaces[i]); |
| Supertype supertype = |
| interfaces[i].buildSupertype(library, charOffset, fileUri); |
| if (supertype != null) { |
| // TODO(ahe): Report an error if supertype is null. |
| actualCls.implementedTypes.add(supertype); |
| } |
| } |
| } |
| |
| constructors.forEach((String name, Declaration constructor) { |
| Declaration member = scopeBuilder[name]; |
| if (member == null) return; |
| if (!member.isStatic) return; |
| // TODO(ahe): Revisit these messages. It seems like the last two should |
| // be `context` parameter to this message. |
| addProblem(templateConflictsWithMember.withArguments(name), |
| constructor.charOffset, noLength); |
| if (constructor.isFactory) { |
| addProblem( |
| templateConflictsWithFactory.withArguments("${this.name}.${name}"), |
| member.charOffset, |
| noLength); |
| } else { |
| addProblem( |
| templateConflictsWithConstructor |
| .withArguments("${this.name}.${name}"), |
| member.charOffset, |
| noLength); |
| } |
| }); |
| |
| scope.setters.forEach((String name, Declaration setter) { |
| Declaration member = scopeBuilder[name]; |
| if (member == null || |
| !(member.isField && !member.isFinal && !member.isConst || |
| member.isRegularMethod && member.isStatic && setter.isStatic)) { |
| return; |
| } |
| if (member.isInstanceMember == setter.isInstanceMember) { |
| addProblem(templateConflictsWithMember.withArguments(name), |
| setter.charOffset, noLength); |
| // TODO(ahe): Context argument to previous message? |
| addProblem(templateConflictsWithSetter.withArguments(name), |
| member.charOffset, noLength); |
| } else { |
| addProblem(templateConflictsWithMemberWarning.withArguments(name), |
| setter.charOffset, noLength); |
| // TODO(ahe): Context argument to previous message? |
| addProblem(templateConflictsWithSetterWarning.withArguments(name), |
| member.charOffset, noLength); |
| } |
| }); |
| |
| scope.setters.forEach((String name, Declaration setter) { |
| Declaration constructor = constructorScopeBuilder[name]; |
| if (constructor == null || !setter.isStatic) return; |
| addProblem(templateConflictsWithConstructor.withArguments(name), |
| setter.charOffset, noLength); |
| addProblem(templateConflictsWithSetter.withArguments(name), |
| constructor.charOffset, noLength); |
| }); |
| |
| cls.procedures.sort(compareProcedures); |
| return cls; |
| } |
| |
| KernelTypeBuilder checkSupertype(KernelTypeBuilder supertype) { |
| if (typeVariables == null || supertype == null) return supertype; |
| Message message; |
| for (int i = 0; i < typeVariables.length; ++i) { |
| int variance = computeVariance(typeVariables[i], supertype); |
| if (variance == Variance.contravariant || |
| variance == Variance.invariant) { |
| message = templateBadTypeVariableInSupertype.withArguments( |
| typeVariables[i].name, supertype.name); |
| library.addProblem(message, charOffset, noLength, fileUri); |
| } |
| } |
| if (message != null) { |
| return new KernelNamedTypeBuilder(supertype.name, null) |
| ..bind(new KernelInvalidTypeBuilder(supertype.name, |
| message.withLocation(fileUri, charOffset, noLength))); |
| } |
| return supertype; |
| } |
| |
| void addSyntheticConstructor(Constructor constructor) { |
| String name = constructor.name.name; |
| cls.constructors.add(constructor); |
| constructor.parent = cls; |
| DillMemberBuilder memberBuilder = new DillMemberBuilder(constructor, this); |
| memberBuilder.next = constructorScopeBuilder[name]; |
| constructorScopeBuilder.addMember(name, memberBuilder); |
| } |
| |
| void prepareTopLevelInference() { |
| scope.forEach((String name, Declaration declaration) { |
| do { |
| if (declaration is KernelFieldBuilder) { |
| declaration.prepareTopLevelInference(); |
| } |
| declaration = declaration.next; |
| } while (declaration != null); |
| }); |
| if (!isPatch) { |
| cls.setupApiMembers(library.loader.interfaceResolver); |
| } |
| } |
| |
| @override |
| int finishPatch() { |
| if (!isPatch) return 0; |
| |
| // TODO(ahe): restore file-offset once we track both origin and patch file |
| // URIs. See https://github.com/dart-lang/sdk/issues/31579 |
| cls.annotations.forEach((m) => m.fileOffset = origin.cls.fileOffset); |
| |
| int count = 0; |
| scope.forEach((String name, Declaration declaration) { |
| count += declaration.finishPatch(); |
| }); |
| constructors.forEach((String name, Declaration declaration) { |
| count += declaration.finishPatch(); |
| }); |
| return count; |
| } |
| |
| List<Declaration> computeDirectSupertypes(ClassBuilder objectClass) { |
| final List<Declaration> result = <Declaration>[]; |
| final KernelTypeBuilder supertype = this.supertype; |
| if (supertype != null) { |
| result.add(supertype.declaration); |
| } else if (objectClass != this) { |
| result.add(objectClass); |
| } |
| final List<KernelTypeBuilder> interfaces = this.interfaces; |
| if (interfaces != null) { |
| for (int i = 0; i < interfaces.length; i++) { |
| KernelTypeBuilder interface = interfaces[i]; |
| result.add(interface.declaration); |
| } |
| } |
| final KernelTypeBuilder mixedInType = this.mixedInType; |
| if (mixedInType != null) { |
| result.add(mixedInType.declaration); |
| } |
| return result; |
| } |
| |
| @override |
| int compareTo(SourceClassBuilder other) { |
| int result = "$fileUri".compareTo("${other.fileUri}"); |
| if (result != 0) return result; |
| return charOffset.compareTo(other.charOffset); |
| } |
| } |