Optimize RTI capture

Change-Id: I1b1b363de442e118ccb1e2018748a2ba7c5fe9e6
Reviewed-on: https://dart-review.googlesource.com/51642
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
Johnni Winther 2018-04-19 16:00:28 +00:00 committed by commit-bot@chromium.org
parent fab5bef194
commit 6a8cad4877
15 changed files with 614 additions and 272 deletions

View file

@ -16,6 +16,7 @@ import '../kernel/element_map.dart';
import '../kernel/env.dart';
import '../options.dart';
import '../ssa/type_builder.dart';
import '../universe/selector.dart';
import '../world.dart';
import 'elements.dart';
import 'closure_visitors.dart';
@ -77,6 +78,7 @@ class KernelClosureAnalysis {
class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
final KernelToElementMapForBuilding _elementMap;
final GlobalLocalsMap _globalLocalsMap;
final CompilerOptions _options;
/// Map of the scoping information that corresponds to a particular entity.
Map<MemberEntity, ScopeInfo> _scopeMap = <MemberEntity, ScopeInfo>{};
@ -93,19 +95,8 @@ class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
Map<ir.TreeNode, ClosureRepresentationInfo> _localClosureRepresentationMap =
<ir.TreeNode, ClosureRepresentationInfo>{};
/// If true add type assertions to assert that at runtime the type is in line
/// with the stated type.
final bool _addTypeChecks;
/// If true, we are compiling using strong mode, and therefore we will create
/// a "signatureMethod" for a closure that can output the type. In this
/// instance, we may need access to a type variable that has not otherwise
/// been captured, and therefore we need to mark it as being used so that the
/// RTI optimization doesn't optimize it away..
final bool _strongMode;
KernelClosureConversionTask(Measurer measurer, this._elementMap,
this._globalLocalsMap, this._addTypeChecks, this._strongMode)
KernelClosureConversionTask(
Measurer measurer, this._elementMap, this._globalLocalsMap, this._options)
: super(measurer);
/// The combined steps of generating our intermediate representation of
@ -118,68 +109,127 @@ class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
void convertClosures(Iterable<MemberEntity> processedEntities,
ClosedWorldRefiner closedWorldRefiner) {}
void _updateScopeBasedOnRtiNeed(
KernelScopeInfo scope,
bool Function(ClassEntity) classNeedsTypeArguments,
bool Function(MemberEntity) methodNeedsTypeArguments,
bool Function(ir.Node) localFunctionNeedsTypeArguments,
void _updateScopeBasedOnRtiNeed(KernelScopeInfo scope, ClosureRtiNeed rtiNeed,
MemberEntity outermostEntity) {
if (scope.thisUsedAsFreeVariableIfNeedsRti &&
(classNeedsTypeArguments(outermostEntity.enclosingClass) ||
// TODO(johnniwinther): Instead of _strongMode, make this branch test
// if an added signature method needs type arguments (see comment in
// TypeVariableKind.method branch below.
_strongMode)) {
scope.thisUsedAsFreeVariable = true;
}
if (_addTypeChecks) {
scope.freeVariables.addAll(scope.freeVariablesForRti);
} else {
for (TypeVariableTypeWithContext typeVariable
in scope.freeVariablesForRti) {
switch (typeVariable.kind) {
case TypeVariableKind.cls:
if (classNeedsTypeArguments(
_elementMap.getClass(typeVariable.typeDeclaration))) {
scope.freeVariables.add(typeVariable);
bool includeForRti(Set<VariableUse> useSet) {
for (VariableUse usage in useSet) {
switch (usage.kind) {
case VariableUseKind.explicit:
return true;
break;
case VariableUseKind.implicitCast:
if (_options.implicitDowncastCheckPolicy.isEmitted) {
return true;
}
break;
case TypeVariableKind.method:
if (methodNeedsTypeArguments(
_elementMap.getMember(typeVariable.typeDeclaration)) ||
// In Dart 2, we have the notion of generic methods. This is
// partly implemented by adding a "method signature" function to
// closure classes. This signature reports the type of the
// method, and therefore may access the type variable of the
// parameter, which might otherwise not be used (and therefore
// isn't captured in the set that `methodNeedsTypeArguments`
// compares against.
// TODO(johnniwinther): Include this reasoning inside
// [methodNeedsTypeArguments] rather than an add on here.
_strongMode &&
scope.freeVariablesForRti.contains(typeVariable)) {
scope.freeVariables.add(typeVariable);
case VariableUseKind.localType:
if (_options.assignmentCheckPolicy.isEmitted) {
return true;
}
break;
case TypeVariableKind.local:
if (localFunctionNeedsTypeArguments(typeVariable.typeDeclaration)) {
scope.freeVariables.add(typeVariable);
case VariableUseKind.constructorTypeArgument:
ConstructorEntity constructor =
_elementMap.getConstructor(usage.member);
if (rtiNeed.classNeedsTypeArguments(constructor.enclosingClass)) {
return true;
}
break;
case TypeVariableKind.function:
case VariableUseKind.staticTypeArgument:
FunctionEntity method = _elementMap.getMethod(usage.member);
if (rtiNeed.methodNeedsTypeArguments(method)) {
return true;
}
break;
case VariableUseKind.instanceTypeArgument:
Selector selector = _elementMap.getSelector(usage.invocation);
if (rtiNeed.selectorNeedsTypeArguments(selector)) {
return true;
}
break;
case VariableUseKind.localTypeArgument:
// TODO(johnniwinther): We should be able to track direct local
// function invocations and not have to use the selector here.
Selector selector = _elementMap.getSelector(usage.invocation);
if (rtiNeed.localFunctionNeedsTypeArguments(usage.localFunction) ||
rtiNeed.selectorNeedsTypeArguments(selector)) {
return true;
}
break;
case VariableUseKind.memberParameter:
if (_options.parameterCheckPolicy.isEmitted) {
return true;
} else {
FunctionEntity method = _elementMap.getMethod(usage.member);
if (rtiNeed.methodNeedsSignature(method)) {
return true;
}
}
break;
case VariableUseKind.localParameter:
if (_options.parameterCheckPolicy.isEmitted) {
return true;
} else if (rtiNeed
.localFunctionNeedsSignature(usage.localFunction)) {
return true;
}
break;
case VariableUseKind.memberReturnType:
if (_options.assignmentCheckPolicy.isEmitted) {
return true;
} else {
FunctionEntity method = _elementMap.getMethod(usage.member);
if (rtiNeed.methodNeedsSignature(method)) {
return true;
}
}
break;
case VariableUseKind.localReturnType:
if (_options.assignmentCheckPolicy.isEmitted) {
return true;
} else if (rtiNeed
.localFunctionNeedsSignature(usage.localFunction)) {
return true;
}
break;
case VariableUseKind.fieldType:
if (_options.assignmentCheckPolicy.isEmitted ||
_options.parameterCheckPolicy.isEmitted) {
return true;
}
break;
case VariableUseKind.listLiteral:
if (rtiNeed.classNeedsTypeArguments(
_elementMap.commonElements.jsArrayClass)) {
return true;
}
break;
case VariableUseKind.mapLiteral:
if (rtiNeed.classNeedsTypeArguments(
_elementMap.commonElements.mapLiteralClass)) {
return true;
}
break;
}
}
return false;
}
if (includeForRti(scope.thisUsedAsFreeVariableIfNeedsRti)) {
scope.thisUsedAsFreeVariable = true;
}
scope.freeVariablesForRti.forEach(
(TypeVariableTypeWithContext typeVariable, Set<VariableUse> useSet) {
if (includeForRti(useSet)) {
scope.freeVariables.add(typeVariable);
}
});
}
Iterable<FunctionEntity> createClosureEntities(
JsClosedWorldBuilder closedWorldBuilder,
Map<MemberEntity, ScopeModel> closureModels,
{bool Function(ir.Node) localFunctionNeedsSignature,
bool Function(ClassEntity) classNeedsTypeArguments,
bool Function(FunctionEntity) methodNeedsTypeArguments,
bool Function(ir.Node) localFunctionNeedsTypeArguments}) {
ClosureRtiNeed rtiNeed) {
List<FunctionEntity> callMethods = <FunctionEntity>[];
closureModels.forEach((MemberEntity member, ScopeModel model) {
KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(member);
@ -192,8 +242,7 @@ class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
.forEach((ir.Node node, KernelCapturedScope scope) {
Map<Local, JRecordField> boxedVariables =
_elementMap.makeRecordContainer(scope, member, localsMap);
_updateScopeBasedOnRtiNeed(scope, classNeedsTypeArguments,
methodNeedsTypeArguments, localFunctionNeedsTypeArguments, member);
_updateScopeBasedOnRtiNeed(scope, rtiNeed, member);
if (scope is KernelCapturedLoopScope) {
_capturedScopesMap[node] = new JsCapturedLoopScope.from(
@ -222,11 +271,9 @@ class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
functionNode,
closuresToGenerate[node],
allBoxedVariables,
classNeedsTypeArguments,
methodNeedsTypeArguments,
localFunctionNeedsTypeArguments,
rtiNeed,
createSignatureMethod:
localFunctionNeedsSignature(functionNode.parent));
rtiNeed.localFunctionNeedsSignature(functionNode.parent));
// Add also for the call method.
_scopeMap[closureClassInfo.callMethod] = closureClassInfo;
_scopeMap[closureClassInfo.signatureMethod] = closureClassInfo;
@ -234,19 +281,14 @@ class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
// Set up capturedScope for signature method. This is distinct from
// _capturedScopesMap because there is no corresponding ir.Node for the
// signature.
if (localFunctionNeedsSignature(functionNode.parent) &&
if (rtiNeed.localFunctionNeedsSignature(functionNode.parent) &&
model.capturedScopesMap[functionNode] != null) {
KernelCapturedScope capturedScope =
model.capturedScopesMap[functionNode];
assert(capturedScope is! KernelCapturedLoopScope);
KernelCapturedScope signatureCapturedScope =
new KernelCapturedScope.forSignature(capturedScope);
_updateScopeBasedOnRtiNeed(
signatureCapturedScope,
classNeedsTypeArguments,
methodNeedsTypeArguments,
localFunctionNeedsTypeArguments,
member);
_updateScopeBasedOnRtiNeed(signatureCapturedScope, rtiNeed, member);
_capturedScopeForSignatureMap[closureClassInfo.signatureMethod] =
new JsCapturedScope.from(
{}, signatureCapturedScope, localsMap, _elementMap);
@ -269,12 +311,9 @@ class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
ir.FunctionNode node,
KernelScopeInfo info,
Map<Local, JRecordField> boxedVariables,
bool Function(ClassEntity) classNeedsTypeArguments,
bool Function(FunctionEntity) methodNeedsTypeArguments,
bool Function(ir.Node) localFunctionNeedsTypeArguments,
ClosureRtiNeed rtiNeed,
{bool createSignatureMethod}) {
_updateScopeBasedOnRtiNeed(info, classNeedsTypeArguments,
methodNeedsTypeArguments, localFunctionNeedsTypeArguments, member);
_updateScopeBasedOnRtiNeed(info, rtiNeed, member);
KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(member);
KernelClosureClassInfo closureClassInfo =
closedWorldBuilder.buildClosureClass(
@ -355,6 +394,150 @@ class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
}
}
enum VariableUseKind {
/// 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 map literal.
mapLiteral,
/// A type variable passed as a type argument to a constructor.
constructorTypeArgument,
/// A type variable passed as a type argument to a static method.
staticTypeArgument,
/// A type variable passed as a type argument to an instance method.
instanceTypeArgument,
/// A type variable passed as a type argument to a local function.
localTypeArgument,
/// A type variable in a parameter type of a member.
memberParameter,
/// A type variable in a parameter type of a local function.
localParameter,
/// A type variable used in a return type of a member.
memberReturnType,
/// A type variable used in a return type of a local function.
localReturnType,
/// A type variable in a field type.
fieldType,
}
class VariableUse {
final VariableUseKind kind;
final ir.Member member;
final ir.TreeNode /*ir.FunctionDeclaration|ir.FunctionExpression*/
localFunction;
final ir.MethodInvocation invocation;
const VariableUse._simple(this.kind)
: this.member = null,
this.localFunction = null,
this.invocation = null;
VariableUse.memberParameter(this.member)
: this.kind = VariableUseKind.memberParameter,
this.localFunction = null,
this.invocation = null;
VariableUse.localParameter(this.localFunction)
: this.kind = VariableUseKind.localParameter,
this.member = null,
this.invocation = null {
assert(localFunction is ir.FunctionDeclaration ||
localFunction is ir.FunctionExpression);
}
VariableUse.memberReturnType(this.member)
: this.kind = VariableUseKind.memberReturnType,
this.localFunction = null,
this.invocation = null;
VariableUse.localReturnType(this.localFunction)
: this.kind = VariableUseKind.localReturnType,
this.member = null,
this.invocation = null {
assert(localFunction is ir.FunctionDeclaration ||
localFunction is ir.FunctionExpression);
}
VariableUse.constructorTypeArgument(this.member)
: this.kind = VariableUseKind.constructorTypeArgument,
this.localFunction = null,
this.invocation = null;
VariableUse.staticTypeArgument(this.member)
: this.kind = VariableUseKind.staticTypeArgument,
this.localFunction = null,
this.invocation = null;
VariableUse.instanceTypeArgument(this.invocation)
: this.kind = VariableUseKind.instanceTypeArgument,
this.member = null,
this.localFunction = null;
VariableUse.localTypeArgument(this.localFunction, this.invocation)
: this.kind = VariableUseKind.localTypeArgument,
this.member = null {
assert(localFunction is ir.FunctionDeclaration ||
localFunction is ir.FunctionExpression);
}
static const VariableUse explicit =
const VariableUse._simple(VariableUseKind.explicit);
static const VariableUse localType =
const VariableUse._simple(VariableUseKind.localType);
static const VariableUse implicitCast =
const VariableUse._simple(VariableUseKind.implicitCast);
static const VariableUse listLiteral =
const VariableUse._simple(VariableUseKind.listLiteral);
static const VariableUse mapLiteral =
const VariableUse._simple(VariableUseKind.mapLiteral);
static const VariableUse fieldType =
const VariableUse._simple(VariableUseKind.fieldType);
int get hashCode =>
kind.hashCode * 11 +
member.hashCode * 13 +
localFunction.hashCode * 17 +
invocation.hashCode * 19;
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! VariableUse) return false;
return kind == other.kind &&
member == other.member &&
localFunction == other.localFunction &&
invocation == other.invocation;
}
String toString() => 'VariableUse(kind=$kind,member=$member,'
'localFunction=$localFunction,invocation=$invocation)';
}
class KernelScopeInfo {
final Set<ir.VariableDeclaration> localsUsedInTryOrSync;
final bool hasThisLocal;
@ -375,8 +558,8 @@ class KernelScopeInfo {
/// 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.
Set<TypeVariableTypeWithContext> freeVariablesForRti =
new Set<TypeVariableTypeWithContext>();
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
@ -387,7 +570,7 @@ class KernelScopeInfo {
/// 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.
bool thisUsedAsFreeVariableIfNeedsRti = false;
Set<VariableUse> thisUsedAsFreeVariableIfNeedsRti = new Set<VariableUse>();
KernelScopeInfo(this.hasThisLocal)
: localsUsedInTryOrSync = new Set<ir.VariableDeclaration>(),
@ -411,10 +594,16 @@ class KernelScopeInfo {
String toString() {
StringBuffer sb = new StringBuffer();
sb.write('this=$hasThisLocal,');
sb.write('KernelScopeInfo(this=$hasThisLocal,');
sb.write('freeVriables=$freeVariables,');
sb.write('localsUsedInTryOrSync={${localsUsedInTryOrSync.join(', ')}}');
sb.write('freeVariablesForRti={${freeVariablesForRti.join(', ')}}');
String comma = '';
sb.write('freeVariablesForRti={');
freeVariablesForRti.forEach((key, value) {
sb.write('$comma$key:$value');
comma = ',';
});
sb.write('})');
return sb.toString();
}
}
@ -482,11 +671,11 @@ class KernelCapturedScope extends KernelScopeInfo {
Set<ir.VariableDeclaration> boxedVariables,
NodeBox capturedVariablesAccessor,
Set<ir.VariableDeclaration> localsUsedInTryOrSync,
Set<ir.Node /* VariableDeclaration | TypeParameterTypeWithContext */ >
Set<ir.Node /* VariableDeclaration | TypeVariableTypeWithContext */ >
freeVariables,
Set<TypeVariableTypeWithContext> freeVariablesForRti,
Map<TypeVariableTypeWithContext, Set<VariableUse>> freeVariablesForRti,
bool thisUsedAsFreeVariable,
bool thisUsedAsFreeVariableIfNeedsRti,
Set<VariableUse> thisUsedAsFreeVariableIfNeedsRti,
bool hasThisLocal)
: super.withBoxedVariables(
boxedVariables,
@ -541,11 +730,11 @@ class KernelCapturedLoopScope extends KernelCapturedScope {
NodeBox capturedVariablesAccessor,
this.boxedLoopVariables,
Set<ir.VariableDeclaration> localsUsedInTryOrSync,
Set<ir.Node /* VariableDeclaration | TypeParameterTypeWithContext */ >
Set<ir.Node /* VariableDeclaration | TypeVariableTypeWithContext */ >
freeVariables,
Set<TypeVariableTypeWithContext> freeVariablesForRti,
Map<TypeVariableTypeWithContext, Set<VariableUse>> freeVariablesForRti,
bool thisUsedAsFreeVariable,
bool thisUsedAsFreeVariableIfNeedsRti,
Set<VariableUse> thisUsedAsFreeVariableIfNeedsRti,
bool hasThisLocal)
: super(
boxedVariables,
@ -959,3 +1148,17 @@ class TypeVariableTypeWithContext implements ir.Node {
'TypeVariableTypeWithContext(type=$type,context=$context,'
'kind=$kind,typeDeclaration=$typeDeclaration)';
}
abstract class ClosureRtiNeed {
bool classNeedsTypeArguments(ClassEntity cls);
bool methodNeedsTypeArguments(FunctionEntity method);
bool methodNeedsSignature(MemberEntity method);
bool localFunctionNeedsTypeArguments(ir.Node node);
bool localFunctionNeedsSignature(ir.Node node);
bool selectorNeedsTypeArguments(Selector selector);
}

View file

@ -68,18 +68,16 @@ class CapturedScopeBuilder extends ir.Visitor {
/// have unique names.
int _boxCounter = 0;
/// Set to `true` in the visitor if a type annotation is always needed.
/// The current usage of a type annotation.
///
/// This is for instance the case for expressions like `o is T` and `o as T`.
bool _contextNeedsType = false;
/// This is updated in the visitor to distinguish between unconditional
/// type variable usage, such as type literals and is tests, and conditional
/// type variable usage, such as type argument in method invocations.
VariableUse _currentTypeUsage;
CapturedScopeBuilder(this._model, this._options, {bool hasThisLocal})
: this._hasThisLocal = hasThisLocal;
/// If true add type assetions to assert that at runtime the type is in line
/// with the stated type.
bool get _addTypeChecks => _options.enableTypeAssertions;
/// Update the [CapturedScope] object corresponding to
/// this node if any variables are captured.
void attachCapturedScopeVariables(ir.TreeNode node) {
@ -179,25 +177,31 @@ class CapturedScopeBuilder extends ir.Visitor {
@override
visitVariableGet(ir.VariableGet node) {
_markVariableAsUsed(node.variable);
node.visitChildren(this);
_markVariableAsUsed(node.variable, VariableUse.explicit);
// Don't visit `node.promotedType`.
}
@override
visitVariableSet(ir.VariableSet node) {
_mutatedVariables.add(node.variable);
_markVariableAsUsed(node.variable);
if (_addTypeChecks) node.variable.type.accept(this);
_markVariableAsUsed(node.variable, VariableUse.explicit);
visitInContext(node.variable.type, VariableUse.localType);
node.visitChildren(this);
}
@override
visitVariableDeclaration(ir.VariableDeclaration declaration) {
if (!declaration.isFieldFormal) {
_scopeVariables.add(declaration);
void handleVariableDeclaration(
ir.VariableDeclaration node, VariableUse usage) {
if (!node.isFieldFormal) {
_scopeVariables.add(node);
}
declaration.visitChildren(this);
visitInContext(node.type, usage);
node.initializer?.accept(this);
}
@override
visitVariableDeclaration(ir.VariableDeclaration node) {
handleVariableDeclaration(node, VariableUse.localType);
}
/// Add this variable to the set of free variables if appropriate and add to
@ -207,9 +211,10 @@ class CapturedScopeBuilder extends ir.Visitor {
/// checked.
void _markVariableAsUsed(
ir.Node /* VariableDeclaration | TypeParameterTypeWithContext */ variable,
{bool onlyForRtiChecks = false}) {
VariableUse usage) {
assert(variable is ir.VariableDeclaration ||
variable is TypeVariableTypeWithContext);
assert(usage != null);
if (_isInsideClosure && !_inCurrentContext(variable)) {
// If the element is not declared in the current function and the element
// is not the closure itself we need to mark the element as free variable.
@ -217,10 +222,12 @@ class CapturedScopeBuilder extends ir.Visitor {
// optimization: factories have type parameters as function
// parameters, and type parameters are declared in the class, not
// the factory.
if (!onlyForRtiChecks) {
if (usage == VariableUse.explicit) {
_currentScopeInfo.freeVariables.add(variable);
} else {
_currentScopeInfo.freeVariablesForRti.add(variable);
_currentScopeInfo.freeVariablesForRti
.putIfAbsent(variable, () => new Set<VariableUse>())
.add(usage);
}
}
if (_inTry && variable is ir.VariableDeclaration) {
@ -230,41 +237,36 @@ class CapturedScopeBuilder extends ir.Visitor {
@override
void visitThisExpression(ir.ThisExpression thisExpression) {
if (_hasThisLocal) _registerNeedsThis();
if (_hasThisLocal) _registerNeedsThis(VariableUse.explicit);
}
@override
void visitTypeParameter(ir.TypeParameter typeParameter) {
if (_addTypeChecks) {
ir.TreeNode context = _executableContext;
TypeVariableTypeWithContext typeVariable =
new TypeVariableTypeWithContext(
new ir.TypeParameterType(typeParameter),
// If this typeParameter is part of a typedef then its parent is
// null because it has no context. Just pass in null for the
// context in that case.
typeParameter.parent != null
? typeParameter.parent.parent
: null);
if (_isInsideClosure && context is ir.Procedure && context.isFactory) {
// This is a closure in a factory constructor. Since there is no
// [:this:], we have to mark the type arguments as free variables to
// capture them in the closure.
_useTypeVariableAsLocal(typeVariable);
}
ir.TreeNode context = _executableContext;
TypeVariableTypeWithContext typeVariable = new TypeVariableTypeWithContext(
new ir.TypeParameterType(typeParameter),
// If this typeParameter is part of a typedef then its parent is
// null because it has no context. Just pass in null for the
// context in that case.
typeParameter.parent != null ? typeParameter.parent.parent : null);
if (_isInsideClosure && context is ir.Procedure && context.isFactory) {
// This is a closure in a factory constructor. Since there is no
// [:this:], we have to mark the type arguments as free variables to
// capture them in the closure.
_useTypeVariableAsLocal(typeVariable, _currentTypeUsage);
}
if (_executableContext is ir.Member && _executableContext is! ir.Field) {
// In checked mode, using a type variable in a type annotation may lead
// to a runtime type check that needs to access the type argument and
// therefore the closure needs a this-element, if it is not in a field
// initializer; field initializers are evaluated in a context where
// the type arguments are available in locals.
if (_executableContext is ir.Member && _executableContext is! ir.Field) {
// In checked mode, using a type variable in a type annotation may lead
// to a runtime type check that needs to access the type argument and
// therefore the closure needs a this-element, if it is not in a field
// initializer; field initializers are evaluated in a context where
// the type arguments are available in locals.
if (_hasThisLocal) {
_registerNeedsThis();
} else {
_useTypeVariableAsLocal(typeVariable);
}
if (_hasThisLocal) {
_registerNeedsThis(_currentTypeUsage);
} else {
_useTypeVariableAsLocal(typeVariable, _currentTypeUsage);
}
}
}
@ -274,12 +276,12 @@ class CapturedScopeBuilder extends ir.Visitor {
/// If [onlyIfNeedsRti] is true, set thisUsedAsFreeVariableIfNeedsRti to true
/// instead of thisUsedAsFreeVariable as we will only use `this` if runtime
/// type information is checked.
void _registerNeedsThis({bool onlyIfNeedsRti = false}) {
void _registerNeedsThis(VariableUse usage) {
if (_isInsideClosure) {
if (!onlyIfNeedsRti) {
if (usage == VariableUse.explicit) {
_currentScopeInfo.thisUsedAsFreeVariable = true;
} else {
_currentScopeInfo.thisUsedAsFreeVariableIfNeedsRti = true;
_currentScopeInfo.thisUsedAsFreeVariableIfNeedsRti.add(usage);
}
}
}
@ -362,19 +364,30 @@ class CapturedScopeBuilder extends ir.Visitor {
scope.hasThisLocal);
}
void visitSuperMethodInvocation(ir.SuperMethodInvocation invocation) {
if (_hasThisLocal) _registerNeedsThis();
invocation.visitChildren(this);
void visitSuperMethodInvocation(ir.SuperMethodInvocation node) {
if (_hasThisLocal) {
_registerNeedsThis(VariableUse.explicit);
}
if (node.arguments.types.isNotEmpty) {
visitListInContext(node.arguments.types,
new VariableUse.staticTypeArgument(node.interfaceTarget));
}
ir.visitList(node.arguments.positional, this);
ir.visitList(node.arguments.named, this);
}
void visitSuperPropertySet(ir.SuperPropertySet propertySet) {
if (_hasThisLocal) _registerNeedsThis();
propertySet.visitChildren(this);
void visitSuperPropertySet(ir.SuperPropertySet node) {
if (_hasThisLocal) {
_registerNeedsThis(VariableUse.explicit);
}
node.visitChildren(this);
}
void visitSuperPropertyGet(ir.SuperPropertyGet propertyGet) {
if (_hasThisLocal) _registerNeedsThis();
propertyGet.visitChildren(this);
void visitSuperPropertyGet(ir.SuperPropertyGet node) {
if (_hasThisLocal) {
_registerNeedsThis(VariableUse.explicit);
}
node.visitChildren(this);
}
void visitInvokable(ir.TreeNode node) {
@ -417,16 +430,21 @@ class CapturedScopeBuilder extends ir.Visitor {
assert(freeVariables.isEmpty || savedIsInsideClosure);
for (ir.Node freeVariable in freeVariables) {
_capturedVariables.add(freeVariable);
_markVariableAsUsed(freeVariable);
}
for (ir.Node freeVariableForRti in savedScopeInfo.freeVariablesForRti) {
_markVariableAsUsed(freeVariableForRti, onlyForRtiChecks: true);
_markVariableAsUsed(freeVariable, VariableUse.explicit);
}
savedScopeInfo.freeVariablesForRti.forEach(
(TypeVariableTypeWithContext freeVariableForRti,
Set<VariableUse> useSet) {
for (VariableUse usage in useSet) {
_markVariableAsUsed(freeVariableForRti, usage);
}
});
if (_isInsideClosure && savedScopeInfo.thisUsedAsFreeVariable) {
_currentScopeInfo.thisUsedAsFreeVariable = true;
}
if (_isInsideClosure && savedScopeInfo.thisUsedAsFreeVariableIfNeedsRti) {
_currentScopeInfo.thisUsedAsFreeVariableIfNeedsRti = true;
if (_isInsideClosure) {
_currentScopeInfo.thisUsedAsFreeVariableIfNeedsRti
.addAll(savedScopeInfo.thisUsedAsFreeVariableIfNeedsRti);
}
}
@ -447,7 +465,9 @@ class CapturedScopeBuilder extends ir.Visitor {
@override
void visitField(ir.Field field) {
_currentTypeUsage = VariableUse.fieldType;
visitInvokable(field);
_currentTypeUsage = null;
}
@override
@ -472,32 +492,141 @@ class CapturedScopeBuilder extends ir.Visitor {
@override
visitTypeParameterType(ir.TypeParameterType type) {
_analyzeTypeVariable(type, onlyIfNeedsRti: !_contextNeedsType);
_analyzeTypeVariable(type, _currentTypeUsage);
}
visitInContext(ir.Node node, VariableUse use) {
VariableUse oldCurrentTypeUsage = _currentTypeUsage;
_currentTypeUsage = use;
node?.accept(this);
_currentTypeUsage = oldCurrentTypeUsage;
}
visitListInContext(List<ir.Node> nodes, VariableUse use) {
VariableUse oldCurrentTypeUsage = _currentTypeUsage;
_currentTypeUsage = use;
ir.visitList(nodes, this);
_currentTypeUsage = oldCurrentTypeUsage;
}
visitChildrenInContext(ir.Node node, VariableUse use) {
VariableUse oldCurrentTypeUsage = _currentTypeUsage;
_currentTypeUsage = use;
node.visitChildren(this);
_currentTypeUsage = oldCurrentTypeUsage;
}
@override
visitTypeLiteral(ir.TypeLiteral node) {
bool oldContextNeedsType = _contextNeedsType;
_contextNeedsType = true;
node.visitChildren(this);
_contextNeedsType = oldContextNeedsType;
visitChildrenInContext(node, VariableUse.explicit);
}
@override
visitIsExpression(ir.IsExpression node) {
bool oldContextNeedsType = _contextNeedsType;
_contextNeedsType = true;
node.visitChildren(this);
_contextNeedsType = oldContextNeedsType;
node.operand.accept(this);
visitInContext(node.type, VariableUse.explicit);
}
@override
visitAsExpression(ir.AsExpression node) {
bool oldContextNeedsType = _contextNeedsType;
_contextNeedsType =
!node.isTypeError || _options.implicitDowncastCheckPolicy.isEmitted;
node.visitChildren(this);
_contextNeedsType = oldContextNeedsType;
node.operand.accept(this);
visitInContext(node.type,
node.isTypeError ? VariableUse.implicitCast : VariableUse.explicit);
}
@override
visitFunctionNode(ir.FunctionNode node) {
VariableUse parameterUsage = node.parent is ir.Member
? new VariableUse.memberParameter(node.parent)
: new VariableUse.localParameter(node.parent);
visitListInContext(node.typeParameters, parameterUsage);
for (ir.VariableDeclaration declaration in node.positionalParameters) {
handleVariableDeclaration(declaration, parameterUsage);
}
for (ir.VariableDeclaration declaration in node.namedParameters) {
handleVariableDeclaration(declaration, parameterUsage);
}
visitInContext(
node.returnType,
node.parent is ir.Member
? new VariableUse.memberReturnType(node.parent)
: new VariableUse.localReturnType(node.parent));
node.body?.accept(this);
}
@override
visitListLiteral(ir.ListLiteral node) {
visitInContext(node.typeArgument, VariableUse.listLiteral);
ir.visitList(node.expressions, this);
}
@override
visitMapLiteral(ir.MapLiteral node) {
visitInContext(node.keyType, VariableUse.mapLiteral);
visitInContext(node.valueType, VariableUse.mapLiteral);
ir.visitList(node.entries, this);
}
@override
visitStaticInvocation(ir.StaticInvocation node) {
if (node.arguments.types.isNotEmpty) {
VariableUse usage;
if (node.target.kind == ir.ProcedureKind.Factory) {
usage = new VariableUse.constructorTypeArgument(node.target);
} else {
usage = new VariableUse.staticTypeArgument(node.target);
}
visitListInContext(node.arguments.types, usage);
}
ir.visitList(node.arguments.positional, this);
ir.visitList(node.arguments.named, this);
}
@override
visitConstructorInvocation(ir.ConstructorInvocation node) {
if (node.arguments.types.isNotEmpty) {
visitListInContext(node.arguments.types,
new VariableUse.constructorTypeArgument(node.target));
}
ir.visitList(node.arguments.positional, this);
ir.visitList(node.arguments.named, this);
}
@override
visitConditionalExpression(ir.ConditionalExpression node) {
node.condition.accept(this);
node.then.accept(this);
node.otherwise.accept(this);
// Don't visit `node.staticType`.
}
@override
visitMethodInvocation(ir.MethodInvocation node) {
ir.TreeNode receiver = node.receiver;
receiver.accept(this);
if (node.arguments.types.isNotEmpty) {
VariableUse usage;
if (receiver is ir.VariableGet &&
(receiver.variable.parent is ir.FunctionDeclaration ||
receiver.variable.parent is ir.FunctionExpression)) {
usage =
new VariableUse.localTypeArgument(receiver.variable.parent, node);
} else {
usage = new VariableUse.instanceTypeArgument(node);
}
visitListInContext(node.arguments.types, usage);
}
ir.visitList(node.arguments.positional, this);
ir.visitList(node.arguments.named, this);
}
@override
visitCatch(ir.Catch node) {
visitInContext(node.guard, VariableUse.explicit);
node.exception?.accept(this);
node.stackTrace?.accept(this);
node.body.accept(this);
}
/// Returns true if the node is a field, or a constructor (factory or
@ -507,8 +636,8 @@ class CapturedScopeBuilder extends ir.Visitor {
node is ir.Field ||
(node is ir.Procedure && node.isFactory);
void _analyzeTypeVariable(ir.TypeParameterType type,
{bool onlyIfNeedsRti: true}) {
void _analyzeTypeVariable(ir.TypeParameterType type, VariableUse usage) {
assert(usage != null);
if (_outermostNode is ir.Member) {
TypeVariableTypeWithContext typeVariable =
new TypeVariableTypeWithContext(type, _outermostNode);
@ -516,17 +645,15 @@ class CapturedScopeBuilder extends ir.Visitor {
case TypeVariableKind.cls:
if (_isFieldOrConstructor(_outermostNode)) {
// Class type variable used in a field or constructor.
_useTypeVariableAsLocal(typeVariable,
onlyForRtiChecks: onlyIfNeedsRti);
_useTypeVariableAsLocal(typeVariable, usage);
} else {
// Class type variable used in a method.
_registerNeedsThis(onlyIfNeedsRti: onlyIfNeedsRti);
_registerNeedsThis(usage);
}
break;
case TypeVariableKind.method:
case TypeVariableKind.local:
_useTypeVariableAsLocal(typeVariable,
onlyForRtiChecks: onlyIfNeedsRti);
_useTypeVariableAsLocal(typeVariable, usage);
break;
case TypeVariableKind.function:
// The type variable is a function type variable, like `T` in
@ -540,11 +667,11 @@ class CapturedScopeBuilder extends ir.Visitor {
/// If [onlyForRtiChecks] is true, the variable will be added to a list
/// indicating it *may* be used only if runtime type information is checked.
void _useTypeVariableAsLocal(TypeVariableTypeWithContext typeVariable,
{bool onlyForRtiChecks: false}) {
void _useTypeVariableAsLocal(
TypeVariableTypeWithContext typeVariable, VariableUse usage) {
if (typeVariable.kind != TypeVariableKind.cls && !_options.strongMode) {
return;
}
_markVariableAsUsed(typeVariable, onlyForRtiChecks: onlyForRtiChecks);
_markVariableAsUsed(typeVariable, usage);
}
}

View file

@ -76,11 +76,7 @@ class JsBackendStrategy implements KernelBackendStrategy {
_elementEnvironment = _elementMap.elementEnvironment;
_commonElements = _elementMap.commonElements;
_closureDataLookup = new KernelClosureConversionTask(
_compiler.measurer,
_elementMap,
_globalLocalsMap,
_compiler.options.enableTypeAssertions,
_compiler.options.strongMode);
_compiler.measurer, _elementMap, _globalLocalsMap, _compiler.options);
JsClosedWorldBuilder closedWorldBuilder = new JsClosedWorldBuilder(
_elementMap, _closureDataLookup, _compiler.options);
return closedWorldBuilder._convertClosedWorld(
@ -281,11 +277,9 @@ class JsClosedWorldBuilder {
if (_options.disableRtiOptimization) {
rtiNeed = new TrivialRuntimeTypesNeed();
callMethods = _closureConversionTask.createClosureEntities(
this, map.toBackendMemberMap(closureModels, identity),
localFunctionNeedsSignature: (_) => true,
classNeedsTypeArguments: (_) => true,
methodNeedsTypeArguments: (_) => true,
localFunctionNeedsTypeArguments: (_) => true);
this,
map.toBackendMemberMap(closureModels, identity),
const TrivialClosureRtiNeed());
} else {
RuntimeTypesNeedImpl kernelRtiNeed = closedWorld.rtiNeed;
Set<ir.Node> localFunctionsNodesNeedingSignature = new Set<ir.Node>();
@ -308,23 +302,14 @@ class JsClosedWorldBuilder {
RuntimeTypesNeedImpl jRtiNeed =
_convertRuntimeTypesNeed(map, backendUsage, kernelRtiNeed);
callMethods = _closureConversionTask.createClosureEntities(
this, map.toBackendMemberMap(closureModels, identity),
localFunctionNeedsSignature: (ir.Node node) {
assert(node is ir.FunctionDeclaration ||
node is ir.FunctionExpression);
return backendUsage.isRuntimeTypeUsed
? true
: localFunctionsNodesNeedingSignature.contains(node);
},
classNeedsTypeArguments: jRtiNeed.classNeedsTypeArguments,
methodNeedsTypeArguments: jRtiNeed.methodNeedsTypeArguments,
localFunctionNeedsTypeArguments: (ir.Node node) {
assert(node is ir.FunctionDeclaration ||
node is ir.FunctionExpression);
return backendUsage.isRuntimeTypeUsed
? true
: localFunctionsNodesNeedingTypeArguments.contains(node);
});
this,
map.toBackendMemberMap(closureModels, identity),
new JsClosureRtiNeed(
backendUsage,
jRtiNeed,
localFunctionsNodesNeedingTypeArguments,
localFunctionsNodesNeedingSignature,
));
List<FunctionEntity> callMethodsNeedingSignature = <FunctionEntity>[];
for (ir.Node node in localFunctionsNodesNeedingSignature) {
@ -773,3 +758,53 @@ class TypeConverter extends DartTypeVisitor<DartType, Null> {
List<DartType> _visitList(List<DartType> list) =>
list.map<DartType>((t) => t.accept(this, null)).toList();
}
class TrivialClosureRtiNeed implements ClosureRtiNeed {
const TrivialClosureRtiNeed();
bool localFunctionNeedsSignature(ir.Node node) => true;
bool classNeedsTypeArguments(ClassEntity cls) => true;
bool methodNeedsTypeArguments(FunctionEntity method) => true;
bool localFunctionNeedsTypeArguments(ir.Node node) => true;
bool selectorNeedsTypeArguments(Selector selector) => true;
bool methodNeedsSignature(MemberEntity method) => true;
}
class JsClosureRtiNeed implements ClosureRtiNeed {
final BackendUsage backendUsage;
final RuntimeTypesNeed rtiNeed;
final Set<ir.Node> localFunctionsNodesNeedingTypeArguments;
final Set<ir.Node> localFunctionsNodesNeedingSignature;
JsClosureRtiNeed(
this.backendUsage,
this.rtiNeed,
this.localFunctionsNodesNeedingTypeArguments,
this.localFunctionsNodesNeedingSignature);
bool localFunctionNeedsSignature(ir.Node node) {
assert(node is ir.FunctionDeclaration || node is ir.FunctionExpression);
return backendUsage.isRuntimeTypeUsed
? true
: localFunctionsNodesNeedingSignature.contains(node);
}
bool classNeedsTypeArguments(ClassEntity cls) =>
rtiNeed.classNeedsTypeArguments(cls);
bool methodNeedsTypeArguments(FunctionEntity method) =>
rtiNeed.methodNeedsTypeArguments(method);
bool localFunctionNeedsTypeArguments(ir.Node node) {
assert(node is ir.FunctionDeclaration || node is ir.FunctionExpression);
return backendUsage.isRuntimeTypeUsed
? true
: localFunctionsNodesNeedingTypeArguments.contains(node);
}
bool selectorNeedsTypeArguments(Selector selector) =>
rtiNeed.selectorNeedsTypeArguments(selector);
bool methodNeedsSignature(MemberEntity method) =>
rtiNeed.methodNeedsSignature(method);
}

View file

@ -6,8 +6,7 @@ import 'package:expect/expect.dart';
@NoInline()
method<T>() {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `T`.
/*fields=[T],free=[T]*/ dynamic local() => <T>[];
/**/ dynamic local() => <T>[];
return local;
}

View file

@ -9,11 +9,7 @@ class A<T> {
/*element: A.method:hasThis*/
@NoInline()
method() {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `this`.
/*ast.hasThis*/
/*kernel.hasThis*/
/*strong.fields=[this],free=[this],hasThis*/
dynamic local() => <T>[];
/*hasThis*/ dynamic local() => <T>[];
return local;
}
}

View file

@ -6,8 +6,7 @@ import 'package:expect/expect.dart';
@NoInline()
method<T>() {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `T`.
/*fields=[T],free=[T]*/ dynamic local() => <T, int>{};
/**/ dynamic local() => <T, int>{};
return local;
}

View file

@ -9,11 +9,7 @@ class A<T> {
/*element: A.method:hasThis*/
@NoInline()
method() {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `T`.
/*ast.hasThis*/
/*kernel.hasThis*/
/*strong.fields=[this],free=[this],hasThis*/
dynamic local() => <T, int>{};
/*hasThis*/ dynamic local() => <T, int>{};
return local;
}
}

View file

@ -30,8 +30,7 @@ method2<T>(dynamic o) {
/*element: method3:*/
method3<T>(dynamic o) {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `T`.
/*fields=[T,o],free=[T,o]*/
/*fields=[o],free=[o]*/
T local() => o;
return local;
}

View file

@ -38,10 +38,7 @@ class Class2<T> {
class Class3<T> {
/*element: Class3.method3:hasThis*/
method3(dynamic o) {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `this`.
/*ast.fields=[o],free=[o],hasThis*/
/*kernel.fields=[o],free=[o],hasThis*/
/*strong.fields=[o,this],free=[o,this],hasThis*/
/*fields=[o],free=[o],hasThis*/
T local() => o;
return local;
}

View file

@ -3,17 +3,15 @@
// BSD-style license that can be found in the LICENSE file.
////////////////////////////////////////////////////////////////////////////////
/// A sound assignment to a local variable doesn't capture the type variable.
/// A sound initialization of a local variable doesn't capture the type
/// variable.
////////////////////////////////////////////////////////////////////////////////
/*element: Class1.:hasThis*/
class Class1<T> {
/*element: Class1.method1:hasThis*/
method1(T o) {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `this`.
/*ast.fields=[o],free=[o],hasThis*/
/*kernel.fields=[o],free=[o],hasThis*/
/*strong.fields=[o,this],free=[o,this],hasThis*/
/*fields=[o],free=[o],hasThis*/
dynamic local() {
T t = o;
return t;
@ -23,6 +21,25 @@ class Class1<T> {
}
}
////////////////////////////////////////////////////////////////////////////////
/// A sound assignment to a local variable doesn't capture the type variable.
////////////////////////////////////////////////////////////////////////////////
/*element: Class1b.:hasThis*/
class Class1b<T> {
/*element: Class1b.method1b:hasThis*/
method1b(T o) {
/*fields=[o],free=[o],hasThis*/
dynamic local() {
T t = null;
t = o;
return t;
}
return local;
}
}
////////////////////////////////////////////////////////////////////////////////
/// A local function parameter type is only captured in strong mode.
////////////////////////////////////////////////////////////////////////////////
@ -139,6 +156,7 @@ class Class8<T> {
main() {
new Class1<int>().method1(0).call();
new Class1b<int>().method1b(0).call();
new Class2<int>().method2().call(0);
new Class3<int>().method3(0).call();
new Class4<int>().method4(0).call();

View file

@ -7,8 +7,7 @@
////////////////////////////////////////////////////////////////////////////////
method1<T>(T o) {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `T`.
/*fields=[T,o],free=[T,o]*/
/*fields=[o],free=[o]*/
dynamic local() {
T t = o;
return t;

View file

@ -7,8 +7,7 @@
////////////////////////////////////////////////////////////////////////////////
method1<T>(T o) {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `T`.
/*fields=[T,o],free=[T,o]*/
/*fields=[o],free=[o]*/
dynamic local() {
T t = o;
return t;
@ -22,8 +21,7 @@ method1<T>(T o) {
////////////////////////////////////////////////////////////////////////////////
method2<T>() {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `T`.
/*fields=[T],free=[T]*/
/**/
dynamic local(T t) => t;
return local;
}
@ -33,8 +31,7 @@ method2<T>() {
////////////////////////////////////////////////////////////////////////////////
method3<T>(dynamic o) {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `T`.
/*fields=[T,o],free=[T,o]*/
/*fields=[o],free=[o]*/
T local() => o;
return local;
}
@ -64,8 +61,7 @@ T method5<T>(dynamic o) {
////////////////////////////////////////////////////////////////////////////////
method6<T>() {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `T`.
/*fields=[T],free=[T]*/
/**/
dynamic local(T t) {
/*fields=[t],free=[t]*/
dynamic inner() => t;
@ -80,8 +76,7 @@ method6<T>() {
////////////////////////////////////////////////////////////////////////////////
method7<T>(dynamic o) {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `T`.
/*fields=[T,o],free=[T,o]*/
/*fields=[o],free=[o]*/
T local() {
/*fields=[o],free=[o]*/
dynamic inner() => o;

View file

@ -10,10 +10,7 @@
class Class1<T> {
/*element: Class1.method1:hasThis*/
method1(T o) {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `this`.
/*ast.fields=[o],free=[o],hasThis*/
/*kernel.fields=[o],free=[o],hasThis*/
/*strong.fields=[o,this],free=[o,this],hasThis*/
/*fields=[o],free=[o],hasThis*/
dynamic local() {
T t = o;
return t;
@ -31,10 +28,7 @@ class Class1<T> {
class Class2<T> {
/*element: Class2.method2:hasThis*/
method2() {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `this`.
/*ast.hasThis*/
/*kernel.hasThis*/
/*strong.fields=[this],free=[this],hasThis*/
/*hasThis*/
dynamic local(T t) => t;
return local;
}
@ -48,10 +42,7 @@ class Class2<T> {
class Class3<T> {
/*element: Class3.method3:hasThis*/
method3(dynamic o) {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `this`.
/*ast.fields=[o],free=[o],hasThis*/
/*kernel.fields=[o],free=[o],hasThis*/
/*strong.fields=[o,this],free=[o,this],hasThis*/
/*fields=[o],free=[o],hasThis*/
T local() => o;
return local;
}
@ -93,10 +84,7 @@ class Class5<T> {
class Class6<T> {
/*element: Class6.method6:hasThis*/
method6() {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `this`.
/*ast.hasThis*/
/*kernel.hasThis*/
/*strong.fields=[this],free=[this],hasThis*/
/*hasThis*/
dynamic local(T t) {
/*fields=[t],free=[t],hasThis*/
dynamic inner() => t;
@ -115,10 +103,7 @@ class Class6<T> {
class Class7<T> {
/*element: Class7.method7:hasThis*/
method7(dynamic o) {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `this`.
/*ast.fields=[o],free=[o],hasThis*/
/*kernel.fields=[o],free=[o],hasThis*/
/*strong.fields=[o,this],free=[o,this],hasThis*/
/*fields=[o],free=[o],hasThis*/
T local() {
/*fields=[o],free=[o],hasThis*/
dynamic inner() => o;

View file

@ -32,10 +32,7 @@ class Class2a<T> {}
class Class2b<T> {
/*element: Class2b.method2:hasThis*/
method2() {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `this`.
/*ast.hasThis*/
/*kernel.hasThis*/
/*strong.fields=[this],free=[this],hasThis*/
/*hasThis*/
dynamic local() => new Class2a<T>();
return local;
}

View file

@ -32,8 +32,7 @@ class Class2a<T> {}
class Class2b<T> {
/*element: Class2b.method2:hasThis*/
method2() {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `this`.
/*fields=[this],free=[this],hasThis*/
/*hasThis*/
dynamic local() => new Class2a<T>();
return local;
}
@ -60,8 +59,7 @@ method3b<T>(o) {
method4a<T>(o) => o;
method4b<T>(o) {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `T`.
/*fields=[T,o],free=[T,o]*/
/*fields=[o],free=[o]*/
dynamic local() => method4a<T>(o);
return local;
}
@ -95,8 +93,7 @@ class Class6a {
}
method6b<T>(o) {
// TODO(johnniwinther): Improve rti tracking to avoid capture of `T`.
/*fields=[T,o],free=[T,o]*/
/*fields=[o],free=[o]*/
dynamic local() => new Class6a().method6a<T>(o);
return local;
}