[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:
Alexander Markov 2022-10-19 17:49:47 +00:00 committed by Commit Queue
parent d923f69436
commit 671717b578
19 changed files with 330 additions and 102 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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';

View file

@ -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;

View file

@ -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

View file

@ -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;
}
}

View file

@ -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) {

View file

@ -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;

View file

@ -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));
}
}
});

View file

@ -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);

View file

@ -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() {}

View file

@ -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)+

View file

@ -0,0 +1 @@
--enable-experiment=records

View file

@ -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]));
}

View file

@ -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})
}

View file

@ -0,0 +1 @@
--enable-experiment=records

View file

@ -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.
///