mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:39:49 +00:00
[vm/aot] Support dynamic record field access in TFA
TEST=language/records/simple/dynamic_field_access_test Issue: https://github.com/dart-lang/sdk/issues/49719 Change-Id: I811db5c649988cbadf7ab29e5c4c70366f55e86b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/262845 Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
parent
d923f69436
commit
671717b578
|
@ -542,8 +542,7 @@ abstract class Target {
|
||||||
Class? concreteConstMapLiteralClass(CoreTypes coreTypes) => null;
|
Class? concreteConstMapLiteralClass(CoreTypes coreTypes) => null;
|
||||||
Class? concreteSetLiteralClass(CoreTypes coreTypes) => null;
|
Class? concreteSetLiteralClass(CoreTypes coreTypes) => null;
|
||||||
Class? concreteConstSetLiteralClass(CoreTypes coreTypes) => null;
|
Class? concreteConstSetLiteralClass(CoreTypes coreTypes) => null;
|
||||||
Class? concreteRecordLiteralClass(CoreTypes coreTypes) => null;
|
Class? concreteRecordClass(CoreTypes coreTypes) => null;
|
||||||
Class? concreteConstRecordLiteralClass(CoreTypes coreTypes) => null;
|
|
||||||
|
|
||||||
Class? concreteIntLiteralClass(CoreTypes coreTypes, int value) => null;
|
Class? concreteIntLiteralClass(CoreTypes coreTypes, int value) => null;
|
||||||
Class? concreteDoubleLiteralClass(CoreTypes coreTypes, double value) => null;
|
Class? concreteDoubleLiteralClass(CoreTypes coreTypes, double value) => null;
|
||||||
|
|
|
@ -453,14 +453,10 @@ class VmTarget extends Target {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Class concreteRecordLiteralClass(CoreTypes coreTypes) {
|
Class concreteRecordClass(CoreTypes coreTypes) {
|
||||||
return _record ??= coreTypes.index.getClass('dart:core', '_Record');
|
return _record ??= coreTypes.index.getClass('dart:core', '_Record');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Class concreteConstRecordLiteralClass(CoreTypes coreTypes) =>
|
|
||||||
concreteRecordLiteralClass(coreTypes);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Class? concreteIntLiteralClass(CoreTypes coreTypes, int value) {
|
Class? concreteIntLiteralClass(CoreTypes coreTypes, int value) {
|
||||||
const int bitsPerInt32 = 32;
|
const int bitsPerInt32 = 32;
|
||||||
|
|
|
@ -538,15 +538,10 @@ class _DispatchableInvocation extends _Invocation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(alexmarkov): Consider caching targets for Null type.
|
|
||||||
void _collectTargetsForNull(Map<Member, _ReceiverTypeBuilder> targets,
|
void _collectTargetsForNull(Map<Member, _ReceiverTypeBuilder> targets,
|
||||||
TypeFlowAnalysis typeFlowAnalysis) {
|
TypeFlowAnalysis typeFlowAnalysis) {
|
||||||
Class nullClass =
|
final Member? target = typeFlowAnalysis.hierarchyCache._nullTFClass
|
||||||
typeFlowAnalysis.environment.coreTypes.deprecatedNullClass;
|
.getDispatchTarget(selector);
|
||||||
|
|
||||||
Member? target = typeFlowAnalysis.hierarchyCache.hierarchy
|
|
||||||
.getDispatchTarget(nullClass, selector.name, setter: selector.isSetter);
|
|
||||||
|
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
if (kPrintTrace) {
|
if (kPrintTrace) {
|
||||||
tracePrint("Found $target for null receiver");
|
tracePrint("Found $target for null receiver");
|
||||||
|
@ -559,9 +554,12 @@ class _DispatchableInvocation extends _Invocation {
|
||||||
ConcreteType receiver,
|
ConcreteType receiver,
|
||||||
Map<Member, _ReceiverTypeBuilder> targets,
|
Map<Member, _ReceiverTypeBuilder> targets,
|
||||||
TypeFlowAnalysis typeFlowAnalysis) {
|
TypeFlowAnalysis typeFlowAnalysis) {
|
||||||
final TFClass cls = receiver.cls;
|
final cls = receiver.cls as _TFClassImpl;
|
||||||
|
|
||||||
Member? target = (cls as _TFClassImpl).getDispatchTarget(selector);
|
Member? target = cls.getDispatchTarget(selector);
|
||||||
|
if (cls.hasMutableDispatchTargets) {
|
||||||
|
cls.dependencyTracker.addDependentInvocation(this);
|
||||||
|
}
|
||||||
|
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
if (kPrintTrace) {
|
if (kPrintTrace) {
|
||||||
|
@ -1011,13 +1009,15 @@ class _TFClassImpl extends TFClass {
|
||||||
late final Map<Name, Member> _dispatchTargetsNonSetters =
|
late final Map<Name, Member> _dispatchTargetsNonSetters =
|
||||||
_initDispatchTargets(false);
|
_initDispatchTargets(false);
|
||||||
final _DependencyTracker dependencyTracker = new _DependencyTracker();
|
final _DependencyTracker dependencyTracker = new _DependencyTracker();
|
||||||
|
final bool hasMutableDispatchTargets;
|
||||||
|
|
||||||
/// Flag indicating if this class has a noSuchMethod() method not inherited
|
/// Flag indicating if this class has a noSuchMethod() method not inherited
|
||||||
/// from Object.
|
/// from Object.
|
||||||
/// Lazy initialized by ClassHierarchyCache.hasNonTrivialNoSuchMethod().
|
/// Lazy initialized by ClassHierarchyCache.hasNonTrivialNoSuchMethod().
|
||||||
bool? hasNonTrivialNoSuchMethod;
|
bool? hasNonTrivialNoSuchMethod;
|
||||||
|
|
||||||
_TFClassImpl(int id, Class classNode, this.superclass, this.supertypes)
|
_TFClassImpl(int id, Class classNode, this.superclass, this.supertypes,
|
||||||
|
{required this.hasMutableDispatchTargets})
|
||||||
: super(id, classNode) {
|
: super(id, classNode) {
|
||||||
supertypes.add(this);
|
supertypes.add(this);
|
||||||
}
|
}
|
||||||
|
@ -1056,6 +1056,7 @@ class _TFClassImpl extends TFClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
void addAllocatedSubtype(_TFClassImpl subType) {
|
void addAllocatedSubtype(_TFClassImpl subType) {
|
||||||
|
assert(subType == this || !hasMutableDispatchTargets);
|
||||||
_allocatedSubtypes.add(subType);
|
_allocatedSubtypes.add(subType);
|
||||||
_specializedConeType = null; // Reset cached specialization.
|
_specializedConeType = null; // Reset cached specialization.
|
||||||
}
|
}
|
||||||
|
@ -1064,6 +1065,7 @@ class _TFClassImpl extends TFClass {
|
||||||
Map<Name, Member> targets;
|
Map<Name, Member> targets;
|
||||||
final superclass = this.superclass;
|
final superclass = this.superclass;
|
||||||
if (superclass != null) {
|
if (superclass != null) {
|
||||||
|
assert(!superclass.hasMutableDispatchTargets);
|
||||||
targets = Map.from(setters
|
targets = Map.from(setters
|
||||||
? superclass._dispatchTargetsSetters
|
? superclass._dispatchTargetsSetters
|
||||||
: superclass._dispatchTargetsNonSetters);
|
: superclass._dispatchTargetsNonSetters);
|
||||||
|
@ -1093,6 +1095,15 @@ class _TFClassImpl extends TFClass {
|
||||||
: _dispatchTargetsNonSetters)[selector.name];
|
: _dispatchTargetsNonSetters)[selector.name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _addField(Field field) {
|
||||||
|
assert(hasMutableDispatchTargets);
|
||||||
|
assert(!field.isStatic && !field.isAbstract);
|
||||||
|
_dispatchTargetsNonSetters[field.name] = field;
|
||||||
|
if (field.hasSetter) {
|
||||||
|
_dispatchTargetsSetters[field.name] = field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String dump() => "$this {supers: $supertypes}";
|
String dump() => "$this {supers: $supertypes}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1167,7 +1178,6 @@ class GenericInterfacesInfoImpl implements GenericInterfacesInfo {
|
||||||
// TODO(alexmarkov): Rename to _TypeHierarchyImpl.
|
// TODO(alexmarkov): Rename to _TypeHierarchyImpl.
|
||||||
class _ClassHierarchyCache extends TypeHierarchy {
|
class _ClassHierarchyCache extends TypeHierarchy {
|
||||||
final TypeFlowAnalysis _typeFlowAnalysis;
|
final TypeFlowAnalysis _typeFlowAnalysis;
|
||||||
final ClosedWorldClassHierarchy hierarchy;
|
|
||||||
final TypeEnvironment environment;
|
final TypeEnvironment environment;
|
||||||
final Set<Class> allocatedClasses = new Set<Class>();
|
final Set<Class> allocatedClasses = new Set<Class>();
|
||||||
final Map<Class, _TFClassImpl> classes = <Class, _TFClassImpl>{};
|
final Map<Class, _TFClassImpl> classes = <Class, _TFClassImpl>{};
|
||||||
|
@ -1178,6 +1188,10 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
||||||
|
|
||||||
static final Name noSuchMethodName = new Name("noSuchMethod");
|
static final Name noSuchMethodName = new Name("noSuchMethod");
|
||||||
|
|
||||||
|
// Class of all record instances (could be synthetic if Target
|
||||||
|
// doesn't provide one).
|
||||||
|
final Class recordClass;
|
||||||
|
|
||||||
/// Class hierarchy is sealed after analysis is finished.
|
/// Class hierarchy is sealed after analysis is finished.
|
||||||
/// Once it is sealed, no new allocated classes may be added and no new
|
/// Once it is sealed, no new allocated classes may be added and no new
|
||||||
/// targets of invocations may appear.
|
/// targets of invocations may appear.
|
||||||
|
@ -1189,10 +1203,31 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
||||||
final Map<DynamicSelector, _DynamicTargetSet> _dynamicTargets =
|
final Map<DynamicSelector, _DynamicTargetSet> _dynamicTargets =
|
||||||
<DynamicSelector, _DynamicTargetSet>{};
|
<DynamicSelector, _DynamicTargetSet>{};
|
||||||
|
|
||||||
_ClassHierarchyCache(this._typeFlowAnalysis, this.hierarchy,
|
final Map<String, Field> _recordFields = <String, Field>{};
|
||||||
this.genericInterfacesInfo, this.environment, bool nullSafety)
|
|
||||||
: objectNoSuchMethod = hierarchy.getDispatchTarget(
|
@override
|
||||||
environment.coreTypes.objectClass, noSuchMethodName)!,
|
late final Type recordType = addAllocatedClass(recordClass);
|
||||||
|
|
||||||
|
late final _TFClassImpl _recordTFClass = getTFClass(recordClass);
|
||||||
|
|
||||||
|
late final _TFClassImpl _objectTFClass =
|
||||||
|
getTFClass(environment.coreTypes.objectClass);
|
||||||
|
|
||||||
|
late final _TFClassImpl _nullTFClass =
|
||||||
|
getTFClass(environment.coreTypes.deprecatedNullClass);
|
||||||
|
|
||||||
|
_ClassHierarchyCache(this._typeFlowAnalysis, this.genericInterfacesInfo,
|
||||||
|
this.environment, bool nullSafety)
|
||||||
|
: objectNoSuchMethod = environment.coreTypes.index
|
||||||
|
.getProcedure('dart:core', 'Object', 'noSuchMethod'),
|
||||||
|
recordClass = _typeFlowAnalysis.target
|
||||||
|
.concreteRecordClass(environment.coreTypes) ??
|
||||||
|
Class(
|
||||||
|
name: "&&Record",
|
||||||
|
implementedTypes: [
|
||||||
|
Supertype(environment.coreTypes.recordClass, [])
|
||||||
|
],
|
||||||
|
fileUri: artificialNodeUri),
|
||||||
super(environment.coreTypes, nullSafety);
|
super(environment.coreTypes, nullSafety);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1208,7 +1243,8 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
||||||
Class? superclassNode = c.superclass;
|
Class? superclassNode = c.superclass;
|
||||||
_TFClassImpl? superclass =
|
_TFClassImpl? superclass =
|
||||||
superclassNode != null ? getTFClass(superclassNode) : null;
|
superclassNode != null ? getTFClass(superclassNode) : null;
|
||||||
return new _TFClassImpl(++_classIdCounter, c, superclass, supertypes);
|
return new _TFClassImpl(++_classIdCounter, c, superclass, supertypes,
|
||||||
|
hasMutableDispatchTargets: c == recordClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConcreteType addAllocatedClass(Class cl) {
|
ConcreteType addAllocatedClass(Class cl) {
|
||||||
|
@ -1286,7 +1322,7 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
||||||
bool? value = classImpl.hasNonTrivialNoSuchMethod;
|
bool? value = classImpl.hasNonTrivialNoSuchMethod;
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
classImpl.hasNonTrivialNoSuchMethod = value =
|
classImpl.hasNonTrivialNoSuchMethod = value =
|
||||||
(hierarchy.getDispatchTarget(c.classNode, noSuchMethodName) !=
|
(classImpl._dispatchTargetsNonSetters[noSuchMethodName] !=
|
||||||
objectNoSuchMethod);
|
objectNoSuchMethod);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
|
@ -1297,11 +1333,7 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
||||||
}
|
}
|
||||||
|
|
||||||
_DynamicTargetSet _createDynamicTargetSet(DynamicSelector selector) {
|
_DynamicTargetSet _createDynamicTargetSet(DynamicSelector selector) {
|
||||||
// TODO(alexmarkov): consider caching the set of Object selectors.
|
final isObjectMethod = _objectTFClass.getDispatchTarget(selector) != null;
|
||||||
final isObjectMethod = (hierarchy.getDispatchTarget(
|
|
||||||
_typeFlowAnalysis.environment.coreTypes.objectClass, selector.name,
|
|
||||||
setter: selector.isSetter) !=
|
|
||||||
null);
|
|
||||||
|
|
||||||
final targetSet = new _DynamicTargetSet(selector, isObjectMethod);
|
final targetSet = new _DynamicTargetSet(selector, isObjectMethod);
|
||||||
for (Class c in allocatedClasses) {
|
for (Class c in allocatedClasses) {
|
||||||
|
@ -1313,8 +1345,7 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
||||||
void _addDynamicTarget(Class c, _DynamicTargetSet targetSet) {
|
void _addDynamicTarget(Class c, _DynamicTargetSet targetSet) {
|
||||||
assert(!_sealed);
|
assert(!_sealed);
|
||||||
final selector = targetSet.selector;
|
final selector = targetSet.selector;
|
||||||
final member = hierarchy.getDispatchTarget(c, selector.name,
|
final member = getTFClass(c).getDispatchTarget(selector);
|
||||||
setter: selector.isSetter);
|
|
||||||
if (member != null) {
|
if (member != null) {
|
||||||
if (targetSet.targets.add(member)) {
|
if (targetSet.targets.add(member)) {
|
||||||
targetSet.invalidateDependentInvocations(_typeFlowAnalysis.workList);
|
targetSet.invalidateDependentInvocations(_typeFlowAnalysis.workList);
|
||||||
|
@ -1322,6 +1353,35 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Field getRecordField(String name) {
|
||||||
|
return _recordFields[name] ??= _addRecordField(Name(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
Field _addRecordField(Name name) {
|
||||||
|
assert(!_sealed);
|
||||||
|
final Field field = Field.immutable(name, fileUri: artificialNodeUri);
|
||||||
|
field.parent = recordClass;
|
||||||
|
// Add field to the record class for future queries.
|
||||||
|
_recordTFClass._addField(field);
|
||||||
|
_recordTFClass.dependencyTracker
|
||||||
|
.invalidateDependentInvocations(_typeFlowAnalysis.workList);
|
||||||
|
// Update dynamic target sets collected so far.
|
||||||
|
_DynamicTargetSet? targetSet =
|
||||||
|
_dynamicTargets[DynamicSelector(CallKind.PropertyGet, name)];
|
||||||
|
if (targetSet != null) {
|
||||||
|
if (targetSet.targets.add(field)) {
|
||||||
|
targetSet.invalidateDependentInvocations(_typeFlowAnalysis.workList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetSet = _dynamicTargets[DynamicSelector(CallKind.Method, name)];
|
||||||
|
if (targetSet != null) {
|
||||||
|
if (targetSet.targets.add(field)) {
|
||||||
|
targetSet.invalidateDependentInvocations(_typeFlowAnalysis.workList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<DartType> flattenedTypeArgumentsFor(Class klass) =>
|
List<DartType> flattenedTypeArgumentsFor(Class klass) =>
|
||||||
genericInterfacesInfo.flattenedTypeArgumentsFor(klass);
|
genericInterfacesInfo.flattenedTypeArgumentsFor(klass);
|
||||||
|
@ -1553,8 +1613,8 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
|
||||||
: annotationMatcher =
|
: annotationMatcher =
|
||||||
matcher ?? new ConstantPragmaAnnotationParser(coreTypes, target) {
|
matcher ?? new ConstantPragmaAnnotationParser(coreTypes, target) {
|
||||||
nativeCodeOracle = new NativeCodeOracle(libraryIndex, annotationMatcher);
|
nativeCodeOracle = new NativeCodeOracle(libraryIndex, annotationMatcher);
|
||||||
hierarchyCache = new _ClassHierarchyCache(this, hierarchy,
|
hierarchyCache = new _ClassHierarchyCache(this, _genericInterfacesInfo,
|
||||||
_genericInterfacesInfo, environment, target.flags.enableNullSafety);
|
environment, target.flags.enableNullSafety);
|
||||||
summaryCollector = new SummaryCollector(
|
summaryCollector = new SummaryCollector(
|
||||||
target,
|
target,
|
||||||
environment,
|
environment,
|
||||||
|
@ -1769,6 +1829,12 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
|
||||||
return hierarchyCache.addAllocatedClass(c);
|
return hierarchyCache.addAllocatedClass(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Field getRecordPositionalField(int pos) => getRecordNamedField("\$$pos");
|
||||||
|
|
||||||
|
@override
|
||||||
|
Field getRecordNamedField(String name) => hierarchyCache.getRecordField(name);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void recordMemberCalledViaInterfaceSelector(Member target) {
|
void recordMemberCalledViaInterfaceSelector(Member target) {
|
||||||
_calledViaInterfaceSelector.add(target);
|
_calledViaInterfaceSelector.add(target);
|
||||||
|
|
|
@ -23,6 +23,12 @@ abstract class EntryPointsListener {
|
||||||
/// Add instantiation of the given class.
|
/// Add instantiation of the given class.
|
||||||
ConcreteType addAllocatedClass(Class c);
|
ConcreteType addAllocatedClass(Class c);
|
||||||
|
|
||||||
|
/// Returns synthetic Field representing positional field of a record.
|
||||||
|
Field getRecordPositionalField(int pos);
|
||||||
|
|
||||||
|
/// Returns synthetic Field representing named field of a record.
|
||||||
|
Field getRecordNamedField(String name);
|
||||||
|
|
||||||
/// Record the fact that given member is called via interface selector
|
/// Record the fact that given member is called via interface selector
|
||||||
/// (not dynamically, and not from `this`).
|
/// (not dynamically, and not from `this`).
|
||||||
void recordMemberCalledViaInterfaceSelector(Member target);
|
void recordMemberCalledViaInterfaceSelector(Member target);
|
||||||
|
|
|
@ -526,6 +526,12 @@ class _EntryPointsListenerImpl implements EntryPointsListener {
|
||||||
@override
|
@override
|
||||||
ConcreteType addAllocatedClass(Class c) => rta.addAllocatedClass(c);
|
ConcreteType addAllocatedClass(Class c) => rta.addAllocatedClass(c);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Field getRecordPositionalField(int pos) => throw 'Unsupported operation';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Field getRecordNamedField(String name) => throw 'Unsupported operation';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void recordMemberCalledViaInterfaceSelector(Member target) =>
|
void recordMemberCalledViaInterfaceSelector(Member target) =>
|
||||||
throw 'Unsupported operation';
|
throw 'Unsupported operation';
|
||||||
|
|
|
@ -298,7 +298,7 @@ class Call extends Statement {
|
||||||
|
|
||||||
Member? _monomorphicTarget;
|
Member? _monomorphicTarget;
|
||||||
|
|
||||||
Member? get monomorphicTarget => _monomorphicTarget;
|
Member? get monomorphicTarget => filterArtificialNode(_monomorphicTarget);
|
||||||
|
|
||||||
bool get isMonomorphic => (_flags & kMonomorphic) != 0;
|
bool get isMonomorphic => (_flags & kMonomorphic) != 0;
|
||||||
|
|
||||||
|
|
|
@ -1622,30 +1622,42 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TypeExpr visitRecordLiteral(RecordLiteral node) {
|
TypeExpr visitRecordLiteral(RecordLiteral node) {
|
||||||
for (var expr in node.positional) {
|
final Type receiver = _typesBuilder.recordType;
|
||||||
_visit(expr);
|
for (int i = 0; i < node.positional.length; ++i) {
|
||||||
|
final Field f = _entryPointsListener.getRecordPositionalField(i);
|
||||||
|
final TypeExpr value = _visit(node.positional[i]);
|
||||||
|
final args = Args<TypeExpr>([receiver, value]);
|
||||||
|
_makeCall(node,
|
||||||
|
DirectSelector(f, callKind: CallKind.SetFieldInConstructor), args);
|
||||||
}
|
}
|
||||||
for (var expr in node.named) {
|
for (var expr in node.named) {
|
||||||
_visit(expr.value);
|
final Field f = _entryPointsListener.getRecordNamedField(expr.name);
|
||||||
|
final TypeExpr value = _visit(expr.value);
|
||||||
|
final args = Args<TypeExpr>([receiver, value]);
|
||||||
|
_makeCall(node,
|
||||||
|
DirectSelector(f, callKind: CallKind.SetFieldInConstructor), args);
|
||||||
}
|
}
|
||||||
Class? concreteClass =
|
callSites.remove(node);
|
||||||
target.concreteRecordLiteralClass(_environment.coreTypes);
|
return receiver;
|
||||||
if (concreteClass != null) {
|
|
||||||
return _entryPointsListener.addAllocatedClass(concreteClass);
|
|
||||||
}
|
|
||||||
return _staticType(node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TypeExpr visitRecordIndexGet(RecordIndexGet node) {
|
TypeExpr visitRecordIndexGet(RecordIndexGet node) {
|
||||||
_visit(node.receiver);
|
final receiver = _visit(node.receiver);
|
||||||
return _staticType(node);
|
final Field field =
|
||||||
|
_entryPointsListener.getRecordPositionalField(node.index);
|
||||||
|
final args = Args<TypeExpr>([receiver]);
|
||||||
|
return _makeCall(
|
||||||
|
node, DirectSelector(field, callKind: CallKind.PropertyGet), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TypeExpr visitRecordNameGet(RecordNameGet node) {
|
TypeExpr visitRecordNameGet(RecordNameGet node) {
|
||||||
_visit(node.receiver);
|
final receiver = _visit(node.receiver);
|
||||||
return _staticType(node);
|
final Field field = _entryPointsListener.getRecordNamedField(node.name);
|
||||||
|
final args = Args<TypeExpr>([receiver]);
|
||||||
|
return _makeCall(
|
||||||
|
node, DirectSelector(field, callKind: CallKind.PropertyGet), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -2594,20 +2606,19 @@ class ConstantAllocationCollector extends ConstantVisitor<Type> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Type visitRecordConstant(RecordConstant constant) {
|
Type visitRecordConstant(RecordConstant constant) {
|
||||||
for (var value in constant.positional) {
|
final epl = summaryCollector._entryPointsListener;
|
||||||
typeFor(value);
|
final Type receiver = summaryCollector._typesBuilder.recordType;
|
||||||
|
for (int i = 0; i < constant.positional.length; ++i) {
|
||||||
|
final Field f = epl.getRecordPositionalField(i);
|
||||||
|
final Type value = typeFor(constant.positional[i]);
|
||||||
|
epl.addFieldUsedInConstant(f, receiver, value);
|
||||||
}
|
}
|
||||||
for (var value in constant.named.values) {
|
constant.named.forEach((String fieldName, Constant fieldValue) {
|
||||||
typeFor(value);
|
final Field f = epl.getRecordNamedField(fieldName);
|
||||||
}
|
final Type value = typeFor(fieldValue);
|
||||||
Class? concreteClass = summaryCollector.target
|
epl.addFieldUsedInConstant(f, receiver, value);
|
||||||
.concreteConstRecordLiteralClass(
|
});
|
||||||
summaryCollector._environment.coreTypes);
|
return receiver;
|
||||||
if (concreteClass != null) {
|
|
||||||
return summaryCollector._entryPointsListener
|
|
||||||
.addAllocatedClass(concreteClass);
|
|
||||||
}
|
|
||||||
return _getStaticType(constant);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -139,8 +139,8 @@ Component transformComponent(
|
||||||
final unboxingInfo = new UnboxingInfoManager(typeFlowAnalysis)
|
final unboxingInfo = new UnboxingInfoManager(typeFlowAnalysis)
|
||||||
..analyzeComponent(component, typeFlowAnalysis, tableSelectorAssigner);
|
..analyzeComponent(component, typeFlowAnalysis, tableSelectorAssigner);
|
||||||
|
|
||||||
new AnnotateKernel(component, typeFlowAnalysis, treeShaker.fieldMorpher,
|
new AnnotateKernel(component, typeFlowAnalysis, hierarchy,
|
||||||
tableSelectorAssigner, unboxingInfo)
|
treeShaker.fieldMorpher, tableSelectorAssigner, unboxingInfo)
|
||||||
.visitComponent(component);
|
.visitComponent(component);
|
||||||
|
|
||||||
transformsStopWatch.stop();
|
transformsStopWatch.stop();
|
||||||
|
@ -300,6 +300,7 @@ class TFADevirtualization extends Devirtualization {
|
||||||
/// Annotates kernel AST with metadata using results of type flow analysis.
|
/// Annotates kernel AST with metadata using results of type flow analysis.
|
||||||
class AnnotateKernel extends RecursiveVisitor {
|
class AnnotateKernel extends RecursiveVisitor {
|
||||||
final TypeFlowAnalysis _typeFlowAnalysis;
|
final TypeFlowAnalysis _typeFlowAnalysis;
|
||||||
|
final ClassHierarchy hierarchy;
|
||||||
final FieldMorpher fieldMorpher;
|
final FieldMorpher fieldMorpher;
|
||||||
final DirectCallMetadataRepository _directCallMetadataRepository;
|
final DirectCallMetadataRepository _directCallMetadataRepository;
|
||||||
final InferredTypeMetadataRepository _inferredTypeMetadata;
|
final InferredTypeMetadataRepository _inferredTypeMetadata;
|
||||||
|
@ -312,8 +313,8 @@ class AnnotateKernel extends RecursiveVisitor {
|
||||||
final Class _intClass;
|
final Class _intClass;
|
||||||
late final Constant _nullConstant = NullConstant();
|
late final Constant _nullConstant = NullConstant();
|
||||||
|
|
||||||
AnnotateKernel(Component component, this._typeFlowAnalysis, this.fieldMorpher,
|
AnnotateKernel(Component component, this._typeFlowAnalysis, this.hierarchy,
|
||||||
this._tableSelectorAssigner, this._unboxingInfo)
|
this.fieldMorpher, this._tableSelectorAssigner, this._unboxingInfo)
|
||||||
: _directCallMetadataRepository =
|
: _directCallMetadataRepository =
|
||||||
component.metadata[DirectCallMetadataRepository.repositoryTag]
|
component.metadata[DirectCallMetadataRepository.repositoryTag]
|
||||||
as DirectCallMetadataRepository,
|
as DirectCallMetadataRepository,
|
||||||
|
@ -429,9 +430,7 @@ class AnnotateKernel extends RecursiveVisitor {
|
||||||
// here), then the receiver cannot be _Smi. This heuristic covers most
|
// here), then the receiver cannot be _Smi. This heuristic covers most
|
||||||
// cases, so we skip these to avoid showering the AST with annotations.
|
// cases, so we skip these to avoid showering the AST with annotations.
|
||||||
if (interfaceTarget == null ||
|
if (interfaceTarget == null ||
|
||||||
_typeFlowAnalysis.hierarchyCache.hierarchy.isSubtypeOf(
|
hierarchy.isSubtypeOf(_intClass, interfaceTarget.enclosingClass!)) {
|
||||||
_typeFlowAnalysis.hierarchyCache.coreTypes.intClass,
|
|
||||||
interfaceTarget.enclosingClass!)) {
|
|
||||||
markReceiverNotInt = true;
|
markReceiverNotInt = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,8 @@ abstract class TypesBuilder {
|
||||||
/// Return [TFClass] corresponding to the given [classNode].
|
/// Return [TFClass] corresponding to the given [classNode].
|
||||||
TFClass getTFClass(Class classNode);
|
TFClass getTFClass(Class classNode);
|
||||||
|
|
||||||
|
late final Type recordType = ConeType(getTFClass(coreTypes.recordClass));
|
||||||
|
|
||||||
/// Create a Type which corresponds to a set of instances constrained by
|
/// Create a Type which corresponds to a set of instances constrained by
|
||||||
/// Dart type annotation [dartType].
|
/// Dart type annotation [dartType].
|
||||||
/// [canBeNull] can be set to false to further constrain the resulting
|
/// [canBeNull] can be set to false to further constrain the resulting
|
||||||
|
@ -88,7 +90,7 @@ abstract class TypesBuilder {
|
||||||
result = const AnyType();
|
result = const AnyType();
|
||||||
} else if (type is RecordType) {
|
} else if (type is RecordType) {
|
||||||
// TODO(dartbug.com/49719): support inference of record types
|
// TODO(dartbug.com/49719): support inference of record types
|
||||||
result = const AnyType();
|
result = recordType;
|
||||||
} else if (type is FutureOrType) {
|
} else if (type is FutureOrType) {
|
||||||
// TODO(alexmarkov): support FutureOr types
|
// TODO(alexmarkov): support FutureOr types
|
||||||
result = const AnyType();
|
result = const AnyType();
|
||||||
|
@ -575,7 +577,7 @@ class SetType extends Type {
|
||||||
return type.intersection(other, typeHierarchy);
|
return type.intersection(other, typeHierarchy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return EmptyType();
|
return const EmptyType();
|
||||||
} else if (other is ConeType) {
|
} else if (other is ConeType) {
|
||||||
return typeHierarchy
|
return typeHierarchy
|
||||||
.specializeTypeCone(other.cls, allowWideCone: true)
|
.specializeTypeCone(other.cls, allowWideCone: true)
|
||||||
|
@ -818,7 +820,8 @@ class ConcreteType extends Type implements Comparable<ConcreteType> {
|
||||||
bool get isRaw => typeArgs == null && constant == null;
|
bool get isRaw => typeArgs == null && constant == null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Class getConcreteClass(TypeHierarchy typeHierarchy) => cls.classNode;
|
Class? getConcreteClass(TypeHierarchy typeHierarchy) =>
|
||||||
|
filterArtificialNode(cls.classNode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isSubtypeOf(TypeHierarchy typeHierarchy, Class other) =>
|
bool isSubtypeOf(TypeHierarchy typeHierarchy, Class other) =>
|
||||||
|
@ -989,7 +992,7 @@ class ConcreteType extends Type implements Comparable<ConcreteType> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
if (!identical(this.cls, other.cls)) {
|
if (!identical(this.cls, other.cls)) {
|
||||||
return EmptyType();
|
return const EmptyType();
|
||||||
}
|
}
|
||||||
if (typeArgs == null && constant == null) {
|
if (typeArgs == null && constant == null) {
|
||||||
return other;
|
return other;
|
||||||
|
@ -1177,8 +1180,8 @@ class RuntimeType extends Type {
|
||||||
throw "ERROR: RuntimeType does not support specialize.";
|
throw "ERROR: RuntimeType does not support specialize.";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Class getConcreteClass(TypeHierarchy typeHierarchy) =>
|
Class? getConcreteClass(TypeHierarchy typeHierarchy) =>
|
||||||
throw "ERROR: ConcreteClass does not support getConcreteClass.";
|
throw "ERROR: RuntimeType does not support getConcreteClass.";
|
||||||
|
|
||||||
bool isSubtypeOfRuntimeType(TypeHierarchy typeHierarchy,
|
bool isSubtypeOfRuntimeType(TypeHierarchy typeHierarchy,
|
||||||
RuntimeType runtimeType, SubtypeTestKind kind) {
|
RuntimeType runtimeType, SubtypeTestKind kind) {
|
||||||
|
|
|
@ -425,3 +425,14 @@ bool mayHaveOrSeeSideEffects(Expression node) {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dedicated fileUri for artifical nodes created during type flow analysis.
|
||||||
|
final Uri artificialNodeUri = Uri(scheme: 'tfa-artificial-node');
|
||||||
|
|
||||||
|
// Returns true if [node] was artificially created during type flow analysis.
|
||||||
|
bool isArtificialNode(TreeNode node) =>
|
||||||
|
node is FileUriNode && identical(node.fileUri, artificialNodeUri);
|
||||||
|
|
||||||
|
// Returns [node] or null, if node is artifical.
|
||||||
|
T? filterArtificialNode<T extends TreeNode>(T? node) =>
|
||||||
|
(node == null || isArtificialNode(node)) ? null : node;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:front_end/src/api_unstable/vm.dart';
|
||||||
import 'package:kernel/ast.dart';
|
import 'package:kernel/ast.dart';
|
||||||
import 'package:kernel/class_hierarchy.dart';
|
import 'package:kernel/class_hierarchy.dart';
|
||||||
import 'package:kernel/core_types.dart';
|
import 'package:kernel/core_types.dart';
|
||||||
|
@ -21,7 +22,7 @@ import 'package:vm/transformations/type_flow/types.dart';
|
||||||
|
|
||||||
import '../../common_test_utils.dart';
|
import '../../common_test_utils.dart';
|
||||||
|
|
||||||
final String pkgVmDir = Platform.script.resolve('../../..').toFilePath();
|
final Uri pkgVmDir = Platform.script.resolve('../../..');
|
||||||
|
|
||||||
class FakeTypesBuilder extends TypesBuilder {
|
class FakeTypesBuilder extends TypesBuilder {
|
||||||
final Map<Class, TFClass> _classes = <Class, TFClass>{};
|
final Map<Class, TFClass> _classes = <Class, TFClass>{};
|
||||||
|
@ -51,6 +52,13 @@ class FakeEntryPointsListener implements EntryPointsListener {
|
||||||
return new ConcreteType(_typesBuilder.getTFClass(c), null);
|
return new ConcreteType(_typesBuilder.getTFClass(c), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Field getRecordPositionalField(int pos) => getRecordNamedField("\$$pos");
|
||||||
|
|
||||||
|
@override
|
||||||
|
Field getRecordNamedField(String name) =>
|
||||||
|
Field.immutable(Name(name), fileUri: dummyUri);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void recordMemberCalledViaInterfaceSelector(Member target) {}
|
void recordMemberCalledViaInterfaceSelector(Member target) {}
|
||||||
|
|
||||||
|
@ -96,9 +104,17 @@ class PrintSummaries extends RecursiveVisitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runTestCase(Uri source) async {
|
class TestOptions {
|
||||||
|
static const Option<List<String>?> enableExperiment =
|
||||||
|
Option('--enable-experiment', StringListValue());
|
||||||
|
|
||||||
|
static const List<Option> options = [enableExperiment];
|
||||||
|
}
|
||||||
|
|
||||||
|
runTestCase(Uri source, List<String>? experimentalFlags) async {
|
||||||
final Target target = new TestingVmTarget(new TargetFlags());
|
final Target target = new TestingVmTarget(new TargetFlags());
|
||||||
final Component component = await compileTestCaseToKernelProgram(source);
|
final Component component = await compileTestCaseToKernelProgram(source,
|
||||||
|
experimentalFlags: experimentalFlags);
|
||||||
final Library library = component.mainMethod!.enclosingLibrary;
|
final Library library = component.mainMethod!.enclosingLibrary;
|
||||||
final CoreTypes coreTypes = new CoreTypes(component);
|
final CoreTypes coreTypes = new CoreTypes(component);
|
||||||
|
|
||||||
|
@ -115,13 +131,22 @@ runTestCase(Uri source) async {
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
group('collect-summary', () {
|
group('collect-summary', () {
|
||||||
final testCasesDir = new Directory(
|
final testCasesDir = Directory.fromUri(pkgVmDir
|
||||||
pkgVmDir + '/testcases/transformations/type_flow/summary_collector');
|
.resolve('testcases/transformations/type_flow/summary_collector'));
|
||||||
|
|
||||||
for (var entry
|
for (var entry
|
||||||
in testCasesDir.listSync(recursive: true, followLinks: false)) {
|
in testCasesDir.listSync(recursive: true, followLinks: false)) {
|
||||||
if (entry.path.endsWith(".dart")) {
|
final path = entry.path;
|
||||||
test(entry.path, () => runTestCase(entry.uri));
|
if (path.endsWith(".dart")) {
|
||||||
|
List<String>? experimentalFlags;
|
||||||
|
final File optionsFile = new File('${path}.options');
|
||||||
|
if (optionsFile.existsSync()) {
|
||||||
|
ParsedOptions parsedOptions = ParsedOptions.parse(
|
||||||
|
ParsedOptions.readOptionsFile(optionsFile.readAsStringSync()),
|
||||||
|
TestOptions.options);
|
||||||
|
experimentalFlags = TestOptions.enableExperiment.read(parsedOptions);
|
||||||
|
}
|
||||||
|
test(path, () => runTestCase(entry.uri, experimentalFlags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,12 +19,14 @@ import '../../common_test_utils.dart';
|
||||||
|
|
||||||
final Uri pkgVmDir = Platform.script.resolve('../../..');
|
final Uri pkgVmDir = Platform.script.resolve('../../..');
|
||||||
|
|
||||||
void runTestCase(
|
void runTestCase(Uri source, bool enableNullSafety,
|
||||||
Uri source, bool enableNullSafety, List<Uri>? linkedDependencies) async {
|
List<Uri>? linkedDependencies, List<String>? experimentalFlags) async {
|
||||||
final target =
|
final target =
|
||||||
new TestingVmTarget(new TargetFlags(enableNullSafety: enableNullSafety));
|
new TestingVmTarget(new TargetFlags(enableNullSafety: enableNullSafety));
|
||||||
Component component = await compileTestCaseToKernelProgram(source,
|
Component component = await compileTestCaseToKernelProgram(source,
|
||||||
target: target, linkedDependencies: linkedDependencies);
|
target: target,
|
||||||
|
linkedDependencies: linkedDependencies,
|
||||||
|
experimentalFlags: experimentalFlags);
|
||||||
|
|
||||||
final coreTypes = new CoreTypes(component);
|
final coreTypes = new CoreTypes(component);
|
||||||
|
|
||||||
|
@ -84,7 +86,10 @@ class TestOptions {
|
||||||
static const Option<bool> nnbdStrong =
|
static const Option<bool> nnbdStrong =
|
||||||
Option('--nnbd-strong', BoolValue(false));
|
Option('--nnbd-strong', BoolValue(false));
|
||||||
|
|
||||||
static const List<Option> options = [linked, nnbdStrong];
|
static const Option<List<String>?> enableExperiment =
|
||||||
|
Option('--enable-experiment', StringListValue());
|
||||||
|
|
||||||
|
static const List<Option> options = [linked, nnbdStrong, enableExperiment];
|
||||||
}
|
}
|
||||||
|
|
||||||
void main(List<String> args) {
|
void main(List<String> args) {
|
||||||
|
@ -107,6 +112,7 @@ void main(List<String> args) {
|
||||||
}
|
}
|
||||||
List<Uri>? linkDependencies;
|
List<Uri>? linkDependencies;
|
||||||
bool enableNullSafety = path.endsWith('_nnbd_strong.dart');
|
bool enableNullSafety = path.endsWith('_nnbd_strong.dart');
|
||||||
|
List<String>? experimentalFlags;
|
||||||
|
|
||||||
File optionsFile = new File('${path}.options');
|
File optionsFile = new File('${path}.options');
|
||||||
if (optionsFile.existsSync()) {
|
if (optionsFile.existsSync()) {
|
||||||
|
@ -121,10 +127,13 @@ void main(List<String> args) {
|
||||||
if (TestOptions.nnbdStrong.read(parsedOptions)) {
|
if (TestOptions.nnbdStrong.read(parsedOptions)) {
|
||||||
enableNullSafety = true;
|
enableNullSafety = true;
|
||||||
}
|
}
|
||||||
|
experimentalFlags = TestOptions.enableExperiment.read(parsedOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
test(path,
|
test(
|
||||||
() => runTestCase(entry.uri, enableNullSafety, linkDependencies));
|
path,
|
||||||
|
() => runTestCase(entry.uri, enableNullSafety, linkDependencies,
|
||||||
|
experimentalFlags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, timeout: Timeout.none);
|
}, timeout: Timeout.none);
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright (c) 2022, 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.
|
||||||
|
|
||||||
|
const recordConstant = (42, 'Hey', foo: 'foo');
|
||||||
|
|
||||||
|
recordLiteral(x, y, z) => (x, y, bar: z);
|
||||||
|
|
||||||
|
recordFieldAccess1((int, String) rec) => rec.$1;
|
||||||
|
|
||||||
|
recordFieldAccess2(({int a, String b}) rec) => rec.b;
|
||||||
|
|
||||||
|
main() {}
|
|
@ -0,0 +1,22 @@
|
||||||
|
------------ recordLiteral ------------
|
||||||
|
%x = _Parameter #0 [_T ANY?]
|
||||||
|
%y = _Parameter #1 [_T ANY?]
|
||||||
|
%z = _Parameter #2 [_T ANY?]
|
||||||
|
t3 = _Call direct set [$0] (_T (dart.core::Record)+, %x)
|
||||||
|
t4 = _Call direct set [$1] (_T (dart.core::Record)+, %y)
|
||||||
|
t5 = _Call direct set [bar] (_T (dart.core::Record)+, %z)
|
||||||
|
RESULT: _T (dart.core::Record)+
|
||||||
|
------------ recordFieldAccess1 ------------
|
||||||
|
%rec = _Parameter #0 [_T (dart.core::Record)+?]
|
||||||
|
t1* = _Call direct get [$1] (%rec)
|
||||||
|
RESULT: t1
|
||||||
|
------------ recordFieldAccess2 ------------
|
||||||
|
%rec = _Parameter #0 [_T (dart.core::Record)+?]
|
||||||
|
t1* = _Call direct get [b] (%rec)
|
||||||
|
RESULT: t1
|
||||||
|
------------ main ------------
|
||||||
|
|
||||||
|
RESULT: _T {}?
|
||||||
|
------------ recordConstant ------------
|
||||||
|
|
||||||
|
RESULT: _T (dart.core::Record)+
|
|
@ -0,0 +1 @@
|
||||||
|
--enable-experiment=records
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) 2022, 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.
|
||||||
|
|
||||||
|
const recordConstant = (42, 'Hey', foo: 'foo');
|
||||||
|
|
||||||
|
recordLiteral(x, y, z) => (x, y, bar: z);
|
||||||
|
|
||||||
|
recordFieldAccess1((int, String) rec) => rec.$0;
|
||||||
|
|
||||||
|
recordFieldAccess2(({int a, String b}) rec) => rec.a;
|
||||||
|
|
||||||
|
dynamic list = ['abc', (42, foo42: 'foo42')];
|
||||||
|
|
||||||
|
recordDynamicFieldAccess(dynamic x) => x.foo42;
|
||||||
|
|
||||||
|
main() {
|
||||||
|
print(recordConstant);
|
||||||
|
print(recordLiteral(1, 2, 3));
|
||||||
|
print(recordFieldAccess1((10, 'hi')));
|
||||||
|
print(recordFieldAccess2((a: 20, b: 'bye')));
|
||||||
|
print(recordDynamicFieldAccess(list[1]));
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
library #lib /*isNonNullableByDefault*/;
|
||||||
|
import self as self;
|
||||||
|
import "dart:core" as core;
|
||||||
|
|
||||||
|
[@vm.inferred-type.metadata=dart.core::_GrowableList?<dart.core::Object>]static field dynamic list = [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::Object>] core::_GrowableList::_literal2<core::Object>("abc", (42, {foo42: "foo42"}));
|
||||||
|
static method recordLiteral() → dynamic
|
||||||
|
return (#C1, #C2, {bar: #C3});
|
||||||
|
[@vm.unboxing-info.metadata=(b)->i]static method recordFieldAccess1([@vm.inferred-type.metadata=dart.core::_Record](core::int, core::String) rec) → dynamic
|
||||||
|
return rec.$0{core::int};
|
||||||
|
[@vm.unboxing-info.metadata=(b)->i]static method recordFieldAccess2([@vm.inferred-type.metadata=dart.core::_Record]({required a: core::int, required b: core::String}) rec) → dynamic
|
||||||
|
return rec.a{core::int};
|
||||||
|
static method recordDynamicFieldAccess(dynamic x) → dynamic
|
||||||
|
return [@vm.inferred-type.metadata=dart.core::_OneByteString (value: "foo42")] x{dynamic}.foo42;
|
||||||
|
static method main() → dynamic {
|
||||||
|
core::print(#C7);
|
||||||
|
core::print([@vm.inferred-type.metadata=dart.core::_Record] self::recordLiteral());
|
||||||
|
core::print([@vm.inferred-type.metadata=dart.core::_Smi] self::recordFieldAccess1((10, "hi")));
|
||||||
|
core::print([@vm.inferred-type.metadata=dart.core::_Smi (value: 20)] self::recordFieldAccess2(({a: 20, b: "bye"})));
|
||||||
|
core::print([@vm.inferred-type.metadata=dart.core::_OneByteString (value: "foo42")] self::recordDynamicFieldAccess([@vm.direct-call.metadata=dart.core::_GrowableList.[]??] [@vm.inferred-type.metadata=!? (receiver not int)] [@vm.inferred-type.metadata=dart.core::_GrowableList?<dart.core::Object>] self::list{dynamic}.[](1)));
|
||||||
|
}
|
||||||
|
constants {
|
||||||
|
#C1 = 1
|
||||||
|
#C2 = 2
|
||||||
|
#C3 = 3
|
||||||
|
#C4 = 42
|
||||||
|
#C5 = "Hey"
|
||||||
|
#C6 = "foo"
|
||||||
|
#C7 = (#C4, #C5, {foo:#C6})
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
--enable-experiment=records
|
|
@ -45,12 +45,17 @@ void main(List<String> args) {
|
||||||
platform('pkg/_fe_analyzer_shared/test/inheritance'),
|
platform('pkg/_fe_analyzer_shared/test/inheritance'),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
var pkgVmPackageDirs = [
|
||||||
|
platform('pkg/vm/testcases'),
|
||||||
|
];
|
||||||
|
|
||||||
// Validate that all the given directories exist.
|
// Validate that all the given directories exist.
|
||||||
var hasMissingDirectories = false;
|
var hasMissingDirectories = false;
|
||||||
for (var path in [
|
for (var path in [
|
||||||
...packageDirs,
|
...packageDirs,
|
||||||
...cfePackageDirs,
|
...cfePackageDirs,
|
||||||
...feAnalyzerSharedPackageDirs
|
...feAnalyzerSharedPackageDirs,
|
||||||
|
...pkgVmPackageDirs
|
||||||
]) {
|
]) {
|
||||||
if (!Directory(join(repoRoot, path)).existsSync()) {
|
if (!Directory(join(repoRoot, path)).existsSync()) {
|
||||||
stderr.writeln("Unable to locate directory: '$path'.");
|
stderr.writeln("Unable to locate directory: '$path'.");
|
||||||
|
@ -65,7 +70,8 @@ void main(List<String> args) {
|
||||||
var packages = <Package>[
|
var packages = <Package>[
|
||||||
...makePackageConfigs(packageDirs),
|
...makePackageConfigs(packageDirs),
|
||||||
...makeCfePackageConfigs(cfePackageDirs),
|
...makeCfePackageConfigs(cfePackageDirs),
|
||||||
...makeFeAnalyzerSharedPackageConfigs(feAnalyzerSharedPackageDirs)
|
...makeFeAnalyzerSharedPackageConfigs(feAnalyzerSharedPackageDirs),
|
||||||
|
...makePkgVmPackageConfigs(pkgVmPackageDirs),
|
||||||
];
|
];
|
||||||
packages.sort((a, b) => a.name.compareTo(b.name));
|
packages.sort((a, b) => a.name.compareTo(b.name));
|
||||||
|
|
||||||
|
@ -134,32 +140,34 @@ Iterable<Package> makePackageConfigs(List<String> packageDirs) sync* {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates package configurations for the special pseudo-packages used by the
|
/// Generates package configurations for the special pseudo-packages.
|
||||||
/// CFE unit tests (`pkg/front_end/test/unit_test_suites.dart`).
|
Iterable<Package> makeSpecialPackageConfigs(
|
||||||
Iterable<Package> makeCfePackageConfigs(List<String> packageDirs) sync* {
|
String packageNamePrefix, List<String> packageDirs) sync* {
|
||||||
// TODO: Remove the use of '.nonexisting/'.
|
// TODO: Remove the use of '.nonexisting/'.
|
||||||
for (var packageDir in packageDirs) {
|
for (var packageDir in packageDirs) {
|
||||||
yield Package(
|
yield Package(
|
||||||
name: 'front_end_${basename(packageDir)}',
|
name: '${packageNamePrefix}_${basename(packageDir)}',
|
||||||
rootUri: packageDir,
|
rootUri: packageDir,
|
||||||
packageUri: '.nonexisting/',
|
packageUri: '.nonexisting/',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates package configurations for the special pseudo-packages used by the
|
||||||
|
/// CFE unit tests (`pkg/front_end/test/unit_test_suites.dart`).
|
||||||
|
Iterable<Package> makeCfePackageConfigs(List<String> packageDirs) =>
|
||||||
|
makeSpecialPackageConfigs('front_end', packageDirs);
|
||||||
|
|
||||||
/// Generates package configurations for the special pseudo-packages used by the
|
/// Generates package configurations for the special pseudo-packages used by the
|
||||||
/// _fe_analyzer_shared id tests.
|
/// _fe_analyzer_shared id tests.
|
||||||
Iterable<Package> makeFeAnalyzerSharedPackageConfigs(
|
Iterable<Package> makeFeAnalyzerSharedPackageConfigs(
|
||||||
List<String> packageDirs) sync* {
|
List<String> packageDirs) =>
|
||||||
// TODO: Remove the use of '.nonexisting/'.
|
makeSpecialPackageConfigs('_fe_analyzer_shared', packageDirs);
|
||||||
for (var packageDir in packageDirs) {
|
|
||||||
yield Package(
|
/// Generates package configurations for the special pseudo-packages used by the
|
||||||
name: '_fe_analyzer_shared_${basename(packageDir)}',
|
/// pkg/vm unit tests (`pkg/vm/test`).
|
||||||
rootUri: packageDir,
|
Iterable<Package> makePkgVmPackageConfigs(List<String> packageDirs) =>
|
||||||
packageUri: '.nonexisting/',
|
makeSpecialPackageConfigs('pkg_vm', packageDirs);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds the paths of the subdirectories of [dirPath] that contain pubspecs.
|
/// Finds the paths of the subdirectories of [dirPath] that contain pubspecs.
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in a new issue