blob: 5e86c2911382212ef09768a1266e24a8e7a957ed [file] [log] [blame] [edit]
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import '../visitor/ast.dart';
import 'dart_keywords.dart';
/// Holds [Symbol]s and assigns unique names to them.
///
/// [Scope]s form a tree from a single root scope. The names assigned to symbols
/// avoid collisions with other names in the same scope, and the parent scopes.
///
/// Usage:
/// - Create a root scope with [createRoot]
/// - Use [addChild] to create child scopes
/// - [add] [Symbol]s to the scopes
/// - Call [fillNames] on the root scope to name all the [Symbol]s
/// - Use [addPrivate] to create ad-hoc names during code generation
class Scope {
final String _debugName;
final _symbols = <Symbol>[];
final _children = <Scope>[];
final Scope? _parent;
final Set<String> _preUsedNames;
Namer? _namer;
Scope._(this._parent, this._debugName, this._preUsedNames);
static Scope createRoot(String debugName) =>
Scope._(null, debugName, const {});
/// Create a new [Scope] as a child of this one.
///
/// [fillNames] must not have been called yet.
Scope addChild(String debugName, {Set<String> preUsedNames = const {}}) {
assert(!_filled);
final ns = Scope._(this, debugName, preUsedNames);
_children.add(ns);
return ns;
}
/// Add a [Symbol] to this [Scope].
///
/// It's fine to add the [Symbol] to this [Scope] multiple times. It's
/// also fine to add the [Symbol] to multiple [Scope]s, as long as one of
/// the [Scope]s is an ancestor of the other (this is checked during
/// [fillNames]).
///
/// [fillNames] must not have been called yet.
void add(Symbol? symbol) {
assert(!_filled);
if (symbol != null) _symbols.add(symbol);
}
/// Add an ad-hoc name to the [Scope].
///
/// This is meant to be used during code generation, for generating unique
/// names for internal use only. It shouldn't be used for user visible names,
/// or for names that need to be used in multiple disparate places throughout
/// ffigen. If you're storing the name in a long term variable, you should
/// probably be using a proper [Symbol].
///
/// To help ensure correct use, only names beginning with '_' are allowed.
/// [fillNames] must have been called already.
String addPrivate(String name) {
assert(_filled);
assert(name.startsWith('_'));
return _namer!.add(name);
}
/// Fill in the names of all the [Symbol]s in this [Scope] and its children.
void fillNames() {
assert(_parent == null);
_fillNames(const {});
}
void _fillNames(Set<String> parentUsedNames) {
assert(!_filled);
final namer = Namer(parentUsedNames.union(_preUsedNames));
_namer = namer;
for (final symbol in _symbols) {
if (symbol._name == null) {
symbol._name = namer.add(symbol.oldName);
} else {
// Symbol already has a name. This can happen if the symbol is in
// multiple scopes, or in the same scope more than once. It's fine as
// long as the name isn't used by a different symbol earlier in this
// scope.
namer.markUsed(symbol._name!);
assert(!_symbols.any((s) => s != symbol && s._name == symbol._name));
}
}
for (final ns in _children) {
ns._fillNames(namer._used);
}
}
bool get _filled => _namer != null;
void debugPrint([String depth = '']) {
final newDepth = ' $depth';
print('$depth$_debugName {');
for (final s in _symbols) {
print('$newDepth$s');
}
for (final ns in _children) {
ns.debugPrint(newDepth);
}
print('$depth}');
}
int debugStackPrint() {
final depth = _parent?.debugStackPrint() ?? 0;
print('${' ' * depth}$_debugName');
return depth + 1;
}
@override
String toString() => _debugName;
}
/// Assigns unique names, avoiding collisions with existing names and keywords,
/// by postfixing '$[int]' if there's a collision.
///
/// This class is used internally by [Scope] to name [Symbol]s, and 99% of the
/// time you should use those instead of this.
class Namer {
final Set<String> _used;
Namer(this._used);
String add(String name) {
if (name.isEmpty) name = 'unnamed';
// TODO(https://github.com/dart-lang/native/issues/2054): Relax this.
final isKeyword = keywords.contains(name);
var newName = isKeyword ? '$name\$' : name;
for (var i = 1; _used.contains(newName); ++i) {
newName = '$name\$$i';
}
markUsed(newName);
return newName;
}
void markUsed(String name) => _used.add(name);
/// Returns a version of [name] that can safely be used in C code. Not
/// guaranteed to be unique.
static String cSafeName(String name) => name.replaceAll('\$', '_');
/// Returns a version of [name] suitable for inclusion in a string literal.
static String stringLiteral(String name) => name.replaceAll('\$', '\\\$');
}
/// A renamable string used to assign names to variables, types, etc.
///
/// Add the [Symbol] to a [Scope], and it will be assigned a name during the
/// transformation phase.
class Symbol extends AstNode {
final String oldName;
String? _name;
/// Only valid if [Scope.fillNames] has been called already.
String get name => _name!;
Symbol(this.oldName);
bool get isFilled => _name != null;
@override
String toString() => _name ?? oldName;
@override
void visit(Visitation visitation) => visitation.visitSymbol(this);
void forceFillForTesting() => _name = oldName;
}
mixin HasLocalScope on AstNode {
Scope? _localScope;
Scope get localScope => _localScope!;
set localScope(Scope ns) {
assert(!localScopeFilled);
_localScope = ns;
}
bool get localScopeFilled => _localScope != null;
}