| // 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. |
| |
| /// ----------------------------------------------------------------------- |
| /// WHEN CHANGING THIS FILE: |
| /// ----------------------------------------------------------------------- |
| /// |
| /// If you are adding/removing/modifying fields/classes of the AST, you must |
| /// also update the following files: |
| /// |
| /// - binary/ast_to_binary.dart |
| /// - binary/ast_from_binary.dart |
| /// - text/ast_to_text.dart |
| /// - clone.dart |
| /// - binary.md |
| /// - type_checker.dart (if relevant) |
| /// |
| /// ----------------------------------------------------------------------- |
| /// ERROR HANDLING |
| /// ----------------------------------------------------------------------- |
| /// |
| /// As a rule of thumb, errors that can be detected statically are handled by |
| /// the frontend, typically by translating the erroneous code into a 'throw' or |
| /// a call to 'noSuchMethod'. |
| /// |
| /// For example, there are no arity mismatches in static invocations, and |
| /// there are no direct invocations of a constructor on a abstract class. |
| /// |
| /// ----------------------------------------------------------------------- |
| /// STATIC vs TOP-LEVEL |
| /// ----------------------------------------------------------------------- |
| /// |
| /// The term `static` includes both static class members and top-level members. |
| /// |
| /// "Static class member" is the preferred term for non-top level statics. |
| /// |
| /// Static class members are not lifted to the library level because mirrors |
| /// and stack traces can observe that they are class members. |
| /// |
| /// ----------------------------------------------------------------------- |
| /// PROCEDURES |
| /// ----------------------------------------------------------------------- |
| /// |
| /// "Procedure" is an umbrella term for method, getter, setter, index-getter, |
| /// index-setter, operator overloader, and factory constructor. |
| /// |
| /// Generative constructors, field initializers, local functions are NOT |
| /// procedures. |
| /// |
| /// ----------------------------------------------------------------------- |
| /// TRANSFORMATIONS |
| /// ----------------------------------------------------------------------- |
| /// |
| /// AST transformations can be performed using [TreeNode.replaceWith] or the |
| /// [Transformer] visitor class. |
| /// |
| /// Use [Transformer] for bulk transformations that are likely to transform lots |
| /// of nodes, and [TreeNode.replaceWith] for sparse transformations that mutate |
| /// relatively few nodes. Or use whichever is more convenient. |
| /// |
| /// The AST can also be mutated by direct field manipulation, but the user then |
| /// has to update parent pointers manually. |
| /// |
| library kernel.ast; |
| |
| import 'dart:collection' show ListBase; |
| import 'dart:convert' show utf8; |
| |
| import 'visitor.dart'; |
| export 'visitor.dart'; |
| |
| import 'canonical_name.dart' show CanonicalName, Reference; |
| export 'canonical_name.dart' show CanonicalName, Reference; |
| |
| import 'default_language_version.dart' show defaultLanguageVersion; |
| export 'default_language_version.dart' show defaultLanguageVersion; |
| |
| import 'transformations/flags.dart'; |
| import 'text/ast_to_text.dart' as astToText; |
| import 'core_types.dart'; |
| import 'class_hierarchy.dart'; |
| import 'type_algebra.dart'; |
| import 'type_environment.dart'; |
| import 'src/assumptions.dart'; |
| import 'src/non_null.dart'; |
| import 'src/printer.dart'; |
| import 'src/text_util.dart'; |
| |
| /// Any type of node in the IR. |
| abstract class Node { |
| const Node(); |
| |
| R accept<R>(Visitor<R> v); |
| R accept1<R, A>(Visitor1<R, A> v, A arg); |
| void visitChildren(Visitor v); |
| |
| /// Returns the textual representation of this node for use in debugging. |
| /// |
| /// [toString] should only be used for debugging, but should not leak. |
| /// |
| /// The data is generally bare-bones, but can easily be updated for your |
| /// specific debugging needs. |
| @override |
| String toString(); |
| |
| /// Returns the textual representation of this node for use in debugging. |
| /// |
| /// [toStringInternal] should only be used for debugging, but should not leak. |
| /// |
| /// The data is generally bare-bones, but can easily be updated for your |
| /// specific debugging needs. |
| /// |
| /// This method is called internally by toString methods to create conciser |
| /// textual representations. |
| String toStringInternal() => toText(defaultAstTextStrategy); |
| |
| /// Returns the textual representation of this node for use in debugging. |
| /// |
| /// Note that this adds some nodes to a static map to ensure consistent |
| /// naming, but that it thus also leaks memory. [leakingDebugToString] should |
| /// thus only be used for debugging and short-running test tools. |
| /// |
| /// Synthetic names are cached globally to retain consistency across different |
| /// [leakingDebugToString] calls (hence the memory leak). |
| String leakingDebugToString() => astToText.debugNodeToString(this); |
| |
| String toText(AstTextStrategy strategy) { |
| AstPrinter printer = new AstPrinter(strategy); |
| toTextInternal(printer); |
| return printer.getText(); |
| } |
| |
| void toTextInternal(AstPrinter printer); |
| } |
| |
| /// A mutable AST node with a parent pointer. |
| /// |
| /// This is anything other than [Name] and [DartType] nodes. |
| abstract class TreeNode extends Node { |
| static int _hashCounter = 0; |
| @override |
| final int hashCode = _hashCounter = (_hashCounter + 1) & 0x3fffffff; |
| static const int noOffset = -1; |
| |
| TreeNode? parent; |
| |
| /// Offset in the source file it comes from. |
| /// |
| /// Valid values are from 0 and up, or -1 ([noOffset]) if the file offset is |
| /// not available (this is the default if none is specifically set). |
| int fileOffset = noOffset; |
| |
| @override |
| R accept<R>(TreeVisitor<R> v); |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg); |
| @override |
| void visitChildren(Visitor v); |
| void transformChildren(Transformer v); |
| void transformOrRemoveChildren(RemovingTransformer v); |
| |
| /// Replaces [child] with [replacement]. |
| /// |
| /// The caller is responsible for ensuring that the AST remains a tree. In |
| /// particular, [replacement] should be an orphan or be part of an orphaned |
| /// subtree. |
| /// |
| /// Has no effect if [child] is not actually a child of this node. |
| /// |
| /// [replacement] must be non-null. |
| void replaceChild(TreeNode child, TreeNode replacement) { |
| // ignore: unnecessary_null_comparison |
| assert(replacement != null); |
| transformChildren(new _ChildReplacer(child, replacement)); |
| } |
| |
| /// Inserts another node in place of this one. |
| /// |
| /// The caller is responsible for ensuring that the AST remains a tree. In |
| /// particular, [replacement] should be an orphan or be part of an orphaned |
| /// subtree. |
| /// |
| /// [replacement] must be non-null. |
| void replaceWith(TreeNode replacement) { |
| // ignore: unnecessary_null_comparison |
| assert(replacement != null); |
| parent!.replaceChild(this, replacement); |
| parent = null; |
| } |
| |
| // TODO(johnniwinther): Make this non-nullable. |
| Component? get enclosingComponent => parent?.enclosingComponent; |
| |
| /// Returns the best known source location of the given AST node, or `null` if |
| /// the node is orphaned. |
| /// |
| /// This getter is intended for diagnostics and debugging, and should be |
| /// avoided in production code. |
| Location? get location { |
| if (fileOffset == noOffset) return parent?.location; |
| return _getLocationInEnclosingFile(fileOffset); |
| } |
| |
| Location? _getLocationInEnclosingFile(int offset) { |
| return parent?._getLocationInEnclosingFile(offset); |
| } |
| } |
| |
| /// An AST node that can be referenced by other nodes. |
| /// |
| /// There is a single [reference] belonging to this node, providing a level of |
| /// indirection that is needed during serialization. |
| abstract class NamedNode extends TreeNode { |
| final Reference reference; |
| |
| NamedNode(Reference? reference) |
| : this.reference = reference ?? new Reference() { |
| this.reference.node = this; |
| } |
| |
| /// This is an advanced feature. |
| /// |
| /// See [Component.relink] for a comprehensive description. |
| /// |
| /// Makes sure the reference in this named node points to itself. |
| void _relinkNode() { |
| this.reference.node = this; |
| } |
| } |
| |
| abstract class FileUriNode extends TreeNode { |
| /// The URI of the source file this node was loaded from. |
| Uri get fileUri; |
| } |
| |
| abstract class Annotatable extends TreeNode { |
| List<Expression> get annotations; |
| void addAnnotation(Expression node); |
| } |
| |
| // ------------------------------------------------------------------------ |
| // LIBRARIES and CLASSES |
| // ------------------------------------------------------------------------ |
| |
| enum NonNullableByDefaultCompiledMode { Weak, Strong, Agnostic, Invalid } |
| |
| class Library extends NamedNode |
| implements Annotatable, Comparable<Library>, FileUriNode { |
| /// An import path to this library. |
| /// |
| /// The [Uri] should have the `dart`, `package`, `app`, or `file` scheme. |
| /// |
| /// If the URI has the `app` scheme, it is relative to the application root. |
| Uri importUri; |
| |
| /// The URI of the source file this library was loaded from. |
| @override |
| Uri fileUri; |
| |
| Version? _languageVersion; |
| Version get languageVersion => _languageVersion ?? defaultLanguageVersion; |
| |
| void setLanguageVersion(Version languageVersion) { |
| // ignore: unnecessary_null_comparison |
| if (languageVersion == null) { |
| throw new StateError("Trying to set language version 'null'"); |
| } |
| _languageVersion = languageVersion; |
| } |
| |
| static const int SyntheticFlag = 1 << 0; |
| static const int NonNullableByDefaultFlag = 1 << 1; |
| static const int NonNullableByDefaultModeBit1 = 1 << 2; |
| static const int NonNullableByDefaultModeBit2 = 1 << 3; |
| |
| int flags = 0; |
| |
| /// If true, the library is synthetic, for instance library that doesn't |
| /// represents an actual file and is created as the result of error recovery. |
| bool get isSynthetic => flags & SyntheticFlag != 0; |
| void set isSynthetic(bool value) { |
| flags = value ? (flags | SyntheticFlag) : (flags & ~SyntheticFlag); |
| } |
| |
| bool get isNonNullableByDefault => (flags & NonNullableByDefaultFlag) != 0; |
| void set isNonNullableByDefault(bool value) { |
| flags = value |
| ? (flags | NonNullableByDefaultFlag) |
| : (flags & ~NonNullableByDefaultFlag); |
| } |
| |
| NonNullableByDefaultCompiledMode get nonNullableByDefaultCompiledMode { |
| bool bit1 = (flags & NonNullableByDefaultModeBit1) != 0; |
| bool bit2 = (flags & NonNullableByDefaultModeBit2) != 0; |
| if (!bit1 && !bit2) return NonNullableByDefaultCompiledMode.Weak; |
| if (bit1 && !bit2) return NonNullableByDefaultCompiledMode.Strong; |
| if (bit1 && bit2) return NonNullableByDefaultCompiledMode.Agnostic; |
| if (!bit1 && bit2) return NonNullableByDefaultCompiledMode.Invalid; |
| throw new StateError("Unused bit-pattern for compilation mode"); |
| } |
| |
| void set nonNullableByDefaultCompiledMode( |
| NonNullableByDefaultCompiledMode mode) { |
| switch (mode) { |
| case NonNullableByDefaultCompiledMode.Weak: |
| flags = (flags & ~NonNullableByDefaultModeBit1) & |
| ~NonNullableByDefaultModeBit2; |
| break; |
| case NonNullableByDefaultCompiledMode.Strong: |
| flags = (flags | NonNullableByDefaultModeBit1) & |
| ~NonNullableByDefaultModeBit2; |
| break; |
| case NonNullableByDefaultCompiledMode.Agnostic: |
| flags = (flags | NonNullableByDefaultModeBit1) | |
| NonNullableByDefaultModeBit2; |
| break; |
| case NonNullableByDefaultCompiledMode.Invalid: |
| flags = (flags & ~NonNullableByDefaultModeBit1) | |
| NonNullableByDefaultModeBit2; |
| break; |
| } |
| } |
| |
| String? name; |
| |
| /// Problems in this [Library] encoded as json objects. |
| /// |
| /// Note that this field can be null, and by convention should be null if the |
| /// list is empty. |
| List<String>? problemsAsJson; |
| |
| @override |
| List<Expression> annotations; |
| |
| List<LibraryDependency> dependencies; |
| |
| /// References to nodes exported by `export` declarations that: |
| /// - aren't ambiguous, or |
| /// - aren't hidden by local declarations. |
| final List<Reference> additionalExports = <Reference>[]; |
| |
| @informative |
| List<LibraryPart> parts; |
| |
| List<Typedef> _typedefs; |
| List<Class> _classes; |
| List<Extension> _extensions; |
| List<Procedure> _procedures; |
| List<Field> _fields; |
| |
| Library(this.importUri, |
| {this.name, |
| List<Expression>? annotations, |
| List<LibraryDependency>? dependencies, |
| List<LibraryPart>? parts, |
| List<Typedef>? typedefs, |
| List<Class>? classes, |
| List<Extension>? extensions, |
| List<Procedure>? procedures, |
| List<Field>? fields, |
| required this.fileUri, |
| Reference? reference}) |
| // ignore: unnecessary_null_comparison |
| : assert(fileUri != null), |
| this.annotations = annotations ?? <Expression>[], |
| this.dependencies = dependencies ?? <LibraryDependency>[], |
| this.parts = parts ?? <LibraryPart>[], |
| this._typedefs = typedefs ?? <Typedef>[], |
| this._classes = classes ?? <Class>[], |
| this._extensions = extensions ?? <Extension>[], |
| this._procedures = procedures ?? <Procedure>[], |
| this._fields = fields ?? <Field>[], |
| super(reference) { |
| setParents(this.dependencies, this); |
| setParents(this.parts, this); |
| setParents(this._typedefs, this); |
| setParents(this._classes, this); |
| setParents(this._extensions, this); |
| setParents(this._procedures, this); |
| setParents(this._fields, this); |
| } |
| |
| List<Typedef> get typedefs => _typedefs; |
| |
| /// Internal. Should *ONLY* be used from within kernel. |
| /// |
| /// Used for adding typedefs when reading the dill file. |
| void set typedefsInternal(List<Typedef> typedefs) { |
| _typedefs = typedefs; |
| } |
| |
| List<Class> get classes => _classes; |
| |
| /// Internal. Should *ONLY* be used from within kernel. |
| /// |
| /// Used for adding classes when reading the dill file. |
| void set classesInternal(List<Class> classes) { |
| _classes = classes; |
| } |
| |
| List<Extension> get extensions => _extensions; |
| |
| /// Internal. Should *ONLY* be used from within kernel. |
| /// |
| /// Used for adding extensions when reading the dill file. |
| void set extensionsInternal(List<Extension> extensions) { |
| _extensions = extensions; |
| } |
| |
| List<Procedure> get procedures => _procedures; |
| |
| /// Internal. Should *ONLY* be used from within kernel. |
| /// |
| /// Used for adding procedures when reading the dill file. |
| void set proceduresInternal(List<Procedure> procedures) { |
| _procedures = procedures; |
| } |
| |
| List<Field> get fields => _fields; |
| |
| /// Internal. Should *ONLY* be used from within kernel. |
| /// |
| /// Used for adding fields when reading the dill file. |
| void set fieldsInternal(List<Field> fields) { |
| _fields = fields; |
| } |
| |
| Nullability get nullable { |
| return isNonNullableByDefault ? Nullability.nullable : Nullability.legacy; |
| } |
| |
| Nullability get nonNullable { |
| return isNonNullableByDefault |
| ? Nullability.nonNullable |
| : Nullability.legacy; |
| } |
| |
| Nullability nullableIfTrue(bool isNullable) { |
| if (isNonNullableByDefault) { |
| return isNullable ? Nullability.nullable : Nullability.nonNullable; |
| } |
| return Nullability.legacy; |
| } |
| |
| /// Returns the top-level fields and procedures defined in this library. |
| /// |
| /// This getter is for convenience, not efficiency. Consider manually |
| /// iterating the members to speed up code in production. |
| Iterable<Member> get members => |
| <Iterable<Member>>[fields, procedures].expand((x) => x); |
| |
| @override |
| void addAnnotation(Expression node) { |
| node.parent = this; |
| annotations.add(node); |
| } |
| |
| void addClass(Class class_) { |
| class_.parent = this; |
| classes.add(class_); |
| } |
| |
| void addExtension(Extension extension) { |
| extension.parent = this; |
| extensions.add(extension); |
| } |
| |
| void addField(Field field) { |
| field.parent = this; |
| fields.add(field); |
| } |
| |
| void addProcedure(Procedure procedure) { |
| procedure.parent = this; |
| procedures.add(procedure); |
| } |
| |
| void addTypedef(Typedef typedef_) { |
| typedef_.parent = this; |
| typedefs.add(typedef_); |
| } |
| |
| void computeCanonicalNames() { |
| CanonicalName canonicalName = this.reference.canonicalName!; |
| for (int i = 0; i < typedefs.length; ++i) { |
| Typedef typedef_ = typedefs[i]; |
| canonicalName.getChildFromTypedef(typedef_).bindTo(typedef_.reference); |
| } |
| for (int i = 0; i < fields.length; ++i) { |
| Field field = fields[i]; |
| canonicalName.getChildFromField(field).bindTo(field.fieldReference); |
| canonicalName |
| .getChildFromFieldGetter(field) |
| .bindTo(field.getterReference); |
| if (field.hasSetter) { |
| canonicalName |
| .getChildFromFieldSetter(field) |
| .bindTo(field.setterReference!); |
| } |
| } |
| for (int i = 0; i < procedures.length; ++i) { |
| Procedure member = procedures[i]; |
| canonicalName.getChildFromProcedure(member).bindTo(member.reference); |
| } |
| for (int i = 0; i < classes.length; ++i) { |
| Class class_ = classes[i]; |
| canonicalName.getChild(class_.name).bindTo(class_.reference); |
| class_.computeCanonicalNames(); |
| } |
| for (int i = 0; i < extensions.length; ++i) { |
| Extension extension = extensions[i]; |
| canonicalName.getChild(extension.name).bindTo(extension.reference); |
| } |
| } |
| |
| /// This is an advanced feature. Use of this method should be coordinated |
| /// with the kernel team. |
| /// |
| /// See [Component.relink] for a comprehensive description. |
| /// |
| /// Makes sure all references in named nodes in this library points to said |
| /// named node. |
| void relink() { |
| _relinkNode(); |
| for (int i = 0; i < typedefs.length; ++i) { |
| Typedef typedef_ = typedefs[i]; |
| typedef_._relinkNode(); |
| } |
| for (int i = 0; i < fields.length; ++i) { |
| Field field = fields[i]; |
| field._relinkNode(); |
| } |
| for (int i = 0; i < procedures.length; ++i) { |
| Procedure member = procedures[i]; |
| member._relinkNode(); |
| } |
| for (int i = 0; i < classes.length; ++i) { |
| Class class_ = classes[i]; |
| class_.relink(); |
| } |
| for (int i = 0; i < extensions.length; ++i) { |
| Extension extension = extensions[i]; |
| extension._relinkNode(); |
| } |
| } |
| |
| void addDependency(LibraryDependency node) { |
| dependencies.add(node..parent = this); |
| } |
| |
| void addPart(LibraryPart node) { |
| parts.add(node..parent = this); |
| } |
| |
| @override |
| R accept<R>(TreeVisitor<R> v) => v.visitLibrary(this); |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitLibrary(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(annotations, v); |
| visitList(dependencies, v); |
| visitList(parts, v); |
| visitList(typedefs, v); |
| visitList(classes, v); |
| visitList(extensions, v); |
| visitList(procedures, v); |
| visitList(fields, v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(annotations, this); |
| v.transformList(dependencies, this); |
| v.transformList(parts, this); |
| v.transformList(typedefs, this); |
| v.transformList(classes, this); |
| v.transformList(extensions, this); |
| v.transformList(procedures, this); |
| v.transformList(fields, this); |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformExpressionList(annotations, this); |
| v.transformLibraryDependencyList(dependencies, this); |
| v.transformLibraryPartList(parts, this); |
| v.transformTypedefList(typedefs, this); |
| v.transformClassList(classes, this); |
| v.transformExtensionList(extensions, this); |
| v.transformProcedureList(procedures, this); |
| v.transformFieldList(fields, this); |
| } |
| |
| static int _libraryIdCounter = 0; |
| int _libraryId = ++_libraryIdCounter; |
| int get libraryIdForTesting => _libraryId; |
| |
| @override |
| int compareTo(Library other) => _libraryId - other._libraryId; |
| |
| /// Returns a possibly synthesized name for this library, consistent with |
| /// the names across all [toString] calls. |
| @override |
| String toString() => libraryNameToString(this); |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write(libraryNameToString(this)); |
| } |
| |
| @override |
| Location? _getLocationInEnclosingFile(int offset) { |
| return _getLocationInComponent(enclosingComponent, fileUri, offset); |
| } |
| |
| @override |
| String leakingDebugToString() => astToText.debugLibraryToString(this); |
| } |
| |
| /// An import or export declaration in a library. |
| /// |
| /// It can represent any of the following forms, |
| /// |
| /// import <url>; |
| /// import <url> as <name>; |
| /// import <url> deferred as <name>; |
| /// export <url>; |
| /// |
| /// optionally with metadata and [Combinators]. |
| class LibraryDependency extends TreeNode implements Annotatable { |
| int flags; |
| |
| @override |
| final List<Expression> annotations; |
| |
| Reference importedLibraryReference; |
| |
| /// The name of the import prefix, if any, or `null` if this is not an import |
| /// with a prefix. |
| /// |
| /// Must be non-null for deferred imports, and must be null for exports. |
| String? name; |
| |
| final List<Combinator> combinators; |
| |
| LibraryDependency(int flags, List<Expression> annotations, |
| Library importedLibrary, String name, List<Combinator> combinators) |
| : this.byReference( |
| flags, annotations, importedLibrary.reference, name, combinators); |
| |
| LibraryDependency.deferredImport(Library importedLibrary, String name, |
| {List<Combinator>? combinators, List<Expression>? annotations}) |
| : this.byReference(DeferredFlag, annotations ?? <Expression>[], |
| importedLibrary.reference, name, combinators ?? <Combinator>[]); |
| |
| LibraryDependency.import(Library importedLibrary, |
| {String? name, |
| List<Combinator>? combinators, |
| List<Expression>? annotations}) |
| : this.byReference(0, annotations ?? <Expression>[], |
| importedLibrary.reference, name, combinators ?? <Combinator>[]); |
| |
| LibraryDependency.export(Library importedLibrary, |
| {List<Combinator>? combinators, List<Expression>? annotations}) |
| : this.byReference(ExportFlag, annotations ?? <Expression>[], |
| importedLibrary.reference, null, combinators ?? <Combinator>[]); |
| |
| LibraryDependency.byReference(this.flags, this.annotations, |
| this.importedLibraryReference, this.name, this.combinators) { |
| setParents(annotations, this); |
| setParents(combinators, this); |
| } |
| |
| Library get enclosingLibrary => parent as Library; |
| Library get targetLibrary => importedLibraryReference.asLibrary; |
| |
| static const int ExportFlag = 1 << 0; |
| static const int DeferredFlag = 1 << 1; |
| |
| bool get isExport => flags & ExportFlag != 0; |
| bool get isImport => !isExport; |
| bool get isDeferred => flags & DeferredFlag != 0; |
| |
| @override |
| void addAnnotation(Expression annotation) { |
| annotations.add(annotation..parent = this); |
| } |
| |
| @override |
| R accept<R>(TreeVisitor<R> v) => v.visitLibraryDependency(this); |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => |
| v.visitLibraryDependency(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(annotations, v); |
| visitList(combinators, v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(annotations, this); |
| v.transformList(combinators, this); |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformExpressionList(annotations, this); |
| v.transformCombinatorList(combinators, this); |
| } |
| |
| @override |
| String toString() { |
| return "LibraryDependency(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| /// A part declaration in a library. |
| /// |
| /// part <url>; |
| /// |
| /// optionally with metadata. |
| class LibraryPart extends TreeNode implements Annotatable { |
| @override |
| final List<Expression> annotations; |
| |
| final String partUri; |
| |
| LibraryPart(this.annotations, this.partUri) { |
| setParents(annotations, this); |
| } |
| |
| @override |
| void addAnnotation(Expression annotation) { |
| annotations.add(annotation..parent = this); |
| } |
| |
| @override |
| R accept<R>(TreeVisitor<R> v) => v.visitLibraryPart(this); |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitLibraryPart(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(annotations, v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(annotations, this); |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformExpressionList(annotations, this); |
| } |
| |
| @override |
| String toString() { |
| return "LibraryPart(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| /// A `show` or `hide` clause for an import or export. |
| class Combinator extends TreeNode { |
| bool isShow; |
| |
| final List<String> names; |
| |
| LibraryDependency get dependency => parent as LibraryDependency; |
| |
| Combinator(this.isShow, this.names); |
| Combinator.show(this.names) : isShow = true; |
| Combinator.hide(this.names) : isShow = false; |
| |
| bool get isHide => !isShow; |
| |
| @override |
| R accept<R>(TreeVisitor<R> v) => v.visitCombinator(this); |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitCombinator(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) {} |
| |
| @override |
| void transformChildren(Transformer v) {} |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) {} |
| |
| @override |
| String toString() { |
| return "Combinator(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| /// Declaration of a type alias. |
| class Typedef extends NamedNode implements FileUriNode, Annotatable { |
| /// The URI of the source file that contains the declaration of this typedef. |
| @override |
| Uri fileUri; |
| |
| @override |
| List<Expression> annotations = const <Expression>[]; |
| |
| String name; |
| final List<TypeParameter> typeParameters; |
| // TODO(johnniwinther): Make this non-nullable. |
| DartType? type; |
| |
| // The following fields describe parameters of the underlying type when |
| // that is a function type. They are needed to keep such attributes as names |
| // and annotations. When the underlying type is not a function type, they are |
| // empty. |
| final List<TypeParameter> typeParametersOfFunctionType; |
| final List<VariableDeclaration> positionalParameters; |
| final List<VariableDeclaration> namedParameters; |
| |
| Typedef(this.name, this.type, |
| {Reference? reference, |
| required this.fileUri, |
| List<TypeParameter>? typeParameters, |
| List<TypeParameter>? typeParametersOfFunctionType, |
| List<VariableDeclaration>? positionalParameters, |
| List<VariableDeclaration>? namedParameters}) |
| // ignore: unnecessary_null_comparison |
| : assert(fileUri != null), |
| this.typeParameters = typeParameters ?? <TypeParameter>[], |
| this.typeParametersOfFunctionType = |
| typeParametersOfFunctionType ?? <TypeParameter>[], |
| this.positionalParameters = |
| positionalParameters ?? <VariableDeclaration>[], |
| this.namedParameters = namedParameters ?? <VariableDeclaration>[], |
| super(reference) { |
| setParents(this.typeParameters, this); |
| setParents(this.typeParametersOfFunctionType, this); |
| setParents(this.positionalParameters, this); |
| setParents(this.namedParameters, this); |
| } |
| |
| Library get enclosingLibrary => parent as Library; |
| |
| @override |
| R accept<R>(TreeVisitor<R> v) => v.visitTypedef(this); |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitTypedef(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(annotations, v); |
| visitList(typeParameters, v); |
| type?.accept(v); |
| visitList(typeParametersOfFunctionType, v); |
| visitList(positionalParameters, v); |
| visitList(namedParameters, v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(annotations, this); |
| v.transformList(typeParameters, this); |
| if (type != null) { |
| type = v.visitDartType(type!); |
| } |
| v.transformList(typeParametersOfFunctionType, this); |
| v.transformList(positionalParameters, this); |
| v.transformList(namedParameters, this); |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformExpressionList(annotations, this); |
| v.transformTypeParameterList(typeParameters, this); |
| if (type != null) { |
| DartType newType = v.visitDartType(type!, dummyDartType); |
| if (identical(newType, dummyDartType)) { |
| type = null; |
| } else { |
| type = newType; |
| } |
| } |
| v.transformTypeParameterList(typeParametersOfFunctionType, this); |
| v.transformVariableDeclarationList(positionalParameters, this); |
| v.transformVariableDeclarationList(namedParameters, this); |
| } |
| |
| @override |
| void addAnnotation(Expression node) { |
| if (annotations.isEmpty) { |
| annotations = <Expression>[]; |
| } |
| annotations.add(node); |
| node.parent = this; |
| } |
| |
| @override |
| Location? _getLocationInEnclosingFile(int offset) { |
| return _getLocationInComponent(enclosingComponent, fileUri, offset); |
| } |
| |
| @override |
| String toString() { |
| return "Typedef(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeTypedefName(reference); |
| } |
| } |
| |
| /// List-wrapper that marks the parent-class as dirty if the list is modified. |
| /// |
| /// The idea being, that for non-dirty classes (classes just loaded from dill) |
| /// the canonical names has already been calculated, and recalculating them is |
| /// not needed. If, however, we change anything, recalculation of the canonical |
| /// names can be needed. |
| class DirtifyingList<E> extends ListBase<E> { |
| final Class dirtifyClass; |
| final List<E> wrapped; |
| |
| DirtifyingList(this.dirtifyClass, this.wrapped); |
| |
| @override |
| int get length { |
| return wrapped.length; |
| } |
| |
| @override |
| void set length(int length) { |
| dirtifyClass.dirty = true; |
| wrapped.length = length; |
| } |
| |
| @override |
| E operator [](int index) { |
| return wrapped[index]; |
| } |
| |
| @override |
| void operator []=(int index, E value) { |
| dirtifyClass.dirty = true; |
| wrapped[index] = value; |
| } |
| } |
| |
| /// Declaration of a regular class or a mixin application. |
| /// |
| /// Mixin applications may not contain fields or procedures, as they implicitly |
| /// use those from its mixed-in type. However, the IR does not enforce this |
| /// rule directly, as doing so can obstruct transformations. It is possible to |
| /// transform a mixin application to become a regular class, and vice versa. |
| class Class extends NamedNode implements Annotatable, FileUriNode { |
| /// Start offset of the class in the source file it comes from. |
| /// |
| /// Note that this includes annotations if any. |
| /// |
| /// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file |
| /// start offset is not available (this is the default if none is specifically |
| /// set). |
| int startFileOffset = TreeNode.noOffset; |
| |
| /// End offset in the source file it comes from. Valid values are from 0 and |
| /// up, or -1 ([TreeNode.noOffset]) if the file end offset is not available |
| /// (this is the default if none is specifically set). |
| int fileEndOffset = TreeNode.noOffset; |
| |
| /// List of metadata annotations on the class. |
| /// |
| /// This defaults to an immutable empty list. Use [addAnnotation] to add |
| /// annotations if needed. |
| @override |
| List<Expression> annotations = const <Expression>[]; |
| |
| /// Name of the class. |
| /// |
| /// Must be non-null and must be unique within the library. |
| /// |
| /// The name may contain characters that are not valid in a Dart identifier, |
| /// in particular, the symbol '&' is used in class names generated for mixin |
| /// applications. |
| String name; |
| |
| // Must match serialized bit positions. |
| static const int FlagAbstract = 1 << 0; |
| static const int FlagEnum = 1 << 1; |
| static const int FlagAnonymousMixin = 1 << 2; |
| static const int FlagEliminatedMixin = 1 << 3; |
| static const int FlagMixinDeclaration = 1 << 4; |
| static const int FlagHasConstConstructor = 1 << 5; |
| |
| int flags = 0; |
| |
| bool get isAbstract => flags & FlagAbstract != 0; |
| |
| void set isAbstract(bool value) { |
| flags = value ? (flags | FlagAbstract) : (flags & ~FlagAbstract); |
| } |
| |
| /// Whether this class is an enum. |
| bool get isEnum => flags & FlagEnum != 0; |
| |
| void set isEnum(bool value) { |
| flags = value ? (flags | FlagEnum) : (flags & ~FlagEnum); |
| } |
| |
| /// Whether this class is a synthetic implementation created for each |
| /// mixed-in class. For example the following code: |
| /// class Z extends A with B, C, D {} |
| /// class A {} |
| /// class B {} |
| /// class C {} |
| /// class D {} |
| /// ...creates: |
| /// abstract class _Z&A&B extends A mixedIn B {} |
| /// abstract class _Z&A&B&C extends A&B mixedIn C {} |
| /// abstract class _Z&A&B&C&D extends A&B&C mixedIn D {} |
| /// class Z extends _Z&A&B&C&D {} |
| /// All X&Y classes are marked as synthetic. |
| bool get isAnonymousMixin => flags & FlagAnonymousMixin != 0; |
| |
| void set isAnonymousMixin(bool value) { |
| flags = |
| value ? (flags | FlagAnonymousMixin) : (flags & ~FlagAnonymousMixin); |
| } |
| |
| /// Whether this class was transformed from a mixin application. |
| /// In such case, its mixed-in type was pulled into the end of implemented |
| /// types list. |
| bool get isEliminatedMixin => flags & FlagEliminatedMixin != 0; |
| |
| void set isEliminatedMixin(bool value) { |
| flags = |
| value ? (flags | FlagEliminatedMixin) : (flags & ~FlagEliminatedMixin); |
| } |
| |
| /// True if this class was a mixin declaration in Dart. |
| /// |
| /// Mixins are declared in Dart with the `mixin` keyword. They are compiled |
| /// to Kernel classes. |
| bool get isMixinDeclaration => flags & FlagMixinDeclaration != 0; |
| |
| void set isMixinDeclaration(bool value) { |
| flags = value |
| ? (flags | FlagMixinDeclaration) |
| : (flags & ~FlagMixinDeclaration); |
| } |
| |
| /// True if this class declares one or more constant constructors. |
| bool get hasConstConstructor => flags & FlagHasConstConstructor != 0; |
| |
| void set hasConstConstructor(bool value) { |
| flags = value |
| ? (flags | FlagHasConstConstructor) |
| : (flags & ~FlagHasConstConstructor); |
| } |
| |
| List<Supertype> superclassConstraints() { |
| List<Supertype> constraints = <Supertype>[]; |
| |
| // Not a mixin declaration. |
| if (!isMixinDeclaration) return constraints; |
| |
| // Otherwise we have a left-linear binary tree (subtrees are supertype and |
| // mixedInType) of constraints, where all the interior nodes are anonymous |
| // mixin applications. |
| Supertype? current = supertype; |
| while (current != null && current.classNode.isAnonymousMixin) { |
| Class currentClass = current.classNode; |
| assert(currentClass.implementedTypes.length == 2); |
| Substitution substitution = Substitution.fromSupertype(current); |
| constraints.add( |
| substitution.substituteSupertype(currentClass.implementedTypes[1])); |
| current = |
| substitution.substituteSupertype(currentClass.implementedTypes[0]); |
| } |
| return constraints..add(current!); |
| } |
| |
| /// The URI of the source file this class was loaded from. |
| @override |
| Uri fileUri; |
| |
| final List<TypeParameter> typeParameters; |
| |
| /// The immediate super type, or `null` if this is the root class. |
| Supertype? supertype; |
| |
| /// The mixed-in type if this is a mixin application, otherwise `null`. |
| Supertype? mixedInType; |
| |
| /// The types from the `implements` clause. |
| List<Supertype> implementedTypes; |
| |
| /// Internal. Should *ONLY* be used from within kernel. |
| /// |
| /// If non-null, the function that will have to be called to fill-out the |
| /// content of this class. Note that this should not be called directly |
| /// though. |
| void Function()? lazyBuilder; |
| |
| /// Makes sure the class is loaded, i.e. the fields, procedures etc have been |
| /// loaded from the dill. Generally, one should not need to call this as it is |
| /// done automatically when accessing the lists. |
| void ensureLoaded() { |
| void Function()? lazyBuilderLocal = lazyBuilder; |
| if (lazyBuilderLocal != null) { |
| lazyBuilder = null; |
| lazyBuilderLocal(); |
| } |
| } |
| |
| List<Field> _fieldsInternal; |
| DirtifyingList<Field>? _fieldsView; |
| |
| /// Fields declared in the class. |
| /// |
| /// For mixin applications this should be empty. |
| List<Field> get fields { |
| ensureLoaded(); |
| // If already dirty the caller just might as well add stuff directly too. |
| if (dirty) return _fieldsInternal; |
| return _fieldsView ??= new DirtifyingList(this, _fieldsInternal); |
| } |
| |
| /// Internal. Should *ONLY* be used from within kernel. |
| /// |
| /// Used for adding fields when reading the dill file. |
| void set fieldsInternal(List<Field> fields) { |
| _fieldsInternal = fields; |
| _fieldsView = null; |
| } |
| |
| List<Constructor> _constructorsInternal; |
| DirtifyingList<Constructor>? _constructorsView; |
| |
| /// Constructors declared in the class. |
| List<Constructor> get constructors { |
| ensureLoaded(); |
| // If already dirty the caller just might as well add stuff directly too. |
| if (dirty) return _constructorsInternal; |
| return _constructorsView ??= |
| new DirtifyingList(this, _constructorsInternal); |
| } |
| |
| /// Internal. Should *ONLY* be used from within kernel. |
| /// |
| /// Used for adding constructors when reading the dill file. |
| void set constructorsInternal(List<Constructor> constructors) { |
| _constructorsInternal = constructors; |
| _constructorsView = null; |
| } |
| |
| List<Procedure> _proceduresInternal; |
| DirtifyingList<Procedure>? _proceduresView; |
| |
| /// Procedures declared in the class. |
| /// |
| /// For mixin applications this should only contain forwarding stubs. |
| List<Procedure> get procedures { |
| ensureLoaded(); |
| // If already dirty the caller just might as well add stuff directly too. |
| if (dirty) return _proceduresInternal; |
| return _proceduresView ??= new DirtifyingList(this, _proceduresInternal); |
| } |
| |
| /// Internal. Should *ONLY* be used from within kernel. |
| /// |
| /// Used for adding procedures when reading the dill file. |
| void set proceduresInternal(List<Procedure> procedures) { |
| _proceduresInternal = procedures; |
| _proceduresView = null; |
| } |
| |
| List<RedirectingFactory> _redirectingFactoriesInternal; |
| DirtifyingList<RedirectingFactory>? _redirectingFactoriesView; |
| |
| /// Redirecting factory constructors declared in the class. |
| /// |
| /// For mixin applications this should be empty. |
| List<RedirectingFactory> get redirectingFactories { |
| ensureLoaded(); |
| // If already dirty the caller just might as well add stuff directly too. |
| if (dirty) return _redirectingFactoriesInternal; |
| return _redirectingFactoriesView ??= |
| new DirtifyingList(this, _redirectingFactoriesInternal); |
| } |
| |
| /// Internal. Should *ONLY* be used from within kernel. |
| /// |
| /// Used for adding redirecting factory constructor when reading the dill |
| /// file. |
| void set redirectingFactoryConstructorsInternal( |
| List<RedirectingFactory> redirectingFactoryConstructors) { |
| _redirectingFactoriesInternal = redirectingFactoryConstructors; |
| _redirectingFactoriesView = null; |
| } |
| |
| Class( |
| {required this.name, |
| bool isAbstract: false, |
| bool isAnonymousMixin: false, |
| this.supertype, |
| this.mixedInType, |
| List<TypeParameter>? typeParameters, |
| List<Supertype>? implementedTypes, |
| List<Constructor>? constructors, |
| List<Procedure>? procedures, |
| List<Field>? fields, |
| List<RedirectingFactory>? redirectingFactoryConstructors, |
| required this.fileUri, |
| Reference? reference}) |
| // ignore: unnecessary_null_comparison |
| : assert(name != null), |
| // ignore: unnecessary_null_comparison |
| assert(fileUri != null), |
| this.typeParameters = typeParameters ?? <TypeParameter>[], |
| this.implementedTypes = implementedTypes ?? <Supertype>[], |
| this._fieldsInternal = fields ?? <Field>[], |
| this._constructorsInternal = constructors ?? <Constructor>[], |
| this._proceduresInternal = procedures ?? <Procedure>[], |
| this._redirectingFactoriesInternal = |
| redirectingFactoryConstructors ?? <RedirectingFactory>[], |
| super(reference) { |
| setParents(this.typeParameters, this); |
| setParents(this._constructorsInternal, this); |
| setParents(this._proceduresInternal, this); |
| setParents(this._fieldsInternal, this); |
| setParents(this._redirectingFactoriesInternal, this); |
| this.isAbstract = isAbstract; |
| this.isAnonymousMixin = isAnonymousMixin; |
| } |
| |
| void computeCanonicalNames() { |
| CanonicalName canonicalName = this.reference.canonicalName!; |
| if (!dirty) return; |
| for (int i = 0; i < fields.length; ++i) { |
| Field member = fields[i]; |
| canonicalName.getChildFromField(member).bindTo(member.fieldReference); |
| canonicalName |
| .getChildFromFieldGetter(member) |
| .bindTo(member.getterReference); |
| if (member.hasSetter) { |
| canonicalName |
| .getChildFromFieldSetter(member) |
| .bindTo(member.setterReference!); |
| } |
| } |
| for (int i = 0; i < procedures.length; ++i) { |
| Procedure member = procedures[i]; |
| canonicalName.getChildFromProcedure(member).bindTo(member.reference); |
| } |
| for (int i = 0; i < constructors.length; ++i) { |
| Constructor member = constructors[i]; |
| canonicalName.getChildFromConstructor(member).bindTo(member.reference); |
| } |
| for (int i = 0; i < redirectingFactories.length; ++i) { |
| RedirectingFactory member = redirectingFactories[i]; |
| canonicalName |
| .getChildFromRedirectingFactory(member) |
| .bindTo(member.reference); |
| } |
| dirty = false; |
| } |
| |
| /// This is an advanced feature. Use of this method should be coordinated |
| /// with the kernel team. |
| /// |
| /// See [Component.relink] for a comprehensive description. |
| /// |
| /// Makes sure all references in named nodes in this class points to said |
| /// named node. |
| void relink() { |
| this.reference.node = this; |
| for (int i = 0; i < fields.length; ++i) { |
| Field member = fields[i]; |
| member._relinkNode(); |
| } |
| for (int i = 0; i < procedures.length; ++i) { |
| Procedure member = procedures[i]; |
| member._relinkNode(); |
| } |
| for (int i = 0; i < constructors.length; ++i) { |
| Constructor member = constructors[i]; |
| member._relinkNode(); |
| } |
| for (int i = 0; i < redirectingFactories.length; ++i) { |
| RedirectingFactory member = redirectingFactories[i]; |
| member._relinkNode(); |
| } |
| dirty = false; |
| } |
| |
| /// The immediate super class, or `null` if this is the root class. |
| Class? get superclass => supertype?.classNode; |
| |
| /// The mixed-in class if this is a mixin application, otherwise `null`. |
| /// |
| /// Note that this may itself be a mixin application. Use [mixin] to get the |
| /// class that has the fields and procedures. |
| Class? get mixedInClass => mixedInType?.classNode; |
| |
| /// The class that declares the field and procedures of this class. |
| Class get mixin => mixedInClass?.mixin ?? this; |
| |
| bool get isMixinApplication => mixedInType != null; |
| |
| String get demangledName { |
| if (isAnonymousMixin) return nameAsMixinApplication; |
| assert(!name.contains('&')); |
| return name; |
| } |
| |
| String get nameAsMixinApplication { |
| assert(isAnonymousMixin); |
| return demangleMixinApplicationName(name); |
| } |
| |
| String get nameAsMixinApplicationSubclass { |
| assert(isAnonymousMixin); |
| return demangleMixinApplicationSubclassName(name); |
| } |
| |
| /// Members declared in this class. |
| /// |
| /// This getter is for convenience, not efficiency. Consider manually |
| /// iterating the members to speed up code in production. |
| Iterable<Member> get members => <Iterable<Member>>[ |
| fields, |
| constructors, |
| procedures, |
| redirectingFactories |
| ].expand((x) => x); |
| |
| /// The immediately extended, mixed-in, and implemented types. |
| /// |
| /// This getter is for convenience, not efficiency. Consider manually |
| /// iterating the super types to speed up code in production. |
| Iterable<Supertype> get supers => <Iterable<Supertype>>[ |
| supertype == null ? const [] : [supertype!], |
| mixedInType == null ? const [] : [mixedInType!], |
| implementedTypes |
| ].expand((x) => x); |
| |
| /// The library containing this class. |
| Library get enclosingLibrary => parent as Library; |
| |
| /// Internal. Should *ONLY* be used from within kernel. |
| /// |
| /// If true we have to compute canonical names for all children of this class. |
| /// if false we can skip it. |
| bool dirty = true; |
| |
| /// Adds a constructor to this class. |
| void addConstructor(Constructor constructor) { |
| dirty = true; |
| constructor.parent = this; |
| _constructorsInternal.add(constructor); |
| } |
| |
| /// Adds a procedure to this class. |
| void addProcedure(Procedure procedure) { |
| dirty = true; |
| procedure.parent = this; |
| _proceduresInternal.add(procedure); |
| } |
| |
| /// Adds a field to this class. |
| void addField(Field field) { |
| dirty = true; |
| field.parent = this; |
| _fieldsInternal.add(field); |
| } |
| |
| /// Adds a field to this class. |
| void addRedirectingFactory(RedirectingFactory redirectingFactory) { |
| dirty = true; |
| redirectingFactory.parent = this; |
| _redirectingFactoriesInternal.add(redirectingFactory); |
| } |
| |
| @override |
| void addAnnotation(Expression node) { |
| if (annotations.isEmpty) { |
| annotations = <Expression>[]; |
| } |
| annotations.add(node); |
| node.parent = this; |
| } |
| |
| @override |
| R accept<R>(TreeVisitor<R> v) => v.visitClass(this); |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitClass(this, arg); |
| |
| R acceptReference<R>(Visitor<R> v) => v.visitClassReference(this); |
| |
| Supertype get asRawSupertype { |
| return new Supertype(this, |
| new List<DartType>.filled(typeParameters.length, const DynamicType())); |
| } |
| |
| Supertype get asThisSupertype { |
| return new Supertype( |
| this, getAsTypeArguments(typeParameters, this.enclosingLibrary)); |
| } |
| |
| /// Returns the type of `this` for the class using [coreTypes] for caching. |
| InterfaceType getThisType(CoreTypes coreTypes, Nullability nullability) { |
| return coreTypes.thisInterfaceType(this, nullability); |
| } |
| |
| @override |
| String toString() => 'Class(${toStringInternal()})'; |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeClassName(reference); |
| } |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(annotations, v); |
| visitList(typeParameters, v); |
| supertype?.accept(v); |
| mixedInType?.accept(v); |
| visitList(implementedTypes, v); |
| visitList(constructors, v); |
| visitList(procedures, v); |
| visitList(fields, v); |
| visitList(redirectingFactories, v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(annotations, this); |
| v.transformList(typeParameters, this); |
| if (supertype != null) { |
| supertype = v.visitSupertype(supertype!); |
| } |
| if (mixedInType != null) { |
| mixedInType = v.visitSupertype(mixedInType!); |
| } |
| v.transformSupertypeList(implementedTypes); |
| v.transformList(constructors, this); |
| v.transformList(procedures, this); |
| v.transformList(fields, this); |
| v.transformList(redirectingFactories, this); |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformExpressionList(annotations, this); |
| v.transformTypeParameterList(typeParameters, this); |
| if (supertype != null) { |
| Supertype newSupertype = v.visitSupertype(supertype!, dummySupertype); |
| if (identical(newSupertype, dummySupertype)) { |
| supertype = null; |
| } else { |
| supertype = newSupertype; |
| } |
| } |
| if (mixedInType != null) { |
| Supertype newMixedInType = v.visitSupertype(mixedInType!, dummySupertype); |
| if (identical(newMixedInType, dummySupertype)) { |
| mixedInType = null; |
| } else { |
| mixedInType = newMixedInType; |
| } |
| } |
| v.transformSupertypeList(implementedTypes); |
| v.transformConstructorList(constructors, this); |
| v.transformProcedureList(procedures, this); |
| v.transformFieldList(fields, this); |
| v.transformRedirectingFactoryList(redirectingFactories, this); |
| } |
| |
| @override |
| Location? _getLocationInEnclosingFile(int offset) { |
| return _getLocationInComponent(enclosingComponent, fileUri, offset); |
| } |
| } |
| |
| /// Declaration of an extension. |
| /// |
| /// The members are converted into top-level procedures and only accessible |
| /// by reference in the [Extension] node. |
| class Extension extends NamedNode implements Annotatable, FileUriNode { |
| /// Name of the extension. |
| /// |
| /// If unnamed, the extension will be given a synthesized name by the |
| /// front end. |
| String name; |
| |
| /// The URI of the source file this class was loaded from. |
| @override |
| Uri fileUri; |
| |
| /// Type parameters declared on the extension. |
| final List<TypeParameter> typeParameters; |
| |
| /// The type in the 'on clause' of the extension declaration. |
| /// |
| /// For instance A in: |
| /// |
| /// class A {} |
| /// extension B on A {} |
| /// |
| /// The 'on clause' appears also in the experimental feature 'extension |
| /// types' as a part of an extension type declaration, for example: |
| /// |
| /// class A {} |
| /// extension type B on A {} |
| late DartType onType; |
| |
| /// The 'show' and 'hide' clauses of an extension type declaration. |
| ExtensionTypeShowHideClause? showHideClause; |
| |
| /// The members declared by the extension. |
| /// |
| /// The members are converted into top-level members and only accessible |
| /// by reference through [ExtensionMemberDescriptor]. |
| List<ExtensionMemberDescriptor> members; |
| |
| @override |
| List<Expression> annotations = const <Expression>[]; |
| |
| // Must match serialized bit positions. |
| static const int FlagExtensionTypeDeclaration = 1 << 0; |
| |
| int flags = 0; |
| |
| @override |
| void addAnnotation(Expression node) { |
| if (annotations.isEmpty) { |
| annotations = <Expression>[]; |
| } |
| annotations.add(node); |
| node.parent = this; |
| } |
| |
| Extension( |
| {required this.name, |
| List<TypeParameter>? typeParameters, |
| DartType? onType, |
| List<ExtensionMemberDescriptor>? members, |
| required this.fileUri, |
| Reference? reference}) |
| // ignore: unnecessary_null_comparison |
| : assert(name != null), |
| // ignore: unnecessary_null_comparison |
| assert(fileUri != null), |
| this.typeParameters = typeParameters ?? <TypeParameter>[], |
| this.members = members ?? <ExtensionMemberDescriptor>[], |
| super(reference) { |
| setParents(this.typeParameters, this); |
| if (onType != null) { |
| this.onType = onType; |
| } |
| } |
| |
| Library get enclosingLibrary => parent as Library; |
| |
| bool get isExtensionTypeDeclaration { |
| return flags & FlagExtensionTypeDeclaration != 0; |
| } |
| |
| void set isExtensionTypeDeclaration(bool value) { |
| flags = value |
| ? (flags | FlagExtensionTypeDeclaration) |
| : (flags & ~FlagExtensionTypeDeclaration); |
| } |
| |
| @override |
| R accept<R>(TreeVisitor<R> v) => v.visitExtension(this); |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitExtension(this, arg); |
| |
| R acceptReference<R>(Visitor<R> v) => v.visitExtensionReference(this); |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(typeParameters, v); |
| onType.accept(v); |
| if (showHideClause != null) { |
| visitList(showHideClause!.shownSupertypes, v); |
| visitList(showHideClause!.hiddenSupertypes, v); |
| } |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(annotations, this); |
| v.transformList(typeParameters, this); |
| // ignore: unnecessary_null_comparison |
| if (onType != null) { |
| onType = v.visitDartType(onType); |
| } |
| if (showHideClause != null) { |
| v.transformSupertypeList(showHideClause!.shownSupertypes); |
| v.transformSupertypeList(showHideClause!.hiddenSupertypes); |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformExpressionList(annotations, this); |
| v.transformTypeParameterList(typeParameters, this); |
| // ignore: unnecessary_null_comparison |
| if (onType != null) { |
| onType = v.visitDartType(onType, cannotRemoveSentinel); |
| } |
| if (showHideClause != null) { |
| v.transformSupertypeList(showHideClause!.shownSupertypes); |
| v.transformSupertypeList(showHideClause!.hiddenSupertypes); |
| } |
| } |
| |
| @override |
| Location? _getLocationInEnclosingFile(int offset) { |
| return _getLocationInComponent(enclosingComponent, fileUri, offset); |
| } |
| |
| @override |
| String toString() { |
| return "Extension(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExtensionName(reference); |
| } |
| } |
| |
| enum ExtensionMemberKind { |
| Field, |
| Method, |
| Getter, |
| Setter, |
| Operator, |
| TearOff, |
| } |
| |
| /// Information about an member declaration in an extension. |
| class ExtensionMemberDescriptor { |
| static const int FlagStatic = 1 << 0; // Must match serialized bit positions. |
| |
| /// The name of the extension member. |
| /// |
| /// The name of the generated top-level member is mangled to ensure |
| /// uniqueness. This name is used to lookup an extension method in the |
| /// extension itself. |
| Name name; |
| |
| /// [ExtensionMemberKind] kind of the original member. |
| /// |
| /// An extension method is converted into a regular top-level method. For |
| /// instance: |
| /// |
| /// class A { |
| /// var foo; |
| /// } |
| /// extension B on A { |
| /// get bar => this.foo; |
| /// } |
| /// |
| /// will be converted into |
| /// |
| /// class A {} |
| /// B|get#bar(A #this) => #this.foo; |
| /// |
| /// where `B|get#bar` is the synthesized name of the top-level method and |
| /// `#this` is the synthesized parameter that holds represents `this`. |
| /// |
| ExtensionMemberKind kind; |
| |
| int flags = 0; |
| |
| /// Reference to the top-level member created for the extension method. |
| final Reference member; |
| |
| ExtensionMemberDescriptor( |
| {required this.name, |
| required this.kind, |
| bool isStatic: false, |
| required this.member}) { |
| this.isStatic = isStatic; |
| } |
| |
| /// Return `true` if the extension method was declared as `static`. |
| bool get isStatic => flags & FlagStatic != 0; |
| |
| void set isStatic(bool value) { |
| flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic); |
| } |
| |
| @override |
| String toString() { |
| return 'ExtensionMemberDescriptor($name,$kind,' |
| '${member.toStringInternal()},isStatic=${isStatic})'; |
| } |
| } |
| |
| enum CallSiteAccessKind { |
| methodInvocation, |
| getterInvocation, |
| setterInvocation, |
| operatorInvocation, |
| } |
| |
| /// Elements of the 'show' and 'hide' clauses of an extension type declaration. |
| class ExtensionTypeShowHideClause { |
| /// The types in the 'show clause' of the extension type declaration. |
| /// |
| /// For instance A, B in: |
| /// |
| /// class A {} |
| /// class B {} |
| /// class C extends B implements A {} |
| /// extension type E on C show B, A {} |
| final List<Supertype> shownSupertypes = <Supertype>[]; |
| |
| /// The methods in the 'show clause' of the extension type declaration. |
| /// |
| /// For instance foo in |
| /// |
| /// class A { |
| /// void foo() {} |
| /// } |
| /// extension type E on A show foo {} |
| final List<Reference> shownMethods = <Reference>[]; |
| |
| /// The getters in the 'show clause' of the extension type declaration. |
| /// |
| /// For instance foo, bar, baz in |
| /// |
| /// class A { |
| /// void foo() {} |
| /// int? bar; |
| /// int get baz => 42; |
| /// } |
| /// extension type E on A show get foo, get bar, get baz {} |
| final List<Reference> shownGetters = <Reference>[]; |
| |
| /// The setters in the 'show clause' of the extension type declaration. |
| /// |
| /// For instance foo, bar in |
| /// |
| /// class A { |
| /// int? foo; |
| /// void set bar(int value) {} |
| /// } |
| /// extension type E on A show set foo, set bar {} |
| final List<Reference> shownSetters = <Reference>[]; |
| |
| /// The operators in the 'show clause' of the extension type declaration. |
| /// |
| /// For instance +, * in |
| /// |
| /// class A { |
| /// A operator+(A other) => other; |
| /// A operator*(A other) => this; |
| /// } |
| /// extension type E on A show operator +, operator * {} |
| final List<Reference> shownOperators = <Reference>[]; |
| |
| /// The types in the 'hide clause' of the extension type declaration. |
| /// |
| /// For instance A, B in: |
| /// |
| /// class A {} |
| /// class B {} |
| /// class C extends B implements A {} |
| /// extension E on C hide A, B {} |
| final List<Supertype> hiddenSupertypes = <Supertype>[]; |
| |
| /// The methods in the 'hide clause' of the extension type declaration. |
| /// |
| /// For instance foo in |
| /// |
| /// class A { |
| /// void foo() {} |
| /// } |
| /// extension type E on A hide foo {} |
| final List<Reference> hiddenMethods = <Reference>[]; |
| |
| /// The getters in the 'hide clause' of the extension type declaration. |
| /// |
| /// For instance foo, bar, baz in |
| /// |
| /// class A { |
| /// void foo() {} |
| /// int? bar; |
| /// int get baz => 42; |
| /// } |
| /// extension type E on A hide get foo, get bar, get baz {} |
| final List<Reference> hiddenGetters = <Reference>[]; |
| |
| /// The setters in the 'hide clause' of the extension type declaration. |
| /// |
| /// For instance foo, bar in |
| /// |
| /// class A { |
| /// int? foo; |
| /// void set bar(int value) {} |
| /// } |
| /// extension type E on A hide set foo, set bar {} |
| final List<Reference> hiddenSetters = <Reference>[]; |
| |
| /// The operators in the 'hide clause' of the extension type declaration. |
| /// |
| /// For instance +, * in |
| /// |
| /// class A { |
| /// A operator+(A other) => other; |
| /// A operator*(A other) => this; |
| /// } |
| /// extension type E on A hide operator +, operator * {} |
| final List<Reference> hiddenOperators = <Reference>[]; |
| |
| Reference? findShownReference(Name name, |
| CallSiteAccessKind callSiteAccessKind, ClassHierarchy hierarchy) { |
| List<Reference> shownReferences; |
| List<Reference> hiddenReferences; |
| switch (callSiteAccessKind) { |
| case CallSiteAccessKind.getterInvocation: |
| shownReferences = shownGetters; |
| hiddenReferences = hiddenGetters; |
| break; |
| case CallSiteAccessKind.setterInvocation: |
| shownReferences = shownSetters; |
| hiddenReferences = hiddenSetters; |
| break; |
| case CallSiteAccessKind.methodInvocation: |
| shownReferences = shownMethods; |
| hiddenReferences = hiddenMethods; |
| break; |
| case CallSiteAccessKind.operatorInvocation: |
| shownReferences = shownOperators; |
| hiddenReferences = hiddenOperators; |
| break; |
| } |
| |
| Reference? reference = _findMember( |
| name, shownReferences, shownSupertypes, hierarchy, callSiteAccessKind); |
| if (reference != null && |
| _findMember(name, hiddenReferences, hiddenSupertypes, hierarchy, |
| callSiteAccessKind) == |
| null) { |
| return reference; |
| } |
| |
| return null; |
| } |
| |
| Reference? _findMember( |
| Name name, |
| List<Reference> references, |
| List<Supertype> interfaces, |
| ClassHierarchy hierarchy, |
| CallSiteAccessKind callSiteAccessKind) { |
| for (Reference reference in references) { |
| if (reference.asMember.name == name) { |
| return reference; |
| } |
| } |
| for (Supertype interface in interfaces) { |
| Member? member = hierarchy.getInterfaceMember(interface.classNode, name, |
| setter: callSiteAccessKind == CallSiteAccessKind.setterInvocation); |
| if (member != null) { |
| return member.reference; |
| } |
| } |
| return null; |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // MEMBERS |
| // ------------------------------------------------------------------------ |
| |
| abstract class Member extends NamedNode implements Annotatable, FileUriNode { |
| /// End offset in the source file it comes from. |
| /// |
| /// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file |
| /// end offset is not available (this is the default if none is specifically |
| /// set). |
| int fileEndOffset = TreeNode.noOffset; |
| |
| /// List of metadata annotations on the member. |
| /// |
| /// This defaults to an immutable empty list. Use [addAnnotation] to add |
| /// annotations if needed. |
| @override |
| List<Expression> annotations = const <Expression>[]; |
| |
| Name name; |
| |
| /// The URI of the source file this member was loaded from. |
| @override |
| Uri fileUri; |
| |
| /// Flags summarizing the kinds of AST nodes contained in this member, for |
| /// speeding up transformations that only affect certain types of nodes. |
| /// |
| /// See [TransformerFlag] for the meaning of each bit. |
| /// |
| /// These should not be used for any purpose other than skipping certain |
| /// members if it can be determined that no work is needed in there. |
| /// |
| /// It is valid for these flags to be false positives in rare cases, so |
| /// transformers must tolerate the case where a flag is spuriously set. |
| /// |
| /// This value is not serialized; it is populated by the frontend and the |
| /// deserializer. |
| // |
| // TODO(asgerf): It might be worthwhile to put this on classes as well. |
| int transformerFlags = 0; |
| |
| Member(this.name, this.fileUri, Reference? reference) |
| // ignore: unnecessary_null_comparison |
| : assert(name != null), |
| // ignore: unnecessary_null_comparison |
| assert(fileUri != null), |
| super(reference); |
| |
| Class? get enclosingClass => parent is Class ? parent as Class : null; |
| Library get enclosingLibrary => |
| (parent is Class ? parent!.parent : parent) as Library; |
| |
| @override |
| R accept<R>(MemberVisitor<R> v); |
| |
| @override |
| R accept1<R, A>(MemberVisitor1<R, A> v, A arg); |
| |
| R acceptReference<R>(MemberReferenceVisitor<R> v); |
| |
| /// Returns true if this is an abstract procedure. |
| bool get isAbstract => false; |
| |
| /// Returns true if the member has the 'const' modifier. |
| bool get isConst; |
| |
| /// True if this is a field or non-setter procedure. |
| /// |
| /// Note that operators and factories return `true`, even though there are |
| /// normally no calls to their getter. |
| bool get hasGetter; |
| |
| /// True if this is a setter or a mutable field. |
| bool get hasSetter; |
| |
| /// True if this is a non-static field or procedure. |
| bool get isInstanceMember; |
| |
| /// True if the member has the `external` modifier, implying that the |
| /// implementation is provided by the backend, and is not necessarily written |
| /// in Dart. |
| /// |
| /// Members can have this modifier independently of whether the enclosing |
| /// library is external. |
| bool get isExternal; |
| void set isExternal(bool value); |
| |
| /// If `true` this member is compiled from a member declared in an extension |
| /// declaration. |
| /// |
| /// For instance `field`, `method1` and `method2` in: |
| /// |
| /// extension A on B { |
| /// static var field; |
| /// B method1() => this; |
| /// static B method2() => new B(); |
| /// } |
| /// |
| bool get isExtensionMember; |
| |
| /// If `true` this member is defined in a library for which non-nullable by |
| /// default is enabled. |
| bool get isNonNullableByDefault; |
| void set isNonNullableByDefault(bool value); |
| |
| /// The function signature and body of the procedure or constructor, or `null` |
| /// if this is a field. |
| FunctionNode? get function => null; |
| |
| /// Returns a possibly synthesized name for this member, consistent with |
| /// the names used across all [toString] calls. |
| @override |
| String toString() => toStringInternal(); |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeMemberName(reference); |
| } |
| |
| @override |
| void addAnnotation(Expression node) { |
| if (annotations.isEmpty) { |
| annotations = <Expression>[]; |
| } |
| annotations.add(node); |
| node.parent = this; |
| } |
| |
| /// Returns the type of this member when accessed as a getter. |
| /// |
| /// For a field, this is the field type. For a getter, this is the return |
| /// type. For a method or constructor, this is the tear off type. |
| /// |
| /// For a setter, this is undefined. Currently, non-nullable `Never` is |
| /// returned. |
| // TODO(johnniwinther): Should we use `InvalidType` for the undefined cases? |
| DartType get getterType; |
| |
| /// Returns the type of this member when access as a getter on a super class. |
| /// |
| /// This is in most cases the same as for [getterType]. |
| /// |
| /// An exception is for forwarding semi stubs: |
| /// |
| /// class Super { |
| /// void method(num a) {} |
| /// } |
| /// class Class extends Super { |
| /// void method(covariant int a); |
| /// } |
| /// class Subclass extends Class { |
| /// void method(int a) { |
| /// super.method; // Type `void Function(num)`. |
| /// Class().method; // Type `void Function(int)`. |
| /// } |
| /// } |
| /// |
| /// Here, `Class.method` is turned into a forwarding semi stub |
| /// |
| /// void method(covariant num a) => super.method(a); |
| /// |
| /// with [signatureType] `void Function(int)`. When `Class.method` is used |
| /// as the target of a super get, it has getter type `void Function(num)` and |
| /// as the target of an instance get, it has getter type `void Function(int)`. |
| DartType get superGetterType => getterType; |
| |
| /// Returns the type of this member when accessed as a setter. |
| /// |
| /// For an assignable field, this is the field type. For a setter this is the |
| /// parameter type. |
| /// |
| /// For other members, including unassignable fields, this is undefined. |
| /// Currently, non-nullable `Never` is returned. |
| // TODO(johnniwinther): Should we use `InvalidType` for the undefined cases? |
| DartType get setterType; |
| |
| /// Returns the type of this member when access as a setter on a super class. |
| /// |
| /// This is in most cases the same as for [setterType]. |
| /// |
| /// An exception is for forwarding semi stubs: |
| /// |
| /// class Super { |
| /// void set setter(num a) {} |
| /// } |
| /// class Class extends Super { |
| /// void set setter(covariant int a); |
| /// } |
| /// class Subclass extends Class { |
| /// void set setter(int a) { |
| /// super.setter = 0.5; // Valid. |
| /// Class().setter = 0.5; // Invalid. |
| /// } |
| /// } |
| /// |
| /// Here, `Class.setter` is turned into a forwarding semi stub |
| /// |
| /// void set setter(covariant num a) => super.setter = a; |
| /// |
| /// with [signatureType] `void Function(int)`. When `Class.setter` is used |
| /// as the target of a super set, it has setter type `num` and as the target |
| /// of an instance set, it has setter type `int`. |
| DartType get superSetterType => setterType; |
| |
| bool get containsSuperCalls { |
| return transformerFlags & TransformerFlag.superCalls != 0; |
| } |
| |
| /// If this member is a member signature, [memberSignatureOrigin] is one of |
| /// the non-member signature members from which it was created. |
| Member? get memberSignatureOrigin => null; |
| } |
| |
| /// A field declaration. |
| /// |
| /// The implied getter and setter for the field are not represented explicitly, |
| /// but can be made explicit if needed. |
| class Field extends Member { |
| DartType type; // Not null. Defaults to DynamicType. |
| int flags = 0; |
| Expression? initializer; // May be null. |
| |
| /// Reference used for reading from this field. |
| /// |
| /// This should be used as the target in [StaticGet], [InstanceGet], and |
| /// [SuperPropertyGet]. |
| final Reference getterReference; |
| |
| /// Reference used for writing to this field. |
| /// |
| /// This should be used as the target in [StaticSet], [InstanceSet], and |
| /// [SuperPropertySet]. |
| final Reference? setterReference; |
| |
| @override |
| @Deprecated("Use the specific getterReference/setterReference instead") |
| Reference get reference => super.reference; |
| |
| /// Reference used for initializing this field. |
| /// |
| /// This should be used as the target in [FieldInitializer] and as the key |
| /// in the field values of [InstanceConstant]. |
| Reference get fieldReference => super.reference; |
| |
| Field.mutable(Name name, |
| {this.type: const DynamicType(), |
| this.initializer, |
| bool isCovariantByDeclaration: false, |
| bool isFinal: false, |
| bool isStatic: false, |
| bool isLate: false, |
| int transformerFlags: 0, |
| required Uri fileUri, |
| Reference? fieldReference, |
| Reference? getterReference, |
| Reference? setterReference}) |
| : this.getterReference = getterReference ?? new Reference(), |
| this.setterReference = setterReference ?? new Reference(), |
| super(name, fileUri, fieldReference) { |
| this.getterReference.node = this; |
| this.setterReference!.node = this; |
| // ignore: unnecessary_null_comparison |
| assert(type != null); |
| initializer?.parent = this; |
| this.isCovariantByDeclaration = isCovariantByDeclaration; |
| this.isFinal = isFinal; |
| this.isStatic = isStatic; |
| this.isLate = isLate; |
| this.transformerFlags = transformerFlags; |
| } |
| |
| Field.immutable(Name name, |
| {this.type: const DynamicType(), |
| this.initializer, |
| bool isCovariantByDeclaration: false, |
| bool isFinal: false, |
| bool isConst: false, |
| bool isStatic: false, |
| bool isLate: false, |
| int transformerFlags: 0, |
| required Uri fileUri, |
| Reference? fieldReference, |
| Reference? getterReference}) |
| : this.getterReference = getterReference ?? new Reference(), |
| this.setterReference = null, |
| super(name, fileUri, fieldReference) { |
| this.getterReference.node = this; |
| // ignore: unnecessary_null_comparison |
| assert(type != null); |
| initializer?.parent = this; |
| this.isCovariantByDeclaration = isCovariantByDeclaration; |
| this.isFinal = isFinal; |
| this.isConst = isConst; |
| this.isStatic = isStatic; |
| this.isLate = isLate; |
| this.transformerFlags = transformerFlags; |
| } |
| |
| @override |
| void _relinkNode() { |
| this.fieldReference.node = this; |
| this.getterReference.node = this; |
| if (hasSetter) { |
| this.setterReference!.node = this; |
| } |
| } |
| |
| static const int FlagFinal = 1 << 0; // Must match serialized bit positions. |
| static const int FlagConst = 1 << 1; |
| static const int FlagStatic = 1 << 2; |
| static const int FlagCovariant = 1 << 3; |
| static const int FlagCovariantByClass = 1 << 4; |
| static const int FlagLate = 1 << 5; |
| static const int FlagExtensionMember = 1 << 6; |
| static const int FlagNonNullableByDefault = 1 << 7; |
| static const int FlagInternalImplementation = 1 << 8; |
| |
| /// Whether the field is declared with the `covariant` keyword. |
| bool get isCovariantByDeclaration => flags & FlagCovariant != 0; |
| |
| bool get isFinal => flags & FlagFinal != 0; |
| |
| @override |
| bool get isConst => flags & FlagConst != 0; |
| |
| bool get isStatic => flags & FlagStatic != 0; |
| |
| @override |
| bool get isExtensionMember => flags & FlagExtensionMember != 0; |
| |
| /// Indicates whether the implicit setter associated with this field needs to |
| /// contain a runtime type check to deal with generic covariance. |
| /// |
| /// When `true`, runtime checks may need to be performed; see |
| /// [DispatchCategory] for details. |
| bool get isCovariantByClass => flags & FlagCovariantByClass != 0; |
| |
| /// Whether the field is declared with the `late` keyword. |
| bool get isLate => flags & FlagLate != 0; |
| |
| // If `true` this field 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 => flags & FlagInternalImplementation != 0; |
| |
| void set isCovariantByDeclaration(bool value) { |
| flags = value ? (flags | FlagCovariant) : (flags & ~FlagCovariant); |
| } |
| |
| void set isFinal(bool value) { |
| flags = value ? (flags | FlagFinal) : (flags & ~FlagFinal); |
| } |
| |
| void set isConst(bool value) { |
| flags = value ? (flags | FlagConst) : (flags & ~FlagConst); |
| } |
| |
| void set isStatic(bool value) { |
| flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic); |
| } |
| |
| void set isExtensionMember(bool value) { |
| flags = |
| value ? (flags | FlagExtensionMember) : (flags & ~FlagExtensionMember); |
| } |
| |
| void set isCovariantByClass(bool value) { |
| flags = value |
| ? (flags | FlagCovariantByClass) |
| : (flags & ~FlagCovariantByClass); |
| } |
| |
| void set isLate(bool value) { |
| flags = value ? (flags | FlagLate) : (flags & ~FlagLate); |
| } |
| |
| void set isInternalImplementation(bool value) { |
| flags = value |
| ? (flags | FlagInternalImplementation) |
| : (flags & ~FlagInternalImplementation); |
| } |
| |
| @override |
| bool get isInstanceMember => !isStatic; |
| |
| @override |
| bool get hasGetter => true; |
| |
| @override |
| bool get hasSetter => setterReference != null; |
| |
| @override |
| bool get isExternal => false; |
| |
| @override |
| void set isExternal(bool value) { |
| if (value) throw 'Fields cannot be external'; |
| } |
| |
| @override |
| bool get isNonNullableByDefault => flags & FlagNonNullableByDefault != 0; |
| |
| @override |
| void set isNonNullableByDefault(bool value) { |
| flags = value |
| ? (flags | FlagNonNullableByDefault) |
| : (flags & ~FlagNonNullableByDefault); |
| } |
| |
| @override |
| R accept<R>(MemberVisitor<R> v) => v.visitField(this); |
| |
| @override |
| R accept1<R, A>(MemberVisitor1<R, A> v, A arg) => v.visitField(this, arg); |
| |
| @override |
| R acceptReference<R>(MemberReferenceVisitor<R> v) => |
| v.visitFieldReference(this); |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(annotations, v); |
| type.accept(v); |
| name.accept(v); |
| initializer?.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| type = v.visitDartType(type); |
| v.transformList(annotations, this); |
| if (initializer != null) { |
| initializer = v.transform(initializer!); |
| initializer?.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| type = v.visitDartType(type, null); |
| v.transformExpressionList(annotations, this); |
| if (initializer != null) { |
| initializer = v.transformOrRemoveExpression(initializer!); |
| initializer?.parent = this; |
| } |
| } |
| |
| @override |
| DartType get getterType => type; |
| |
| @override |
| DartType get setterType => hasSetter ? type : const NeverType.nonNullable(); |
| |
| @override |
| Location? _getLocationInEnclosingFile(int offset) { |
| return _getLocationInComponent(enclosingComponent, fileUri, offset); |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeMemberName(fieldReference); |
| } |
| } |
| |
| /// A generative constructor, possibly redirecting. |
| /// |
| /// Note that factory constructors are treated as [Procedure]s. |
| /// |
| /// Constructors do not take type parameters. Type arguments from a constructor |
| /// invocation should be matched with the type parameters declared in the class. |
| /// |
| /// For unnamed constructors, the name is an empty string (in a [Name]). |
| class Constructor extends Member { |
| /// Start offset of the constructor in the source file it comes from. |
| /// |
| /// Note that this includes annotations if any. |
| /// |
| /// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file |
| /// start offset is not available (this is the default if none is specifically |
| /// set). |
| int startFileOffset = TreeNode.noOffset; |
| |
| int flags = 0; |
| |
| @override |
| FunctionNode function; |
| |
| List<Initializer> initializers; |
| |
| Constructor(this.function, |
| {required Name name, |
| bool isConst: false, |
| bool isExternal: false, |
| bool isSynthetic: false, |
| List<Initializer>? initializers, |
| int transformerFlags: 0, |
| required Uri fileUri, |
| Reference? reference}) |
| : this.initializers = initializers ?? <Initializer>[], |
| // ignore: unnecessary_null_comparison |
| assert(function != null), |
| super(name, fileUri, reference) { |
| function.parent = this; |
| setParents(this.initializers, this); |
| this.isConst = isConst; |
| this.isExternal = isExternal; |
| this.isSynthetic = isSynthetic; |
| this.transformerFlags = transformerFlags; |
| } |
| |
| @override |
| Class get enclosingClass => parent as Class; |
| |
| static const int FlagConst = 1 << 0; // Must match serialized bit positions. |
| static const int FlagExternal = 1 << 1; |
| static const int FlagSynthetic = 1 << 2; |
| static const int FlagNonNullableByDefault = 1 << 3; |
| |
| @override |
| bool get isConst => flags & FlagConst != 0; |
| |
| @override |
| bool get isExternal => flags & FlagExternal != 0; |
| |
| /// True if this is a synthetic constructor inserted in a class that |
| /// does not otherwise declare any constructors. |
| bool get isSynthetic => flags & FlagSynthetic != 0; |
| |
| void set isConst(bool value) { |
| flags = value ? (flags | FlagConst) : (flags & ~FlagConst); |
| } |
| |
| @override |
| void set isExternal(bool value) { |
| flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal); |
| } |
| |
| void set isSynthetic(bool value) { |
| flags = value ? (flags | FlagSynthetic) : (flags & ~FlagSynthetic); |
| } |
| |
| @override |
| bool get isInstanceMember => false; |
| |
| @override |
| bool get hasGetter => false; |
| |
| @override |
| bool get hasSetter => false; |
| |
| @override |
| bool get isExtensionMember => false; |
| |
| @override |
| bool get isNonNullableByDefault => flags & FlagNonNullableByDefault != 0; |
| |
| @override |
| void set isNonNullableByDefault(bool value) { |
| flags = value |
| ? (flags | FlagNonNullableByDefault) |
| : (flags & ~FlagNonNullableByDefault); |
| } |
| |
| @override |
| R accept<R>(MemberVisitor<R> v) => v.visitConstructor(this); |
| |
| @override |
| R accept1<R, A>(MemberVisitor1<R, A> v, A arg) => |
| v.visitConstructor(this, arg); |
| |
| @override |
| R acceptReference<R>(MemberReferenceVisitor<R> v) => |
| v.visitConstructorReference(this); |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(annotations, v); |
| name.accept(v); |
| visitList(initializers, v); |
| function.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(annotations, this); |
| v.transformList(initializers, this); |
| // ignore: unnecessary_null_comparison |
| if (function != null) { |
| function = v.transform(function); |
| function.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformExpressionList(annotations, this); |
| v.transformInitializerList(initializers, this); |
| // ignore: unnecessary_null_comparison |
| if (function != null) { |
| function = v.transform(function); |
| function.parent = this; |
| } |
| } |
| |
| // TODO(johnniwinther): Provide the tear off type here. |
| @override |
| DartType get getterType => const NeverType.nonNullable(); |
| |
| @override |
| DartType get setterType => const NeverType.nonNullable(); |
| |
| @override |
| Location? _getLocationInEnclosingFile(int offset) { |
| return _getLocationInComponent(enclosingComponent, fileUri, offset); |
| } |
| } |
| |
| /// Residue of a redirecting factory constructor for the linking phase. |
| /// |
| /// In the following example, `bar` is a redirecting factory constructor. |
| /// |
| /// class A { |
| /// A.foo(); |
| /// factory A.bar() = A.foo; |
| /// } |
| /// |
| /// An invocation of `new A.bar()` has the same effect as an invocation of |
| /// `new A.foo()`. In Kernel, the invocations of `bar` are replaced with |
| /// invocations of `foo`, and after it is done, the redirecting constructor can |
| /// be removed from the class. However, it is needed during the linking phase, |
| /// because other modules can refer to that constructor. |
| /// |
| /// [RedirectingFactory]s contain the necessary information for |
| /// linking and are treated as non-runnable members of classes that merely serve |
| /// as containers for that information. |
| /// |
| /// Redirecting factory constructors can be unnamed. In this case, the name is |
| /// an empty string (in a [Name]). |
| class RedirectingFactory extends Member { |
| int flags = 0; |
| |
| /// [RedirectingFactory]s may redirect to constructors or factories |
| /// of instantiated generic types, that is, generic types with supplied type |
| /// arguments. The supplied type arguments are stored in this field. |
| final List<DartType> typeArguments; |
| |
| /// Reference to the constructor or the factory that this |
| /// [RedirectingFactory] redirects to. |
| // TODO(johnniwinther): Make this non-nullable. |
| Reference? targetReference; |
| |
| /// [FunctionNode] that holds the type parameters, copied from the enclosing |
| /// class, and the parameters defined on the redirecting factory. |
| /// |
| /// The `FunctionNode.body` is `null` or a synthesized [ConstructorInvocation] |
| /// of the [targetReference] constructor using the [typeArguments] and |
| /// [VariableGet] of the parameters. |
| @override |
| FunctionNode function; |
| |
| RedirectingFactory(this.targetReference, |
| {required Name name, |
| bool isConst: false, |
| bool isExternal: false, |
| int transformerFlags: 0, |
| List<DartType>? typeArguments, |
| required this.function, |
| required Uri fileUri, |
| Reference? reference}) |
| : this.typeArguments = typeArguments ?? <DartType>[], |
| super(name, fileUri, reference) { |
| function.parent = this; |
| this.isConst = isConst; |
| this.isExternal = isExternal; |
| this.transformerFlags = transformerFlags; |
| } |
| |
| @override |
| Class get enclosingClass => parent as Class; |
| |
| static const int FlagConst = 1 << 0; // Must match serialized bit positions. |
| static const int FlagExternal = 1 << 1; |
| static const int FlagNonNullableByDefault = 1 << 2; |
| |
| @override |
| bool get isConst => flags & FlagConst != 0; |
| |
| @override |
| bool get isExternal => flags & FlagExternal != 0; |
| |
| void set isConst(bool value) { |
| flags = value ? (flags | FlagConst) : (flags & ~FlagConst); |
| } |
| |
| @override |
| void set isExternal(bool value) { |
| flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal); |
| } |
| |
| @override |
| bool get isInstanceMember => false; |
| |
| @override |
| bool get hasGetter => false; |
| |
| @override |
| bool get hasSetter => false; |
| |
| @override |
| bool get isExtensionMember => false; |
| |
| bool get isUnresolved => targetReference == null; |
| |
| @override |
| bool get isNonNullableByDefault => flags & FlagNonNullableByDefault != 0; |
| |
| @override |
| void set isNonNullableByDefault(bool value) { |
| flags = value |
| ? (flags | FlagNonNullableByDefault) |
| : (flags & ~FlagNonNullableByDefault); |
| } |
| |
| Member? get target => targetReference?.asMember; |
| |
| void set target(Member? member) { |
| assert(member is Constructor || |
| (member is Procedure && member.kind == ProcedureKind.Factory)); |
| targetReference = getMemberReferenceGetter(member); |
| } |
| |
| @override |
| R accept<R>(MemberVisitor<R> v) => v.visitRedirectingFactory(this); |
| |
| @override |
| R accept1<R, A>(MemberVisitor1<R, A> v, A arg) => |
| v.visitRedirectingFactory(this, arg); |
| |
| @override |
| R acceptReference<R>(MemberReferenceVisitor<R> v) => |
| v.visitRedirectingFactoryReference(this); |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(annotations, v); |
| target?.acceptReference(v); |
| visitList(typeArguments, v); |
| name.accept(v); |
| function.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(annotations, this); |
| v.transformDartTypeList(typeArguments); |
| function = v.transform(function)..parent = this; |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformExpressionList(annotations, this); |
| v.transformDartTypeList(typeArguments); |
| function = v.transform(function)..parent = this; |
| } |
| |
| @override |
| DartType get getterType => |
| function.computeFunctionType(enclosingLibrary.nonNullable); |
| |
| @override |
| DartType get setterType => const NeverType.nonNullable(); |
| |
| @override |
| Location? _getLocationInEnclosingFile(int offset) { |
| return _getLocationInComponent(enclosingComponent, fileUri, offset); |
| } |
| } |
| |
| /// Enum for the semantics of the `Procedure.stubTarget` property. |
| enum ProcedureStubKind { |
| /// A regular procedure declared in source code. |
| /// |
| /// The stub target is `null`. |
| Regular, |
| |
| /// An abstract procedure inserted to add `isCovariantByDeclaration` and |
| /// `isCovariantByClass` to parameters for a set of overridden members. |
| /// |
| /// The stub is inserted when not all of the overridden members agree on |
| /// the covariance flags. For instance: |
| /// |
| /// class A<T> { |
| /// void method1(num o) {} |
| /// void method2(T o) {} |
| /// } |
| /// class B { |
| /// void method1(covariant int o) {} |
| /// void method2(int o) {} |
| /// } |
| /// class C implements A<int>, B { |
| /// // Abstract forwarding stub needed because the parameter is |
| /// // covariant in `B.method1` but not in `A.method1`. |
| /// void method1(covariant num o); |
| /// // Abstract forwarding stub needed because the parameter is a |
| /// // generic covariant impl in `A.method2` but not in `B.method2`. |
| /// void method2(/*generic-covariant-impl*/ int o); |
| /// } |
| /// |
| /// The stub target is one of the overridden members. |
| AbstractForwardingStub, |
| |
| /// A concrete procedure inserted to add `isCovariantByDeclaration` and |
| /// `isCovariantByClass` checks to parameters before calling the |
| /// overridden member in the superclass. |
| /// |
| /// The stub is inserted when not all of the overridden members agree on |
| /// the covariance flags and the overridden super class member does not |
| /// have the same covariance flags. For instance: |
| /// |
| /// class A<T> { |
| /// void method1(num o) {} |
| /// void method2(T o) {} |
| /// } |
| /// class B { |
| /// void method1(covariant int o) {} |
| /// void method2(int o) {} |
| /// } |
| /// class C extends A<int> implements B { |
| /// // Concrete forwarding stub needed because the parameter is |
| /// // covariant in `B.method1` but not in `A.method1`. |
| /// void method1(covariant num o) => super.method1(o); |
| /// // No need for a concrete forwarding stub for `A.method2` because |
| /// // it has the right covariance flags already. |
| /// } |
| /// |
| /// The stub target is the called superclass member. |
| ConcreteForwardingStub, |
| |
| /// A concrete procedure inserted to forward calls to `noSuchMethod` for |
| /// an inherited member that it does not implement. |
| /// |
| /// The stub is inserted when a class implements private members of another |
| /// library or declares/inherits a user-defined `noSuchMethod` method. For |
| /// instance: |
| /// |
| /// // lib1: |
| /// class A { |
| /// void _privateMethod() {} |
| /// } |
| /// // lib2: |
| /// class B implements A { |
| /// // Forwarding stub inserted to forward calls to `A._privateMethod`. |
| /// void _privateMethod() => noSuchMethod(#_privateMethod, ...); |
| /// } |
| /// class C { |
| /// void method() {} |
| /// } |
| /// class D implements C { |
| /// noSuchMethod(o) { ... } |
| /// // Forwarding stub inserted to forward calls to `C.method`. |
| /// void method() => noSuchMethod(#method, ...); |
| /// } |
| /// |
| /// |
| /// The stub target is `null` if the procedure preexisted as an abstract |
| /// procedure. Otherwise the stub target is one of the inherited members. |
| NoSuchMethodForwarder, |
| |
| /// An abstract procedure inserted to show the combined member signature type |
| /// of set of overridden members. |
| /// |
| /// The stub is inserted when an opt-in member is inherited into an opt-out |
| /// library or when NNBD_TOP_MERGE was used to compute the type of a merge |
| /// point in an opt-in library. For instance: |
| /// |
| /// // lib1: opt-in |
| /// class A { |
| /// int? method1() => null; |
| /// void method2(Object? o) {} |
| /// } |
| /// class B { |
| /// dynamic method2(dynamic o); |
| /// } |
| /// class C implements A, B { |
| /// // Member signature inserted for the NNBD_TOP_MERGE type of |
| /// // `A.method2` and `B.method2`. |
| /// Object? method2(Object? o); |
| /// } |
| /// // lib2: opt-out |
| /// class D extends A { |
| /// // Member signature inserted for the LEGACY_ERASURE type of |
| /// // `A.method1` and `A.method2` with types `int* Function()` |
| /// // and `void Function(Object*)`, respectively. |
| /// int method1(); |
| /// void method2(Object o); |
| /// } |
| /// |
| /// The stub target is one of the overridden members. |
| MemberSignature, |
| |
| /// An abstract procedure inserted for the application of an abstract mixin |
| /// member. |
| /// |
| /// The stub is inserted when an abstract member is mixed into a mixin |
| /// application. For instance: |
| /// |
| /// class Super {} |
| /// abstract class Mixin { |
| /// void method(); |
| /// } |
| /// class Class = Super with Mixin |
| /// // An abstract mixin stub for `A.method` is added to `Class` |
| /// void method(); |
| /// ; |
| /// |
| /// This is added to ensure that interface targets are resolved consistently |
| /// in face of cloning. For instance, without the abstract mixin stub, this |
| /// call: |
| /// |
| /// method(Class c) => c.method(); |
| /// |
| /// would use `Mixin.method` as its target, but after loading from a VM .dill |
| /// (which clones all mixin members) the call would resolve to `Class.method` |
| /// instead. By adding the mixin stub to `Class`, all accesses both before |
| /// and after .dill will point to `Class.method`. |
| /// |
| /// The stub target is the mixin member. |
| AbstractMixinStub, |
| |
| /// A concrete procedure inserted for the application of a concrete mixin |
| /// member. The implementation calls the mixin member via a super-call. |
| /// |
| /// The stub is inserted when a concrete member is mixed into a mixin |
| /// application. For instance: |
| /// |
| /// class Super {} |
| /// abstract class Mixin { |
| /// void method() {} |
| /// } |
| /// class Class = Super with Mixin |
| /// // A concrete mixin stub for `A.method` is added to `Class` which |
| /// // calls `A.method`. |
| /// void method() => super.method(); |
| /// ; |
| /// |
| /// This is added to ensure that super accesses are resolved correctly, even |
| /// in face of cloning. For instance, without the concrete mixin stub, this |
| /// super call: |
| /// |
| /// class Subclass extends Class { |
| /// method(Class c) => super.method(); |
| /// } |
| /// |
| /// would use `Mixin.method` as its target, which would need to be updated to |
| /// match the clone of the mixin member performed for instance by the VM. By |
| /// adding the concrete mixin stub to `Class`, all accesses both before and |
| /// after cloning will point to `Class.method`. |
| /// |
| /// The stub target is the called mixin member. |
| ConcreteMixinStub, |
| } |
| |
| /// A method, getter, setter, index-getter, index-setter, operator overloader, |
| /// or factory. |
| /// |
| /// Procedures can have the static, abstract, and/or external modifier, although |
| /// only the static and external modifiers may be used together. |
| /// |
| /// For non-static procedures the name is required for dynamic dispatch. |
| /// For external procedures the name is required for identifying the external |
| /// implementation. |
| /// |
| /// For methods, getters, and setters the name is just as it was declared. |
| /// For setters this does not include a trailing `=`. |
| /// For index-getters/setters, this is `[]` and `[]=`. |
| /// For operators, this is the token for the operator, e.g. `+` or `==`, |
| /// except for the unary minus operator, whose name is `unary-`. |
| class Procedure extends Member { |
| /// Start offset of the function in the source file it comes from. |
| /// |
| /// Note that this includes annotations if any. |
| /// |
| /// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file |
| /// start offset is not available (this is the default if none is specifically |
| /// set). |
| int startFileOffset = TreeNode.noOffset; |
| |
| final ProcedureKind kind; |
| int flags = 0; |
| |
| @override |
| FunctionNode function; |
| |
| // The function node's body might be lazily loaded, meaning that this value |
| // might not be set correctly yet. Make sure the body is loaded before |
| // returning anything. |
| @override |
| int get transformerFlags { |
| function.body; |
| return super.transformerFlags; |
| } |
| |
| // The function node's body might be lazily loaded, meaning that this value |
| // might get overwritten later (when the body is read). To avoid that read the |
| // body now and only set the value afterwards. |
| @override |
| void set transformerFlags(int newValue) { |
| function.body; |
| super.transformerFlags = newValue; |
| } |
| |
| // This function will set the transformer flags without loading the body. |
| // Used when reading the binary. For other cases one should probably use |
| // `transformerFlags = value;`. |
| void setTransformerFlagsWithoutLazyLoading(int newValue) { |
| super.transformerFlags = newValue; |
| } |
| |
| ProcedureStubKind stubKind; |
| Reference? stubTargetReference; |
| |
| /// The interface member signature type of this procedure. |
| /// |
| /// Normally this is derived from the parameter types and return type of |
| /// [function]. In rare cases, the interface member signature type is |
| /// different from the class member type, in which case the interface member |
| /// signature type is stored here. |
| /// |
| /// For instance |
| /// |
| /// class Super { |
| /// void method(num a) {} |
| /// } |
| /// class Class extends Super { |
| /// void method(covariant int a); |
| /// } |
| /// |
| /// Here the member `Class.method` is turned into a forwarding semi stub to |
| /// ensure that arguments passed to `Super.method` are checked as covariant. |
| /// Since `Super.method` allows `num` as argument, the inserted covariant |
| /// check must be against `num` and not `int`, and the parameter type of the |
| /// forwarding semi stub must be changed to `num`. Still, the interface of |
| /// `Class` requires that `Class.method` is `void Function(int)`, so for this, |
| /// it is stored explicitly as the [signatureType] on the procedure. |
| FunctionType? signatureType; |
| |
| Procedure(Name name, ProcedureKind kind, FunctionNode function, |
| {bool isAbstract: false, |
| bool isStatic: false, |
| bool isExternal: false, |
| bool isConst: false, |
| bool isExtensionMember: false, |
| bool isSynthetic: false, |
| int transformerFlags: 0, |
| required Uri fileUri, |
| Reference? reference, |
| ProcedureStubKind stubKind: ProcedureStubKind.Regular, |
| Member? stubTarget}) |
| : this._byReferenceRenamed(name, kind, function, |
| isAbstract: isAbstract, |
| isStatic: isStatic, |
| isExternal: isExternal, |
| isConst: isConst, |
| isExtensionMember: isExtensionMember, |
| isSynthetic: isSynthetic, |
| transformerFlags: transformerFlags, |
| fileUri: fileUri, |
| reference: reference, |
| stubKind: stubKind, |
| stubTargetReference: |
| getMemberReferenceBasedOnProcedureKind(stubTarget, kind)); |
| |
| Procedure._byReferenceRenamed(Name name, this.kind, this.function, |
| {bool isAbstract: false, |
| bool isStatic: false, |
| bool isExternal: false, |
| bool isConst: false, |
| bool isExtensionMember: false, |
| bool isSynthetic: false, |
| int transformerFlags: 0, |
| required Uri fileUri, |
| Reference? reference, |
| this.stubKind: ProcedureStubKind.Regular, |
| this.stubTargetReference}) |
| // ignore: unnecessary_null_comparison |
| : assert(kind != null), |
| // ignore: unnecessary_null_comparison |
| assert(function != null), |
| super(name, fileUri, reference) { |
| function.parent = this; |
| this.isAbstract = isAbstract; |
| this.isStatic = isStatic; |
| this.isExternal = isExternal; |
| this.isConst = isConst; |
| this.isExtensionMember = isExtensionMember; |
| this.isSynthetic = isSynthetic; |
| setTransformerFlagsWithoutLazyLoading(transformerFlags); |
| assert(!(isMemberSignature && stubTargetReference == null), |
| "No member signature origin for member signature $this."); |
| assert( |
| !(memberSignatureOrigin is Procedure && |
| (memberSignatureOrigin as Procedure).isMemberSignature), |
| "Member signature origin cannot be a member signature " |
| "$memberSignatureOrigin for $this."); |
| } |
| |
| static const int FlagStatic = 1 << 0; // Must match serialized bit positions. |
| static const int FlagAbstract = 1 << 1; |
| static const int FlagExternal = 1 << 2; |
| static const int FlagConst = 1 << 3; // Only for external const factories. |
| // TODO(29841): Remove this flag after the issue is resolved. |
| static const int FlagRedirectingFactory = 1 << 4; |
| static const int FlagExtensionMember = 1 << 5; |
| static const int FlagNonNullableByDefault = 1 << 6; |
| static const int FlagSynthetic = 1 << 7; |
| |
| bool get isStatic => flags & FlagStatic != 0; |
| |
| @override |
| bool get isAbstract => flags & FlagAbstract != 0; |
| |
| @override |
| bool get isExternal => flags & FlagExternal != 0; |
| |
| /// True if this has the `const` modifier. This is only possible for external |
| /// constant factories, such as `String.fromEnvironment`. |
| @override |
| bool get isConst => flags & FlagConst != 0; |
| |
| /// If set, this flag indicates that this function's implementation exists |
| /// solely for the purpose of type checking arguments and forwarding to |
| /// [concreteForwardingStubTarget]. |
| /// |
| /// Note that just because this bit is set doesn't mean that the function was |
| /// not declared in the source; it's possible that this is a forwarding |
| /// semi-stub (see isForwardingSemiStub). To determine whether this function |
| /// was present in the source, consult [isSyntheticForwarder]. |
| bool get isForwardingStub => |
| stubKind == ProcedureStubKind.AbstractForwardingStub || |
| stubKind == ProcedureStubKind.ConcreteForwardingStub; |
| |
| /// If set, this flag indicates that although this function is a forwarding |
| /// stub, it was present in the original source as an abstract method. |
| bool get isForwardingSemiStub => !isSynthetic && isForwardingStub; |
| |
| /// If set, this method is a class member added to show the type of an |
| /// inherited member. |
| /// |
| /// This is used when the type of the inherited member cannot be computed |
| /// directly from the member(s) in the supertypes. For instance in case of |
| /// an nnbd opt-out class inheriting from an nnbd opt-in class; here all nnbd- |
| /// aware types are replaced with legacy types in the inherited signature. |
| bool get isMemberSignature => stubKind == ProcedureStubKind.MemberSignature; |
| |
| // Indicates if this [Procedure] represents a redirecting factory constructor |
| // and doesn't have a runnable body. |
| bool get isRedirectingFactory { |
| return flags & FlagRedirectingFactory != 0; |
| } |
| |
| /// If set, this flag indicates that this function was not present in the |
| /// source, and it exists solely for the purpose of type checking arguments |
| /// and forwarding to [concreteForwardingStubTarget]. |
| bool get isSyntheticForwarder => isForwardingStub && !isForwardingSemiStub; |
| bool get isSynthetic => flags & FlagSynthetic != 0; |
| |
| bool get isNoSuchMethodForwarder => |
| stubKind == ProcedureStubKind.NoSuchMethodForwarder; |
| |
| @override |
| bool get isExtensionMember => flags & FlagExtensionMember != 0; |
| |
| void set isStatic(bool value) { |
| flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic); |
| } |
| |
| void set isAbstract(bool value) { |
| flags = value ? (flags | FlagAbstract) : (flags & ~FlagAbstract); |
| } |
| |
| @override |
| void set isExternal(bool value) { |
| flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal); |
| } |
| |
| void set isConst(bool value) { |
| flags = value ? (flags | FlagConst) : (flags & ~FlagConst); |
| } |
| |
| void set isRedirectingFactory(bool value) { |
| flags = value |
| ? (flags | FlagRedirectingFactory) |
| : (flags & ~FlagRedirectingFactory); |
| } |
| |
| void set isExtensionMember(bool value) { |
| flags = |
| value ? (flags | FlagExtensionMember) : (flags & ~FlagExtensionMember); |
| } |
| |
| void set isSynthetic(bool value) { |
| flags = value ? (flags | FlagSynthetic) : (flags & ~FlagSynthetic); |
| } |
| |
| @override |
| bool get isInstanceMember => !isStatic; |
| |
| bool get isGetter => kind == ProcedureKind.Getter; |
| bool get isSetter => kind == ProcedureKind.Setter; |
| bool get isAccessor => isGetter || isSetter; |
| |
| @override |
| bool get hasGetter => kind != ProcedureKind.Setter; |
| |
| @override |
| bool get hasSetter => kind == ProcedureKind.Setter; |
| |
| bool get isFactory => kind == ProcedureKind.Factory; |
| |
| @override |
| bool get isNonNullableByDefault => flags & FlagNonNullableByDefault != 0; |
| |
| @override |
| void set isNonNullableByDefault(bool value) { |
| flags = value |
| ? (flags | FlagNonNullableByDefault) |
| : (flags & ~FlagNonNullableByDefault); |
| } |
| |
| Member? get concreteForwardingStubTarget => |
| stubKind == ProcedureStubKind.ConcreteForwardingStub |
| ? stubTargetReference?.asMember |
| : null; |
| |
| Member? get abstractForwardingStubTarget => |
| stubKind == ProcedureStubKind.AbstractForwardingStub |
| ? stubTargetReference?.asMember |
| : null; |
| |
| Member? get stubTarget => stubTargetReference?.asMember; |
| |
| void set stubTarget(Member? target) { |
| stubTargetReference = getMemberReferenceBasedOnProcedureKind(target, kind); |
| } |
| |
| @override |
| Member? get memberSignatureOrigin => |
| stubKind == ProcedureStubKind.MemberSignature |
| ? stubTargetReference?.asMember |
| : null; |
| |
| @override |
| R accept<R>(MemberVisitor<R> v) => v.visitProcedure(this); |
| |
| @override |
| R accept1<R, A>(MemberVisitor1<R, A> v, A arg) => v.visitProcedure(this, arg); |
| |
| @override |
| R acceptReference<R>(MemberReferenceVisitor<R> v) => |
|