blob: f4dac1ff992099fe67a8136db38fe18a6bd25cd8 [file] [log] [blame] [edit]
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:kernel/ast.dart' as ir;
// ignore: implementation_imports
import 'package:kernel/src/printer.dart' as ir;
import 'package:kernel/text/ast_to_text.dart' as ir show debugNodeToString;
/// Collection of scope data collected for a single member.
class ClosureScopeModel {
/// Collection [ScopeInfo] data for the member.
KernelScopeInfo? scopeInfo;
/// Collected [CapturedScope] data for nodes.
Map<ir.Node, KernelCapturedScope> capturedScopesMap =
<ir.Node, KernelCapturedScope>{};
/// Collected [ScopeInfo] data for nodes.
Map<ir.LocalFunction, KernelScopeInfo> closuresToGenerate =
<ir.LocalFunction, KernelScopeInfo>{};
@override
String toString() {
return '$scopeInfo\n$capturedScopesMap\n$closuresToGenerate';
}
}
class KernelScopeInfo {
final Set<ir.VariableDeclaration> localsUsedInTryOrSync;
final bool hasThisLocal;
final Set<ir.VariableDeclaration> boxedVariables;
// If boxedVariables is empty, this will be null, because no variables will
// need to be boxed.
final NodeBox? capturedVariablesAccessor;
/// The set of variables that were defined in another scope, but are used in
/// this scope. The items in this set are either of type VariableDeclaration
/// or TypeParameterTypeWithContext.
Set<ir.Node /* VariableDeclaration | TypeParameterTypeWithContext */>
freeVariables = <ir.Node>{};
/// A set of type parameters that are defined in another scope and are only
/// used if runtime type information is checked. If runtime type information
/// needs to be retained, all of these type variables will be added ot the
/// freeVariables set. Whether these variables are actually used as
/// freeVariables will be set by the time this structure is converted to a
/// JsScopeInfo, so JsScopeInfo does not need to use them.
Map<TypeVariableTypeWithContext, Set<VariableUse>> freeVariablesForRti =
<TypeVariableTypeWithContext, Set<VariableUse>>{};
/// If true, `this` is used as a free variable, in this scope. It is stored
/// separately from [freeVariables] because there is no single
/// `VariableDeclaration` node that represents `this`.
bool thisUsedAsFreeVariable = false;
/// If true, `this` is used as a free variable, in this scope if we are also
/// performing runtime type checks. It is stored
/// separately from [thisUsedAsFreeVariable] because we don't know at this
/// stage if we will be needing type checks for this scope.
Set<VariableUse> thisUsedAsFreeVariableIfNeedsRti = <VariableUse>{};
KernelScopeInfo(this.hasThisLocal)
: localsUsedInTryOrSync = <ir.VariableDeclaration>{},
boxedVariables = <ir.VariableDeclaration>{},
capturedVariablesAccessor = null;
KernelScopeInfo.from(this.hasThisLocal, KernelScopeInfo info)
: localsUsedInTryOrSync = info.localsUsedInTryOrSync,
boxedVariables = info.boxedVariables,
capturedVariablesAccessor = null;
KernelScopeInfo.withBoxedVariables(
this.boxedVariables,
this.capturedVariablesAccessor,
this.localsUsedInTryOrSync,
this.freeVariables,
this.freeVariablesForRti,
this.thisUsedAsFreeVariable,
this.thisUsedAsFreeVariableIfNeedsRti,
this.hasThisLocal,
);
@override
String toString() {
StringBuffer sb = StringBuffer();
sb.write('KernelScopeInfo(this=$hasThisLocal,');
sb.write('freeVariables=$freeVariables,');
sb.write('localsUsedInTryOrSync={${localsUsedInTryOrSync.join(', ')}}');
String comma = '';
sb.write('freeVariablesForRti={');
freeVariablesForRti.forEach((key, value) {
sb.write('$comma$key:$value');
comma = ',';
});
sb.write('})');
return sb.toString();
}
}
class KernelCapturedScope extends KernelScopeInfo {
KernelCapturedScope(
super.boxedVariables,
super.capturedVariablesAccessor,
super.localsUsedInTryOrSync,
super.freeVariables,
super.freeVariablesForRti,
super.thisUsedAsFreeVariable,
super.thisUsedAsFreeVariableIfNeedsRti,
super.hasThisLocal,
) : super.withBoxedVariables();
// Loops through the free variables of an existing KernelCapturedScope and
// creates a new KernelCapturedScope that only captures type variables.
KernelCapturedScope.forSignature(KernelCapturedScope scope)
: this(
_empty,
null,
_empty,
Set.of(scope.freeVariables.whereType<TypeVariableTypeWithContext>()),
scope.freeVariablesForRti,
scope.thisUsedAsFreeVariable,
scope.thisUsedAsFreeVariableIfNeedsRti,
scope.hasThisLocal,
);
// Silly hack because we don't have const sets.
static final Set<ir.VariableDeclaration> _empty = {};
bool get requiresContextBox => boxedVariables.isNotEmpty;
}
class KernelCapturedLoopScope extends KernelCapturedScope {
final List<ir.VariableDeclaration> boxedLoopVariables;
KernelCapturedLoopScope(
Set<ir.VariableDeclaration> boxedVariables,
NodeBox? capturedVariablesAccessor,
this.boxedLoopVariables,
Set<ir.VariableDeclaration> localsUsedInTryOrSync,
Set<ir.Node /* VariableDeclaration | TypeVariableTypeWithContext */>
freeVariables,
Map<TypeVariableTypeWithContext, Set<VariableUse>> freeVariablesForRti,
bool thisUsedAsFreeVariable,
Set<VariableUse> thisUsedAsFreeVariableIfNeedsRti,
bool hasThisLocal,
) : super(
boxedVariables,
capturedVariablesAccessor,
localsUsedInTryOrSync,
freeVariables,
freeVariablesForRti,
thisUsedAsFreeVariable,
thisUsedAsFreeVariableIfNeedsRti,
hasThisLocal,
);
bool get hasBoxedLoopVariables => boxedLoopVariables.isNotEmpty;
}
/// A local variable to disambiguate between a variable that has been captured
/// from one scope to another. This is the ir.Node version that corresponds to
/// [BoxLocal].
class NodeBox {
final String name;
final ir.TreeNode executableContext;
NodeBox(this.name, this.executableContext);
}
sealed class VariableUse {}
enum SimpleVariableUse implements VariableUse {
/// An explicit variable use.
///
/// For type variable this is an explicit as-cast, an is-test or a type
/// literal.
explicit,
/// A type variable used in the type of a local variable.
localType,
/// A type variable used in an implicit cast.
implicitCast,
/// A type variable passed as the type argument of a list literal.
listLiteral,
/// A type variable passed as the type argument of a set literal.
setLiteral,
/// A type variable passed as the type argument of a map literal.
mapLiteral,
/// A type variable in a field type.
fieldType,
}
/// A type variable in a parameter type of a member.
class MemberParameterVariableUse implements VariableUse {
final ir.Member member;
MemberParameterVariableUse(this.member);
@override
int get hashCode => Object.hash(MemberParameterVariableUse, member);
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! MemberParameterVariableUse) return false;
return member == other.member;
}
@override
String toString() => 'MemberParameterVariableUse(member=$member)';
}
/// A type variable in a parameter type of a local function.
class LocalParameterVariableUse implements VariableUse {
final ir.LocalFunction localFunction;
LocalParameterVariableUse(this.localFunction);
@override
int get hashCode => Object.hash(LocalParameterVariableUse, localFunction);
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! LocalParameterVariableUse) return false;
return localFunction == other.localFunction;
}
@override
String toString() =>
'LocalParameterVariableUse(localFunction=$localFunction)';
}
/// A type variable used in a return type of a member.
class MemberReturnTypeVariableUse implements VariableUse {
final ir.Member member;
MemberReturnTypeVariableUse(this.member);
@override
int get hashCode => Object.hash(MemberReturnTypeVariableUse, member);
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! MemberReturnTypeVariableUse) return false;
return member == other.member;
}
@override
String toString() => 'MemberReturnTypeVariableUse(member=$member)';
}
/// A type variable used in a return type of a local function.
class LocalReturnTypeVariableUse implements VariableUse {
final ir.LocalFunction localFunction;
LocalReturnTypeVariableUse(this.localFunction);
@override
int get hashCode => Object.hash(LocalReturnTypeVariableUse, localFunction);
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! LocalReturnTypeVariableUse) return false;
return localFunction == other.localFunction;
}
@override
String toString() =>
'LocalReturnTypeVariableUse(localFunction=$localFunction)';
}
/// A type variable passed as a type argument to a constructor.
class ConstructorTypeArgumentVariableUse implements VariableUse {
final ir.Member member;
ConstructorTypeArgumentVariableUse(this.member);
@override
int get hashCode => Object.hash(ConstructorTypeArgumentVariableUse, member);
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! ConstructorTypeArgumentVariableUse) return false;
return member == other.member;
}
@override
String toString() => 'ConstructorTypeArgumentVariableUse(member=$member)';
}
/// A type variable passed as a type argument to a static method.
class StaticTypeArgumentVariableUse implements VariableUse {
final ir.Procedure procedure;
StaticTypeArgumentVariableUse(this.procedure);
@override
int get hashCode => Object.hash(StaticTypeArgumentVariableUse, procedure);
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! StaticTypeArgumentVariableUse) return false;
return procedure == other.procedure;
}
@override
String toString() => 'StaticTypeArgumentVariableUse(procedure=$procedure)';
}
/// A type variable passed as a type argument to an instance method.
class InstanceTypeArgumentVariableUse implements VariableUse {
final ir.Expression invocation;
InstanceTypeArgumentVariableUse(this.invocation);
@override
int get hashCode => Object.hash(InstanceTypeArgumentVariableUse, invocation);
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! InstanceTypeArgumentVariableUse) return false;
return invocation == other.invocation;
}
@override
String toString() =>
'InstanceTypeArgumentVariableUse(invocation=$invocation)';
}
/// A type variable passed as a type argument to a local function.
class LocalTypeArgumentVariableUse implements VariableUse {
final ir.LocalFunction localFunction;
final ir.Expression invocation;
LocalTypeArgumentVariableUse(this.localFunction, this.invocation);
@override
int get hashCode =>
Object.hash(LocalTypeArgumentVariableUse, localFunction, invocation);
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! LocalTypeArgumentVariableUse) return false;
return localFunction == other.localFunction &&
invocation == other.invocation;
}
@override
String toString() =>
'LocalTypeArgumentVariableUse(localFunction=$localFunction,invocation=$invocation)';
}
/// A type argument of an generic instantiation.
class InstantiationTypeArgumentVariableUse implements VariableUse {
final ir.Instantiation instantiation;
InstantiationTypeArgumentVariableUse(this.instantiation);
@override
int get hashCode =>
Object.hash(InstantiationTypeArgumentVariableUse, instantiation);
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! InstantiationTypeArgumentVariableUse) return false;
return instantiation == other.instantiation;
}
@override
String toString() =>
'InstantiationTypeArgumentVariableUse(instantiation=$instantiation)';
}
enum TypeVariableKind { cls, method, local }
/// A fake ir.Node that holds the TypeParameterType as well as the context in
/// which it occurs.
class TypeVariableTypeWithContext implements ir.Node {
final ir.TreeNode? context;
final ir.TypeParameterType type;
final TypeVariableKind kind;
final ir.TreeNode? typeDeclaration;
/// [context] can be either an ir.Member or a ir.FunctionDeclaration or
/// ir.FunctionExpression.
factory TypeVariableTypeWithContext(
ir.TypeParameterType type,
ir.TreeNode? context,
) {
TypeVariableKind kind;
ir.GenericDeclaration? typeDeclaration = type.parameter.declaration;
// TODO(fishythefish): Use exhaustive pattern switch.
if (typeDeclaration is ir.Class) {
// We have a class type variable, like `T` in `class Class<T> { ... }`.
kind = TypeVariableKind.cls;
} else if (typeDeclaration is ir.Procedure) {
if (typeDeclaration.isFactory) {
// We have a synthesized generic method type variable for a class type
// variable.
// TODO(johnniwinther): Handle constructor/factory type variables as
// method type variables.
kind = TypeVariableKind.cls;
typeDeclaration = typeDeclaration.enclosingClass;
} else {
// We have a generic method type variable, like `T` in
// `m<T>() { ... }`.
kind = TypeVariableKind.method;
context = typeDeclaration;
}
} else {
// We have a generic local function type variable, like `T` in
// `m() { local<T>() { ... } ... }`.
assert(
typeDeclaration is ir.LocalFunction,
"Unexpected type declaration: $typeDeclaration",
);
kind = TypeVariableKind.local;
context = typeDeclaration;
}
return TypeVariableTypeWithContext.internal(
type,
context,
kind,
typeDeclaration,
);
}
TypeVariableTypeWithContext.internal(
this.type,
this.context,
this.kind,
this.typeDeclaration,
);
@override
R accept<R>(ir.Visitor<R> v) {
throw UnsupportedError('TypeVariableTypeWithContext.accept');
}
@override
R accept1<R, A>(ir.Visitor1<R, A> v, A arg) {
throw UnsupportedError('TypeVariableTypeWithContext.accept1');
}
@override
Never visitChildren(ir.Visitor<Object?> v) {
throw UnsupportedError('TypeVariableTypeWithContext.visitChildren');
}
@override
int get hashCode => type.hashCode;
@override
bool operator ==(other) {
if (other is! TypeVariableTypeWithContext) return false;
return type == other.type && context == other.context;
}
@override
String toString() => 'TypeVariableTypeWithContext(${toStringInternal()})';
@override
String toStringInternal() =>
'type=${type.toStringInternal()},context=${context!.toStringInternal()},'
'kind=$kind,typeDeclaration=${typeDeclaration!.toStringInternal()}';
@override
String toText(ir.AstTextStrategy strategy) => type.toText(strategy);
@override
void toTextInternal(ir.AstPrinter printer) => type.toTextInternal(printer);
@override
String leakingDebugToString() => ir.debugNodeToString(this);
}