mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 08:24:39 +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? concreteSetLiteralClass(CoreTypes coreTypes) => null;
|
||||
Class? concreteConstSetLiteralClass(CoreTypes coreTypes) => null;
|
||||
Class? concreteRecordLiteralClass(CoreTypes coreTypes) => null;
|
||||
Class? concreteConstRecordLiteralClass(CoreTypes coreTypes) => null;
|
||||
Class? concreteRecordClass(CoreTypes coreTypes) => null;
|
||||
|
||||
Class? concreteIntLiteralClass(CoreTypes coreTypes, int value) => null;
|
||||
Class? concreteDoubleLiteralClass(CoreTypes coreTypes, double value) => null;
|
||||
|
|
|
@ -453,14 +453,10 @@ class VmTarget extends Target {
|
|||
}
|
||||
|
||||
@override
|
||||
Class concreteRecordLiteralClass(CoreTypes coreTypes) {
|
||||
Class concreteRecordClass(CoreTypes coreTypes) {
|
||||
return _record ??= coreTypes.index.getClass('dart:core', '_Record');
|
||||
}
|
||||
|
||||
@override
|
||||
Class concreteConstRecordLiteralClass(CoreTypes coreTypes) =>
|
||||
concreteRecordLiteralClass(coreTypes);
|
||||
|
||||
@override
|
||||
Class? concreteIntLiteralClass(CoreTypes coreTypes, int value) {
|
||||
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,
|
||||
TypeFlowAnalysis typeFlowAnalysis) {
|
||||
Class nullClass =
|
||||
typeFlowAnalysis.environment.coreTypes.deprecatedNullClass;
|
||||
|
||||
Member? target = typeFlowAnalysis.hierarchyCache.hierarchy
|
||||
.getDispatchTarget(nullClass, selector.name, setter: selector.isSetter);
|
||||
|
||||
final Member? target = typeFlowAnalysis.hierarchyCache._nullTFClass
|
||||
.getDispatchTarget(selector);
|
||||
if (target != null) {
|
||||
if (kPrintTrace) {
|
||||
tracePrint("Found $target for null receiver");
|
||||
|
@ -559,9 +554,12 @@ class _DispatchableInvocation extends _Invocation {
|
|||
ConcreteType receiver,
|
||||
Map<Member, _ReceiverTypeBuilder> targets,
|
||||
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 (kPrintTrace) {
|
||||
|
@ -1011,13 +1009,15 @@ class _TFClassImpl extends TFClass {
|
|||
late final Map<Name, Member> _dispatchTargetsNonSetters =
|
||||
_initDispatchTargets(false);
|
||||
final _DependencyTracker dependencyTracker = new _DependencyTracker();
|
||||
final bool hasMutableDispatchTargets;
|
||||
|
||||
/// Flag indicating if this class has a noSuchMethod() method not inherited
|
||||
/// from Object.
|
||||
/// Lazy initialized by ClassHierarchyCache.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) {
|
||||
supertypes.add(this);
|
||||
}
|
||||
|
@ -1056,6 +1056,7 @@ class _TFClassImpl extends TFClass {
|
|||
}
|
||||
|
||||
void addAllocatedSubtype(_TFClassImpl subType) {
|
||||
assert(subType == this || !hasMutableDispatchTargets);
|
||||
_allocatedSubtypes.add(subType);
|
||||
_specializedConeType = null; // Reset cached specialization.
|
||||
}
|
||||
|
@ -1064,6 +1065,7 @@ class _TFClassImpl extends TFClass {
|
|||
Map<Name, Member> targets;
|
||||
final superclass = this.superclass;
|
||||
if (superclass != null) {
|
||||
assert(!superclass.hasMutableDispatchTargets);
|
||||
targets = Map.from(setters
|
||||
? superclass._dispatchTargetsSetters
|
||||
: superclass._dispatchTargetsNonSetters);
|
||||
|
@ -1093,6 +1095,15 @@ class _TFClassImpl extends TFClass {
|
|||
: _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}";
|
||||
}
|
||||
|
||||
|
@ -1167,7 +1178,6 @@ class GenericInterfacesInfoImpl implements GenericInterfacesInfo {
|
|||
// TODO(alexmarkov): Rename to _TypeHierarchyImpl.
|
||||
class _ClassHierarchyCache extends TypeHierarchy {
|
||||
final TypeFlowAnalysis _typeFlowAnalysis;
|
||||
final ClosedWorldClassHierarchy hierarchy;
|
||||
final TypeEnvironment environment;
|
||||
final Set<Class> allocatedClasses = new Set<Class>();
|
||||
final Map<Class, _TFClassImpl> classes = <Class, _TFClassImpl>{};
|
||||
|
@ -1178,6 +1188,10 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
|||
|
||||
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.
|
||||
/// Once it is sealed, no new allocated classes may be added and no new
|
||||
/// targets of invocations may appear.
|
||||
|
@ -1189,10 +1203,31 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
|||
final Map<DynamicSelector, _DynamicTargetSet> _dynamicTargets =
|
||||
<DynamicSelector, _DynamicTargetSet>{};
|
||||
|
||||
_ClassHierarchyCache(this._typeFlowAnalysis, this.hierarchy,
|
||||
this.genericInterfacesInfo, this.environment, bool nullSafety)
|
||||
: objectNoSuchMethod = hierarchy.getDispatchTarget(
|
||||
environment.coreTypes.objectClass, noSuchMethodName)!,
|
||||
final Map<String, Field> _recordFields = <String, Field>{};
|
||||
|
||||
@override
|
||||
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);
|
||||
|
||||
@override
|
||||
|
@ -1208,7 +1243,8 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
|||
Class? superclassNode = c.superclass;
|
||||
_TFClassImpl? superclass =
|
||||
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) {
|
||||
|
@ -1286,7 +1322,7 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
|||
bool? value = classImpl.hasNonTrivialNoSuchMethod;
|
||||
if (value == null) {
|
||||
classImpl.hasNonTrivialNoSuchMethod = value =
|
||||
(hierarchy.getDispatchTarget(c.classNode, noSuchMethodName) !=
|
||||
(classImpl._dispatchTargetsNonSetters[noSuchMethodName] !=
|
||||
objectNoSuchMethod);
|
||||
}
|
||||
return value;
|
||||
|
@ -1297,11 +1333,7 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
|||
}
|
||||
|
||||
_DynamicTargetSet _createDynamicTargetSet(DynamicSelector selector) {
|
||||
// TODO(alexmarkov): consider caching the set of Object selectors.
|
||||
final isObjectMethod = (hierarchy.getDispatchTarget(
|
||||
_typeFlowAnalysis.environment.coreTypes.objectClass, selector.name,
|
||||
setter: selector.isSetter) !=
|
||||
null);
|
||||
final isObjectMethod = _objectTFClass.getDispatchTarget(selector) != null;
|
||||
|
||||
final targetSet = new _DynamicTargetSet(selector, isObjectMethod);
|
||||
for (Class c in allocatedClasses) {
|
||||
|
@ -1313,8 +1345,7 @@ class _ClassHierarchyCache extends TypeHierarchy {
|
|||
void _addDynamicTarget(Class c, _DynamicTargetSet targetSet) {
|
||||
assert(!_sealed);
|
||||
final selector = targetSet.selector;
|
||||
final member = hierarchy.getDispatchTarget(c, selector.name,
|
||||
setter: selector.isSetter);
|
||||
final member = getTFClass(c).getDispatchTarget(selector);
|
||||
if (member != null) {
|
||||
if (targetSet.targets.add(member)) {
|
||||
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
|
||||
List<DartType> flattenedTypeArgumentsFor(Class klass) =>
|
||||
genericInterfacesInfo.flattenedTypeArgumentsFor(klass);
|
||||
|
@ -1553,8 +1613,8 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
|
|||
: annotationMatcher =
|
||||
matcher ?? new ConstantPragmaAnnotationParser(coreTypes, target) {
|
||||
nativeCodeOracle = new NativeCodeOracle(libraryIndex, annotationMatcher);
|
||||
hierarchyCache = new _ClassHierarchyCache(this, hierarchy,
|
||||
_genericInterfacesInfo, environment, target.flags.enableNullSafety);
|
||||
hierarchyCache = new _ClassHierarchyCache(this, _genericInterfacesInfo,
|
||||
environment, target.flags.enableNullSafety);
|
||||
summaryCollector = new SummaryCollector(
|
||||
target,
|
||||
environment,
|
||||
|
@ -1769,6 +1829,12 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
|
|||
return hierarchyCache.addAllocatedClass(c);
|
||||
}
|
||||
|
||||
@override
|
||||
Field getRecordPositionalField(int pos) => getRecordNamedField("\$$pos");
|
||||
|
||||
@override
|
||||
Field getRecordNamedField(String name) => hierarchyCache.getRecordField(name);
|
||||
|
||||
@override
|
||||
void recordMemberCalledViaInterfaceSelector(Member target) {
|
||||
_calledViaInterfaceSelector.add(target);
|
||||
|
|
|
@ -23,6 +23,12 @@ abstract class EntryPointsListener {
|
|||
/// Add instantiation of the given class.
|
||||
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
|
||||
/// (not dynamically, and not from `this`).
|
||||
void recordMemberCalledViaInterfaceSelector(Member target);
|
||||
|
|
|
@ -526,6 +526,12 @@ class _EntryPointsListenerImpl implements EntryPointsListener {
|
|||
@override
|
||||
ConcreteType addAllocatedClass(Class c) => rta.addAllocatedClass(c);
|
||||
|
||||
@override
|
||||
Field getRecordPositionalField(int pos) => throw 'Unsupported operation';
|
||||
|
||||
@override
|
||||
Field getRecordNamedField(String name) => throw 'Unsupported operation';
|
||||
|
||||
@override
|
||||
void recordMemberCalledViaInterfaceSelector(Member target) =>
|
||||
throw 'Unsupported operation';
|
||||
|
|
|
@ -298,7 +298,7 @@ class Call extends Statement {
|
|||
|
||||
Member? _monomorphicTarget;
|
||||
|
||||
Member? get monomorphicTarget => _monomorphicTarget;
|
||||
Member? get monomorphicTarget => filterArtificialNode(_monomorphicTarget);
|
||||
|
||||
bool get isMonomorphic => (_flags & kMonomorphic) != 0;
|
||||
|
||||
|
|
|
@ -1622,30 +1622,42 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
|
|||
|
||||
@override
|
||||
TypeExpr visitRecordLiteral(RecordLiteral node) {
|
||||
for (var expr in node.positional) {
|
||||
_visit(expr);
|
||||
final Type receiver = _typesBuilder.recordType;
|
||||
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) {
|
||||
_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 =
|
||||
target.concreteRecordLiteralClass(_environment.coreTypes);
|
||||
if (concreteClass != null) {
|
||||
return _entryPointsListener.addAllocatedClass(concreteClass);
|
||||
}
|
||||
return _staticType(node);
|
||||
callSites.remove(node);
|
||||
return receiver;
|
||||
}
|
||||
|
||||
@override
|
||||
TypeExpr visitRecordIndexGet(RecordIndexGet node) {
|
||||
_visit(node.receiver);
|
||||
return _staticType(node);
|
||||
final receiver = _visit(node.receiver);
|
||||
final Field field =
|
||||
_entryPointsListener.getRecordPositionalField(node.index);
|
||||
final args = Args<TypeExpr>([receiver]);
|
||||
return _makeCall(
|
||||
node, DirectSelector(field, callKind: CallKind.PropertyGet), args);
|
||||
}
|
||||
|
||||
@override
|
||||
TypeExpr visitRecordNameGet(RecordNameGet node) {
|
||||
_visit(node.receiver);
|
||||
return _staticType(node);
|
||||
final receiver = _visit(node.receiver);
|
||||
final Field field = _entryPointsListener.getRecordNamedField(node.name);
|
||||
final args = Args<TypeExpr>([receiver]);
|
||||
return _makeCall(
|
||||
node, DirectSelector(field, callKind: CallKind.PropertyGet), args);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -2594,20 +2606,19 @@ class ConstantAllocationCollector extends ConstantVisitor<Type> {
|
|||
|
||||
@override
|
||||
Type visitRecordConstant(RecordConstant constant) {
|
||||
for (var value in constant.positional) {
|
||||
typeFor(value);
|
||||
final epl = summaryCollector._entryPointsListener;
|
||||
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) {
|
||||
typeFor(value);
|
||||
}
|
||||
Class? concreteClass = summaryCollector.target
|
||||
.concreteConstRecordLiteralClass(
|
||||
summaryCollector._environment.coreTypes);
|
||||
if (concreteClass != null) {
|
||||
return summaryCollector._entryPointsListener
|
||||
.addAllocatedClass(concreteClass);
|
||||
}
|
||||
return _getStaticType(constant);
|
||||
constant.named.forEach((String fieldName, Constant fieldValue) {
|
||||
final Field f = epl.getRecordNamedField(fieldName);
|
||||
final Type value = typeFor(fieldValue);
|
||||
epl.addFieldUsedInConstant(f, receiver, value);
|
||||
});
|
||||
return receiver;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -139,8 +139,8 @@ Component transformComponent(
|
|||
final unboxingInfo = new UnboxingInfoManager(typeFlowAnalysis)
|
||||
..analyzeComponent(component, typeFlowAnalysis, tableSelectorAssigner);
|
||||
|
||||
new AnnotateKernel(component, typeFlowAnalysis, treeShaker.fieldMorpher,
|
||||
tableSelectorAssigner, unboxingInfo)
|
||||
new AnnotateKernel(component, typeFlowAnalysis, hierarchy,
|
||||
treeShaker.fieldMorpher, tableSelectorAssigner, unboxingInfo)
|
||||
.visitComponent(component);
|
||||
|
||||
transformsStopWatch.stop();
|
||||
|
@ -300,6 +300,7 @@ class TFADevirtualization extends Devirtualization {
|
|||
/// Annotates kernel AST with metadata using results of type flow analysis.
|
||||
class AnnotateKernel extends RecursiveVisitor {
|
||||
final TypeFlowAnalysis _typeFlowAnalysis;
|
||||
final ClassHierarchy hierarchy;
|
||||
final FieldMorpher fieldMorpher;
|
||||
final DirectCallMetadataRepository _directCallMetadataRepository;
|
||||
final InferredTypeMetadataRepository _inferredTypeMetadata;
|
||||
|
@ -312,8 +313,8 @@ class AnnotateKernel extends RecursiveVisitor {
|
|||
final Class _intClass;
|
||||
late final Constant _nullConstant = NullConstant();
|
||||
|
||||
AnnotateKernel(Component component, this._typeFlowAnalysis, this.fieldMorpher,
|
||||
this._tableSelectorAssigner, this._unboxingInfo)
|
||||
AnnotateKernel(Component component, this._typeFlowAnalysis, this.hierarchy,
|
||||
this.fieldMorpher, this._tableSelectorAssigner, this._unboxingInfo)
|
||||
: _directCallMetadataRepository =
|
||||
component.metadata[DirectCallMetadataRepository.repositoryTag]
|
||||
as DirectCallMetadataRepository,
|
||||
|
@ -429,9 +430,7 @@ class AnnotateKernel extends RecursiveVisitor {
|
|||
// here), then the receiver cannot be _Smi. This heuristic covers most
|
||||
// cases, so we skip these to avoid showering the AST with annotations.
|
||||
if (interfaceTarget == null ||
|
||||
_typeFlowAnalysis.hierarchyCache.hierarchy.isSubtypeOf(
|
||||
_typeFlowAnalysis.hierarchyCache.coreTypes.intClass,
|
||||
interfaceTarget.enclosingClass!)) {
|
||||
hierarchy.isSubtypeOf(_intClass, interfaceTarget.enclosingClass!)) {
|
||||
markReceiverNotInt = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,8 @@ abstract class TypesBuilder {
|
|||
/// Return [TFClass] corresponding to the given [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
|
||||
/// Dart type annotation [dartType].
|
||||
/// [canBeNull] can be set to false to further constrain the resulting
|
||||
|
@ -88,7 +90,7 @@ abstract class TypesBuilder {
|
|||
result = const AnyType();
|
||||
} else if (type is RecordType) {
|
||||
// TODO(dartbug.com/49719): support inference of record types
|
||||
result = const AnyType();
|
||||
result = recordType;
|
||||
} else if (type is FutureOrType) {
|
||||
// TODO(alexmarkov): support FutureOr types
|
||||
result = const AnyType();
|
||||
|
@ -575,7 +577,7 @@ class SetType extends Type {
|
|||
return type.intersection(other, typeHierarchy);
|
||||
}
|
||||
}
|
||||
return EmptyType();
|
||||
return const EmptyType();
|
||||
} else if (other is ConeType) {
|
||||
return typeHierarchy
|
||||
.specializeTypeCone(other.cls, allowWideCone: true)
|
||||
|
@ -818,7 +820,8 @@ class ConcreteType extends Type implements Comparable<ConcreteType> {
|
|||
bool get isRaw => typeArgs == null && constant == null;
|
||||
|
||||
@override
|
||||
Class getConcreteClass(TypeHierarchy typeHierarchy) => cls.classNode;
|
||||
Class? getConcreteClass(TypeHierarchy typeHierarchy) =>
|
||||
filterArtificialNode(cls.classNode);
|
||||
|
||||
@override
|
||||
bool isSubtypeOf(TypeHierarchy typeHierarchy, Class other) =>
|
||||
|
@ -989,7 +992,7 @@ class ConcreteType extends Type implements Comparable<ConcreteType> {
|
|||
return this;
|
||||
}
|
||||
if (!identical(this.cls, other.cls)) {
|
||||
return EmptyType();
|
||||
return const EmptyType();
|
||||
}
|
||||
if (typeArgs == null && constant == null) {
|
||||
return other;
|
||||
|
@ -1177,8 +1180,8 @@ class RuntimeType extends Type {
|
|||
throw "ERROR: RuntimeType does not support specialize.";
|
||||
|
||||
@override
|
||||
Class getConcreteClass(TypeHierarchy typeHierarchy) =>
|
||||
throw "ERROR: ConcreteClass does not support getConcreteClass.";
|
||||
Class? getConcreteClass(TypeHierarchy typeHierarchy) =>
|
||||
throw "ERROR: RuntimeType does not support getConcreteClass.";
|
||||
|
||||
bool isSubtypeOfRuntimeType(TypeHierarchy typeHierarchy,
|
||||
RuntimeType runtimeType, SubtypeTestKind kind) {
|
||||
|
|
|
@ -425,3 +425,14 @@ bool mayHaveOrSeeSideEffects(Expression node) {
|
|||
}
|
||||
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 'package:front_end/src/api_unstable/vm.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/class_hierarchy.dart';
|
||||
import 'package:kernel/core_types.dart';
|
||||
|
@ -21,7 +22,7 @@ import 'package:vm/transformations/type_flow/types.dart';
|
|||
|
||||
import '../../common_test_utils.dart';
|
||||
|
||||
final String pkgVmDir = Platform.script.resolve('../../..').toFilePath();
|
||||
final Uri pkgVmDir = Platform.script.resolve('../../..');
|
||||
|
||||
class FakeTypesBuilder extends TypesBuilder {
|
||||
final Map<Class, TFClass> _classes = <Class, TFClass>{};
|
||||
|
@ -51,6 +52,13 @@ class FakeEntryPointsListener implements EntryPointsListener {
|
|||
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
|
||||
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 Component component = await compileTestCaseToKernelProgram(source);
|
||||
final Component component = await compileTestCaseToKernelProgram(source,
|
||||
experimentalFlags: experimentalFlags);
|
||||
final Library library = component.mainMethod!.enclosingLibrary;
|
||||
final CoreTypes coreTypes = new CoreTypes(component);
|
||||
|
||||
|
@ -115,13 +131,22 @@ runTestCase(Uri source) async {
|
|||
|
||||
main() {
|
||||
group('collect-summary', () {
|
||||
final testCasesDir = new Directory(
|
||||
pkgVmDir + '/testcases/transformations/type_flow/summary_collector');
|
||||
final testCasesDir = Directory.fromUri(pkgVmDir
|
||||
.resolve('testcases/transformations/type_flow/summary_collector'));
|
||||
|
||||
for (var entry
|
||||
in testCasesDir.listSync(recursive: true, followLinks: false)) {
|
||||
if (entry.path.endsWith(".dart")) {
|
||||
test(entry.path, () => runTestCase(entry.uri));
|
||||
final path = entry.path;
|
||||
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('../../..');
|
||||
|
||||
void runTestCase(
|
||||
Uri source, bool enableNullSafety, List<Uri>? linkedDependencies) async {
|
||||
void runTestCase(Uri source, bool enableNullSafety,
|
||||
List<Uri>? linkedDependencies, List<String>? experimentalFlags) async {
|
||||
final target =
|
||||
new TestingVmTarget(new TargetFlags(enableNullSafety: enableNullSafety));
|
||||
Component component = await compileTestCaseToKernelProgram(source,
|
||||
target: target, linkedDependencies: linkedDependencies);
|
||||
target: target,
|
||||
linkedDependencies: linkedDependencies,
|
||||
experimentalFlags: experimentalFlags);
|
||||
|
||||
final coreTypes = new CoreTypes(component);
|
||||
|
||||
|
@ -84,7 +86,10 @@ class TestOptions {
|
|||
static const Option<bool> nnbdStrong =
|
||||
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) {
|
||||
|
@ -107,6 +112,7 @@ void main(List<String> args) {
|
|||
}
|
||||
List<Uri>? linkDependencies;
|
||||
bool enableNullSafety = path.endsWith('_nnbd_strong.dart');
|
||||
List<String>? experimentalFlags;
|
||||
|
||||
File optionsFile = new File('${path}.options');
|
||||
if (optionsFile.existsSync()) {
|
||||
|
@ -121,10 +127,13 @@ void main(List<String> args) {
|
|||
if (TestOptions.nnbdStrong.read(parsedOptions)) {
|
||||
enableNullSafety = true;
|
||||
}
|
||||
experimentalFlags = TestOptions.enableExperiment.read(parsedOptions);
|
||||
}
|
||||
|
||||
test(path,
|
||||
() => runTestCase(entry.uri, enableNullSafety, linkDependencies));
|
||||
test(
|
||||
path,
|
||||
() => runTestCase(entry.uri, enableNullSafety, linkDependencies,
|
||||
experimentalFlags));
|
||||
}
|
||||
}
|
||||
}, 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'),
|
||||
];
|
||||
|
||||
var pkgVmPackageDirs = [
|
||||
platform('pkg/vm/testcases'),
|
||||
];
|
||||
|
||||
// Validate that all the given directories exist.
|
||||
var hasMissingDirectories = false;
|
||||
for (var path in [
|
||||
...packageDirs,
|
||||
...cfePackageDirs,
|
||||
...feAnalyzerSharedPackageDirs
|
||||
...feAnalyzerSharedPackageDirs,
|
||||
...pkgVmPackageDirs
|
||||
]) {
|
||||
if (!Directory(join(repoRoot, path)).existsSync()) {
|
||||
stderr.writeln("Unable to locate directory: '$path'.");
|
||||
|
@ -65,7 +70,8 @@ void main(List<String> args) {
|
|||
var packages = <Package>[
|
||||
...makePackageConfigs(packageDirs),
|
||||
...makeCfePackageConfigs(cfePackageDirs),
|
||||
...makeFeAnalyzerSharedPackageConfigs(feAnalyzerSharedPackageDirs)
|
||||
...makeFeAnalyzerSharedPackageConfigs(feAnalyzerSharedPackageDirs),
|
||||
...makePkgVmPackageConfigs(pkgVmPackageDirs),
|
||||
];
|
||||
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
|
||||
/// CFE unit tests (`pkg/front_end/test/unit_test_suites.dart`).
|
||||
Iterable<Package> makeCfePackageConfigs(List<String> packageDirs) sync* {
|
||||
/// Generates package configurations for the special pseudo-packages.
|
||||
Iterable<Package> makeSpecialPackageConfigs(
|
||||
String packageNamePrefix, List<String> packageDirs) sync* {
|
||||
// TODO: Remove the use of '.nonexisting/'.
|
||||
for (var packageDir in packageDirs) {
|
||||
yield Package(
|
||||
name: 'front_end_${basename(packageDir)}',
|
||||
name: '${packageNamePrefix}_${basename(packageDir)}',
|
||||
rootUri: packageDir,
|
||||
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
|
||||
/// _fe_analyzer_shared id tests.
|
||||
Iterable<Package> makeFeAnalyzerSharedPackageConfigs(
|
||||
List<String> packageDirs) sync* {
|
||||
// TODO: Remove the use of '.nonexisting/'.
|
||||
for (var packageDir in packageDirs) {
|
||||
yield Package(
|
||||
name: '_fe_analyzer_shared_${basename(packageDir)}',
|
||||
rootUri: packageDir,
|
||||
packageUri: '.nonexisting/',
|
||||
);
|
||||
}
|
||||
}
|
||||
List<String> packageDirs) =>
|
||||
makeSpecialPackageConfigs('_fe_analyzer_shared', packageDirs);
|
||||
|
||||
/// Generates package configurations for the special pseudo-packages used by the
|
||||
/// pkg/vm unit tests (`pkg/vm/test`).
|
||||
Iterable<Package> makePkgVmPackageConfigs(List<String> packageDirs) =>
|
||||
makeSpecialPackageConfigs('pkg_vm', packageDirs);
|
||||
|
||||
/// Finds the paths of the subdirectories of [dirPath] that contain pubspecs.
|
||||
///
|
||||
|
|
Loading…
Reference in a new issue