| // Copyright (c) 2024, 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. | |
| part of '../../ast.dart'; | |
| /// Any type of node in the IR. | |
| abstract class Node { | |
| const new(); | |
| 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 the node 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). | |
| /// | |
| /// This is an index into [Source.text]. | |
| int fileOffset = noOffset; | |
| /// When the node has more offsets that just [fileOffset], the list of all | |
| /// offsets. | |
| List<int>? get fileOffsetsIfMultiple => null; | |
| @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) { | |
| 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) { | |
| 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; | |
| new(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; | |
| } | |
| /// Computes the canonical names for this node using the [parent] as the | |
| /// canonical name of the parent node. | |
| void bindCanonicalNames(CanonicalName parent); | |
| } | |
| /// Declaration that can introduce [TypeParameter]s. | |
| sealed class GenericDeclaration implements TreeNode { | |
| /// The type parameters introduced by this declaration. | |
| TypeParameterList get typeParameters; | |
| } | |
| /// Functions that can introduce [TypeParameter]s. | |
| sealed class GenericFunction implements GenericDeclaration { | |
| /// The [FunctionNode] that holds the introduced [typeParameters]. | |
| FunctionNode get function; | |
| } | |
| abstract class FileUriNode extends TreeNode { | |
| /// The URI of the source file this node was loaded from. | |
| Uri get fileUri; | |
| void set fileUri(Uri value); | |
| } | |
| abstract class Annotatable extends TreeNode { | |
| List<Expression> get annotations; | |
| void addAnnotation(Expression node); | |
| } | |
| class Version extends Object { | |
| final int major; | |
| final int minor; | |
| const new(this.major, this.minor); | |
| bool operator <(Version other) { | |
| if (major < other.major) return true; | |
| if (major > other.major) return false; | |
| // Major is the same. | |
| if (minor < other.minor) return true; | |
| return false; | |
| } | |
| bool operator <=(Version other) { | |
| if (major < other.major) return true; | |
| if (major > other.major) return false; | |
| // Major is the same. | |
| if (minor <= other.minor) return true; | |
| return false; | |
| } | |
| bool operator >(Version other) { | |
| if (major > other.major) return true; | |
| if (major < other.major) return false; | |
| // Major is the same. | |
| if (minor > other.minor) return true; | |
| return false; | |
| } | |
| bool operator >=(Version other) { | |
| if (major > other.major) return true; | |
| if (major < other.major) return false; | |
| // Major is the same. | |
| if (minor >= other.minor) return true; | |
| return false; | |
| } | |
| /// Returns this language version as a 'major.minor' text. | |
| String toText() => '${major}.${minor}'; | |
| @override | |
| int get hashCode { | |
| return major.hashCode * 13 + minor.hashCode * 17; | |
| } | |
| @override | |
| bool operator ==(Object other) { | |
| if (identical(this, other)) return true; | |
| return other is Version && major == other.major && minor == other.minor; | |
| } | |
| @override | |
| String toString() { | |
| return "Version(major=$major, minor=$minor)"; | |
| } | |
| } | |
| /// Helper class for creating [Let] and [LocalInitializer] objects. | |
| class CachedExpression({ | |
| required final SyntheticVariable variable, | |
| required final Expression value, | |
| }) { | |
| new fromValue({ | |
| required Expression value, | |
| required DartType type, | |
| int? fileOffset, | |
| String? cosmeticName, | |
| // TODO(johnniwinther): This should always be final. | |
| bool isFinal = false, | |
| }) : this( | |
| variable: new SyntheticVariable( | |
| type: type, | |
| cosmeticName: cosmeticName, | |
| isFinal: isFinal, | |
| )..fileOffset = fileOffset ?? value.fileOffset, | |
| value: value, | |
| ); | |
| DartType get type => variable.type; | |
| int get fileOffset => variable.fileOffset; | |
| /// Creates a [VariableGet] that reads the cached [variable] | |
| VariableGet createRead({DartType? promotedType, int? fileOffset}) => | |
| new VariableGet(variable, promotedType) | |
| ..fileOffset = fileOffset ?? this.fileOffset; | |
| Let createLet({required Expression body, int? fileOffset}) { | |
| return new Let(variable: variable, value: value, body: body) | |
| ..fileOffset = fileOffset ?? variable.fileOffset; | |
| } | |
| LocalInitializer createLocalInitializer() { | |
| return new LocalInitializer(variable, value)..fileOffset = fileOffset; | |
| } | |
| @override | |
| String toString() => '$runtimeType(variable=$variable,value=$value)'; | |
| } |