Files
sdk/pkg/compiler/test/rti/rti_need_test_helper.dart
T
Mayank Patke 3c5767ccd3 [dart2js] Ensure RTI need/checks account for instantiations.
If a type variable T is used in an `is` or `as` expression (e.g.
`42 is T` or `42 as T`) or is part of a subtype check, then any types
which may be substituted for T via instantiation must also be treated as
participating in a type check.

This CL ensures that the RTI need computation includes type arguments
for those substituted types and that those types are included in the
list of implicit checks.

Additionally, in order for this to work, this CL fixes a bug in which
the impact transformer was failing to register generic instantiations
occurring in (partial) constants.

Fixes: https://github.com/dart-lang/sdk/issues/45046
Change-Id: I98ae0eca0adcbbb26cdd664318da0da578b289b5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/190460
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Mayank Patke <fishythefish@google.com>
2021-03-12 04:47:59 +00:00

381 lines
14 KiB
Dart

// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart = 2.7
import 'dart:io';
import 'package:_fe_analyzer_shared/src/testing/features.dart';
import 'package:async_helper/async_helper.dart';
import 'package:compiler/src/closure.dart';
import 'package:compiler/src/common.dart';
import 'package:compiler/src/common_elements.dart';
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/diagnostics/diagnostic_listener.dart';
import 'package:compiler/src/elements/entities.dart';
import 'package:compiler/src/elements/types.dart';
import 'package:compiler/src/js_backend/runtime_types_resolution.dart';
import 'package:compiler/src/js_model/js_world.dart';
import 'package:compiler/src/js_model/element_map.dart';
import 'package:compiler/src/kernel/element_map.dart';
import 'package:compiler/src/kernel/kernel_strategy.dart';
import 'package:compiler/src/universe/feature.dart';
import 'package:compiler/src/universe/resolution_world_builder.dart';
import 'package:compiler/src/universe/selector.dart';
import 'package:kernel/ast.dart' as ir;
import '../equivalence/check_helpers.dart';
import '../equivalence/id_equivalence.dart';
import '../equivalence/id_equivalence_helper.dart';
main(List<String> args) {
runTests(args);
}
runTests(List<String> args, [int shardIndex]) {
asyncTest(() async {
Directory dataDir = new Directory.fromUri(Platform.script.resolve('data'));
await checkTests(dataDir, const RtiNeedDataComputer(),
options: [],
args: args,
shardIndex: shardIndex ?? 0,
shards: shardIndex != null ? 4 : 1);
});
}
class Tags {
static const String needsTypeArguments = 'needsArgs';
static const String needsSignature = 'needsSignature';
static const String dependencies = 'deps';
static const String explicitTypeCheck = 'explicit';
static const String implicitTypeCheck = 'implicit';
static const String directTypeArgumentTest = 'direct';
static const String indirectTypeArgumentTest = 'indirect';
static const String typeLiteral = 'exp';
static const String selectors = 'selectors';
static const String instantiationsNeedTypeArguments = 'needsInst';
}
abstract class ComputeValueMixin {
Compiler get compiler;
KernelFrontendStrategy get frontendStrategy => compiler.frontendStrategy;
ResolutionWorldBuilder get resolutionWorldBuilder =>
compiler.resolutionWorldBuilderForTesting;
RuntimeTypesNeedBuilderImpl get rtiNeedBuilder =>
frontendStrategy.runtimeTypesNeedBuilderForTesting;
RuntimeTypesNeedImpl get rtiNeed =>
compiler.backendClosedWorldForTesting.rtiNeed;
ClassEntity getFrontendClass(ClassEntity cls);
MemberEntity getFrontendMember(MemberEntity member);
Local getFrontendClosure(MemberEntity member);
void findChecks(
Features features, String key, Entity entity, Set<DartType> checks) {
Set<DartType> types = new Set<DartType>();
FindTypeVisitor finder = new FindTypeVisitor(entity);
for (DartType type in checks) {
if (type.accept(finder, null)) {
types.add(type);
}
}
List<String> list = types.map(typeToString).toList()..sort();
if (list.isNotEmpty) {
features[key] = '[${list.join(',')}]';
}
}
void findDependencies(Features features, Entity entity) {
Iterable<Entity> dependencies = rtiNeedBuilder.typeVariableTestsForTesting
.getTypeArgumentDependencies(entity);
if (dependencies.isNotEmpty) {
List<String> names = dependencies.map((Entity d) {
if (d is MemberEntity && d.enclosingClass != null) {
return '${d.enclosingClass.name}.${d.name}';
}
return d.name;
}).toList()
..sort();
features[Tags.dependencies] = '[${names.join(',')}]';
}
}
String getClassValue(ClassEntity backendClass) {
Features features = new Features();
if (rtiNeed.classNeedsTypeArguments(backendClass)) {
features.add(Tags.needsTypeArguments);
}
ClassEntity frontendClass = getFrontendClass(backendClass);
findDependencies(features, frontendClass);
if (rtiNeedBuilder.classesUsingTypeVariableLiterals
.contains(frontendClass)) {
features.add(Tags.typeLiteral);
}
if (rtiNeedBuilder.typeVariableTestsForTesting.directClassTestsForTesting
.contains(frontendClass)) {
features.add(Tags.directTypeArgumentTest);
} else if (rtiNeedBuilder.typeVariableTestsForTesting.classTestsForTesting
.contains(frontendClass)) {
features.add(Tags.indirectTypeArgumentTest);
}
findChecks(features, Tags.explicitTypeCheck, frontendClass,
rtiNeedBuilder.typeVariableTestsForTesting.explicitIsChecks);
findChecks(features, Tags.implicitTypeCheck, frontendClass,
rtiNeedBuilder.typeVariableTestsForTesting.implicitIsChecks);
return features.getText();
}
String getMemberValue(MemberEntity backendMember) {
MemberEntity frontendMember = getFrontendMember(backendMember);
Local frontendClosure = getFrontendClosure(backendMember);
Features features = new Features();
if (backendMember is FunctionEntity) {
if (rtiNeed.methodNeedsTypeArguments(backendMember)) {
features.add(Tags.needsTypeArguments);
}
if (rtiNeed.methodNeedsSignature(backendMember)) {
features.add(Tags.needsSignature);
}
void addFrontendData(Entity entity) {
findDependencies(features, entity);
if (rtiNeedBuilder
.typeVariableTestsForTesting.directMethodTestsForTesting
.contains(entity)) {
features.add(Tags.directTypeArgumentTest);
} else if (rtiNeedBuilder
.typeVariableTestsForTesting.methodTestsForTesting
.contains(entity)) {
features.add(Tags.indirectTypeArgumentTest);
}
findChecks(features, Tags.explicitTypeCheck, entity,
rtiNeedBuilder.typeVariableTestsForTesting.explicitIsChecks);
findChecks(features, Tags.implicitTypeCheck, entity,
rtiNeedBuilder.typeVariableTestsForTesting.implicitIsChecks);
rtiNeedBuilder.selectorsNeedingTypeArgumentsForTesting
?.forEach((Selector selector, Set<Entity> targets) {
if (targets.contains(entity)) {
features.addElement(Tags.selectors, selector);
}
});
rtiNeedBuilder
.instantiatedEntitiesNeedingTypeArgumentsForTesting[entity]
?.forEach((GenericInstantiation instantiation) {
features.addElement(
Tags.instantiationsNeedTypeArguments, instantiation.shortText);
});
}
if (frontendClosure != null) {
addFrontendData(frontendClosure);
if (rtiNeedBuilder.localFunctionsUsingTypeVariableLiterals
.contains(frontendClosure)) {
features.add(Tags.typeLiteral);
}
} else if (frontendMember != null) {
addFrontendData(frontendMember);
if (rtiNeedBuilder.methodsUsingTypeVariableLiterals
.contains(frontendMember)) {
features.add(Tags.typeLiteral);
}
}
}
return features.getText();
}
}
/// Visitor that determines whether a type refers to [entity].
class FindTypeVisitor extends BaseDartTypeVisitor<bool, Null> {
final Entity entity;
FindTypeVisitor(this.entity);
bool visitTypes(List<DartType> types) {
for (DartType type in types) {
if (type.accept(this, null)) {
return true;
}
}
return false;
}
@override
bool visitType(DartType type, _) => false;
@override
bool visitLegacyType(LegacyType type, _) => visit(type.baseType, _);
@override
bool visitNullableType(NullableType type, _) => visit(type.baseType, _);
@override
bool visitInterfaceType(InterfaceType type, _) {
if (type.element == entity) return true;
return visitTypes(type.typeArguments);
}
@override
bool visitFunctionType(FunctionType type, _) {
if (type.returnType.accept(this, null)) return true;
if (visitTypes(type.typeVariables)) return true;
if (visitTypes(type.parameterTypes)) return true;
if (visitTypes(type.optionalParameterTypes)) return true;
if (visitTypes(type.namedParameterTypes)) return true;
return false;
}
@override
bool visitTypeVariableType(TypeVariableType type, _) {
return type.element.typeDeclaration == entity;
}
@override
bool visitFutureOrType(FutureOrType type, _) {
return type.typeArgument.accept(this, null);
}
}
class RtiNeedDataComputer extends DataComputer<String> {
const RtiNeedDataComputer();
/// Compute RTI need data for [member] from the new frontend.
///
/// Fills [actualMap] with the data.
@override
void computeMemberData(Compiler compiler, MemberEntity member,
Map<Id, ActualData<String>> actualMap,
{bool verbose: false}) {
JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
JsToElementMap elementMap = closedWorld.elementMap;
MemberDefinition definition = elementMap.getMemberDefinition(member);
new RtiNeedIrComputer(compiler.reporter, actualMap, elementMap, compiler,
closedWorld.closureDataLookup)
.run(definition.node);
}
/// Compute RTI need data for [cls] from the new frontend.
///
/// Fills [actualMap] with the data.
@override
void computeClassData(
Compiler compiler, ClassEntity cls, Map<Id, ActualData<String>> actualMap,
{bool verbose: false}) {
JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
JsToElementMap elementMap = closedWorld.elementMap;
new RtiNeedIrComputer(compiler.reporter, actualMap, elementMap, compiler,
closedWorld.closureDataLookup)
.computeForClass(elementMap.getClassDefinition(cls).node);
}
@override
DataInterpreter<String> get dataValidator => const StringDataInterpreter();
}
abstract class IrMixin implements ComputeValueMixin {
@override
MemberEntity getFrontendMember(MemberEntity backendMember) {
ElementEnvironment elementEnvironment =
compiler.frontendClosedWorldForTesting.elementEnvironment;
LibraryEntity frontendLibrary =
elementEnvironment.lookupLibrary(backendMember.library.canonicalUri);
if (backendMember.enclosingClass != null) {
if (backendMember.enclosingClass.isClosure) return null;
ClassEntity frontendClass = elementEnvironment.lookupClass(
frontendLibrary, backendMember.enclosingClass.name);
if (backendMember is ConstructorEntity) {
return elementEnvironment.lookupConstructor(
frontendClass, backendMember.name);
} else {
return elementEnvironment.lookupClassMember(
frontendClass, backendMember.name,
setter: backendMember.isSetter);
}
}
return elementEnvironment.lookupLibraryMember(
frontendLibrary, backendMember.name,
setter: backendMember.isSetter);
}
@override
ClassEntity getFrontendClass(ClassEntity backendClass) {
if (backendClass.isClosure) return null;
ElementEnvironment elementEnvironment =
compiler.frontendClosedWorldForTesting.elementEnvironment;
LibraryEntity frontendLibrary =
elementEnvironment.lookupLibrary(backendClass.library.canonicalUri);
return elementEnvironment.lookupClass(frontendLibrary, backendClass.name);
}
@override
Local getFrontendClosure(MemberEntity member) {
JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
ir.Node node = closedWorld.elementMap.getMemberDefinition(member).node;
if (node is ir.FunctionDeclaration || node is ir.FunctionExpression) {
KernelFrontendStrategy frontendStrategy = compiler.frontendStrategy;
KernelToElementMap frontendElementMap = frontendStrategy.elementMap;
return frontendElementMap.getLocalFunction(node);
}
return null;
}
}
class RtiClassNeedIrComputer extends DataRegistry<String>
with ComputeValueMixin, IrMixin, IrDataRegistryMixin<String> {
@override
final Compiler compiler;
final JsToElementMap _elementMap;
@override
final Map<Id, ActualData<String>> actualMap;
RtiClassNeedIrComputer(this.compiler, this._elementMap, this.actualMap);
@override
DiagnosticReporter get reporter => compiler.reporter;
void computeClassValue(ClassEntity cls) {
Id id = new ClassId(cls.name);
ir.TreeNode node = _elementMap.getClassDefinition(cls).node;
ir.TreeNode nodeWithOffset = computeTreeNodeWithOffset(node);
registerValue(nodeWithOffset?.location?.file, nodeWithOffset?.fileOffset,
id, getClassValue(cls), cls);
}
}
/// AST visitor for computing inference data for a member.
class RtiNeedIrComputer extends IrDataExtractor<String>
with ComputeValueMixin, IrMixin {
final JsToElementMap _elementMap;
final ClosureData _closureDataLookup;
@override
final Compiler compiler;
RtiNeedIrComputer(
DiagnosticReporter reporter,
Map<Id, ActualData<String>> actualMap,
this._elementMap,
this.compiler,
this._closureDataLookup)
: super(reporter, actualMap);
@override
String computeClassValue(Id id, ir.Class node) {
return getClassValue(_elementMap.getClass(node));
}
@override
String computeMemberValue(Id id, ir.Member node) {
return getMemberValue(_elementMap.getMember(node));
}
@override
String computeNodeValue(Id id, ir.TreeNode node) {
if (node is ir.FunctionExpression || node is ir.FunctionDeclaration) {
ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo(node);
return getMemberValue(info.callMethod);
}
return null;
}
}