mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 08:24:39 +00:00
[pkg:js] Add type/conformance checking for createStaticInteropMock
Bug: https://github.com/dart-lang/sdk/issues/49351 Adds checks for the following cases: - Type arguments to createStaticInteropMock are correct - No missing members in implementing class - Inherited and non-overridden @staticInterop members are implemented - Dart class can implement through inheritance and mixins - Implemented members are correct subtypes of @staticInterop members - Potential extension member conflicts that are attempted to be resolved through subtyping rules Change-Id: Iacbe5846040ba7fab41459aa19be77351cf1efca Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255761 Reviewed-by: Sigmund Cherem <sigmund@google.com> Reviewed-by: Riley Porter <rileyporter@google.com>
This commit is contained in:
parent
8ebb7d70e5
commit
8a081b95c6
|
@ -7121,6 +7121,76 @@ const MessageCode messageJsInteropOperatorsNotSupported = const MessageCode(
|
|||
problemMessage: r"""JS interop classes do not support operator methods.""",
|
||||
correctionMessage: r"""Try replacing this with a normal method.""");
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<Message Function(String name, String string)>
|
||||
templateJsInteropStaticInteropMockExternalExtensionMemberConflict =
|
||||
const Template<Message Function(String name, String string)>(
|
||||
problemMessageTemplate:
|
||||
r"""External extension member with name '#name' is defined in the following extensions and none are more specific: #string.""",
|
||||
correctionMessageTemplate:
|
||||
r"""Try using the `@JS` annotation to rename conflicting members.""",
|
||||
withArguments:
|
||||
_withArgumentsJsInteropStaticInteropMockExternalExtensionMemberConflict);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(String name, String string)>
|
||||
codeJsInteropStaticInteropMockExternalExtensionMemberConflict =
|
||||
const Code<Message Function(String name, String string)>(
|
||||
"JsInteropStaticInteropMockExternalExtensionMemberConflict",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArgumentsJsInteropStaticInteropMockExternalExtensionMemberConflict(
|
||||
String name, String string) {
|
||||
if (name.isEmpty) throw 'No name provided';
|
||||
name = demangleMixinApplicationName(name);
|
||||
if (string.isEmpty) throw 'No string provided';
|
||||
return new Message(
|
||||
codeJsInteropStaticInteropMockExternalExtensionMemberConflict,
|
||||
problemMessage:
|
||||
"""External extension member with name '${name}' is defined in the following extensions and none are more specific: ${string}.""",
|
||||
correctionMessage: """Try using the `@JS` annotation to rename conflicting members.""",
|
||||
arguments: {'name': name, 'string': string});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<
|
||||
Message Function(
|
||||
String name,
|
||||
String name2,
|
||||
String
|
||||
name3)> templateJsInteropStaticInteropMockMissingOverride = const Template<
|
||||
Message Function(
|
||||
String name, String name2, String name3)>(
|
||||
problemMessageTemplate:
|
||||
r"""`@staticInterop` class '#name' has external extension member '#name2', but Dart class '#name3' does not have an overriding instance member.""",
|
||||
correctionMessageTemplate:
|
||||
r"""Add a Dart instance member in '#name3' that overrides '#name.#name2'.""",
|
||||
withArguments: _withArgumentsJsInteropStaticInteropMockMissingOverride);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(String name, String name2, String name3)>
|
||||
codeJsInteropStaticInteropMockMissingOverride =
|
||||
const Code<Message Function(String name, String name2, String name3)>(
|
||||
"JsInteropStaticInteropMockMissingOverride",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArgumentsJsInteropStaticInteropMockMissingOverride(
|
||||
String name, String name2, String name3) {
|
||||
if (name.isEmpty) throw 'No name provided';
|
||||
name = demangleMixinApplicationName(name);
|
||||
if (name2.isEmpty) throw 'No name provided';
|
||||
name2 = demangleMixinApplicationName(name2);
|
||||
if (name3.isEmpty) throw 'No name provided';
|
||||
name3 = demangleMixinApplicationName(name3);
|
||||
return new Message(codeJsInteropStaticInteropMockMissingOverride,
|
||||
problemMessage:
|
||||
"""`@staticInterop` class '${name}' has external extension member '${name2}', but Dart class '${name3}' does not have an overriding instance member.""",
|
||||
correctionMessage: """Add a Dart instance member in '${name3}' that overrides '${name}.${name2}'.""",
|
||||
arguments: {'name': name, 'name2': name2, 'name3': name3});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<Message Function(String name)>
|
||||
templateJsInteropStaticInteropTrustTypesUsageNotAllowed =
|
||||
|
|
|
@ -0,0 +1,435 @@
|
|||
// 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.
|
||||
|
||||
import 'package:front_end/src/fasta/fasta_codes.dart'
|
||||
show
|
||||
templateJsInteropStaticInteropMockMemberNotSubtype,
|
||||
templateJsInteropStaticInteropMockNotDartInterfaceType,
|
||||
templateJsInteropStaticInteropMockNotStaticInteropType;
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/target/targets.dart';
|
||||
import 'package:kernel/type_environment.dart';
|
||||
import 'package:_fe_analyzer_shared/src/messages/codes.dart'
|
||||
show
|
||||
Message,
|
||||
LocatedMessage,
|
||||
templateJsInteropStaticInteropMockMissingOverride,
|
||||
templateJsInteropStaticInteropMockExternalExtensionMemberConflict;
|
||||
import 'package:_js_interop_checks/src/js_interop.dart';
|
||||
|
||||
class _ExtensionVisitor extends RecursiveVisitor {
|
||||
final Map<Reference, Extension> staticInteropClassesWithExtensions;
|
||||
|
||||
_ExtensionVisitor(this.staticInteropClassesWithExtensions);
|
||||
|
||||
@override
|
||||
void visitExtension(Extension extension) {
|
||||
// TODO(srujzs): This code was written with the assumption there would be
|
||||
// one single extension per `@staticInterop` class. This is no longer true
|
||||
// and this code needs to be refactored to handle multiple extensions.
|
||||
var onType = extension.onType;
|
||||
if (onType is InterfaceType &&
|
||||
hasStaticInteropAnnotation(onType.classNode)) {
|
||||
if (!staticInteropClassesWithExtensions.containsKey(onType.className)) {
|
||||
staticInteropClassesWithExtensions[onType.className] = extension;
|
||||
}
|
||||
}
|
||||
super.visitExtension(extension);
|
||||
}
|
||||
}
|
||||
|
||||
class StaticInteropMockCreator extends Transformer {
|
||||
late final _ExtensionVisitor _extensionVisitor;
|
||||
final Map<Reference, Extension> _staticInteropClassesWithExtensions = {};
|
||||
final TypeEnvironment _typeEnvironment;
|
||||
final DiagnosticReporter<Message, LocatedMessage> _diagnosticReporter;
|
||||
final Procedure _createStaticInteropMock;
|
||||
|
||||
StaticInteropMockCreator(this._typeEnvironment, this._diagnosticReporter)
|
||||
: _createStaticInteropMock = _typeEnvironment.coreTypes.index
|
||||
.getTopLevelProcedure('dart:js_util', 'createStaticInteropMock') {
|
||||
_extensionVisitor = _ExtensionVisitor(_staticInteropClassesWithExtensions);
|
||||
}
|
||||
|
||||
void processExtensions(Library library) =>
|
||||
_extensionVisitor.visitLibrary(library);
|
||||
|
||||
@override
|
||||
TreeNode visitStaticInvocation(StaticInvocation node) {
|
||||
if (node.target != _createStaticInteropMock) return node;
|
||||
var typeArguments = node.arguments.types;
|
||||
assert(typeArguments.length == 2);
|
||||
var staticInteropType = typeArguments[0];
|
||||
var dartType = typeArguments[1];
|
||||
var typeArgumentsError = false;
|
||||
if (staticInteropType is! InterfaceType ||
|
||||
!hasStaticInteropAnnotation(staticInteropType.classNode)) {
|
||||
_diagnosticReporter.report(
|
||||
templateJsInteropStaticInteropMockNotStaticInteropType.withArguments(
|
||||
staticInteropType, true),
|
||||
node.fileOffset,
|
||||
node.name.text.length,
|
||||
node.location?.file);
|
||||
typeArgumentsError = true;
|
||||
}
|
||||
if (dartType is! InterfaceType ||
|
||||
hasJSInteropAnnotation(dartType.classNode) ||
|
||||
hasStaticInteropAnnotation(dartType.classNode) ||
|
||||
hasAnonymousAnnotation(dartType.classNode)) {
|
||||
_diagnosticReporter.report(
|
||||
templateJsInteropStaticInteropMockNotDartInterfaceType.withArguments(
|
||||
dartType, true),
|
||||
node.fileOffset,
|
||||
node.name.text.length,
|
||||
node.location?.file);
|
||||
typeArgumentsError = true;
|
||||
}
|
||||
// Can't proceed with these errors.
|
||||
if (typeArgumentsError) return node;
|
||||
|
||||
var staticInteropClass = (staticInteropType as InterfaceType).classNode;
|
||||
var dartClass = (dartType as InterfaceType).classNode;
|
||||
|
||||
var dartMemberMap = <String, Member>{};
|
||||
for (var procedure in dartClass.allInstanceProcedures) {
|
||||
// We only care about concrete instance getters, setters, and methods.
|
||||
if (procedure.isAbstract ||
|
||||
procedure.isStatic ||
|
||||
procedure.isExtensionMember ||
|
||||
procedure.isFactory) {
|
||||
continue;
|
||||
}
|
||||
var name = procedure.name.text;
|
||||
// Add a suffix to differentiate getters and setters.
|
||||
if (procedure.isSetter) name += '=';
|
||||
dartMemberMap[name] = procedure;
|
||||
}
|
||||
for (var field in dartClass.allInstanceFields) {
|
||||
// We only care about concrete instance fields.
|
||||
if (field.isAbstract || field.isStatic) continue;
|
||||
var name = field.name.text;
|
||||
dartMemberMap[name] = field;
|
||||
if (!field.isFinal) {
|
||||
// Add the setter.
|
||||
name += '=';
|
||||
dartMemberMap[name] = field;
|
||||
}
|
||||
}
|
||||
|
||||
var conformanceError = false;
|
||||
var nameToDescriptors = <String, List<ExtensionMemberDescriptor>>{};
|
||||
var descriptorToClass = <ExtensionMemberDescriptor, Class>{};
|
||||
staticInteropClass.computeAllNonStaticExternalExtensionMembers(
|
||||
nameToDescriptors,
|
||||
descriptorToClass,
|
||||
_staticInteropClassesWithExtensions,
|
||||
_typeEnvironment);
|
||||
for (var descriptorName in nameToDescriptors.keys) {
|
||||
var descriptors = nameToDescriptors[descriptorName]!;
|
||||
// In the case of a getter/setter, we may have 2 descriptors per extension
|
||||
// with the same name, and therefore per class. So, only get one
|
||||
// descriptor per class to determine if there are conflicts.
|
||||
var visitedClasses = <Class>{};
|
||||
var descriptorConflicts = <ExtensionMemberDescriptor>{};
|
||||
for (var descriptor in descriptors) {
|
||||
if (visitedClasses.add(descriptorToClass[descriptor]!)) {
|
||||
descriptorConflicts.add(descriptor);
|
||||
}
|
||||
}
|
||||
if (descriptorConflicts.length > 1) {
|
||||
// Conflict, report an error.
|
||||
var violations = <String>[];
|
||||
for (var descriptor in descriptorConflicts) {
|
||||
var cls = descriptorToClass[descriptor]!;
|
||||
var extension = _staticInteropClassesWithExtensions[cls.reference]!;
|
||||
var extensionName =
|
||||
extension.isUnnamedExtension ? 'unnamed' : extension.name;
|
||||
violations.add("'${cls.name}.$extensionName'");
|
||||
}
|
||||
// Sort violations so error expectations can be deterministic.
|
||||
violations.sort();
|
||||
_diagnosticReporter.report(
|
||||
templateJsInteropStaticInteropMockExternalExtensionMemberConflict
|
||||
.withArguments(descriptorName, violations.join(', ')),
|
||||
node.fileOffset,
|
||||
node.name.text.length,
|
||||
node.location?.file);
|
||||
conformanceError = true;
|
||||
continue;
|
||||
}
|
||||
// With no conflicts, there should be either just 1 entry or 2 entries
|
||||
// where one is a getter and the other is a setter in the same extension
|
||||
// (and therefore the same @staticInterop class).
|
||||
assert(descriptors.length == 1 || descriptors.length == 2);
|
||||
if (descriptors.length == 2) {
|
||||
var first = descriptors[0];
|
||||
var second = descriptors[1];
|
||||
assert(descriptorToClass[first]! == descriptorToClass[second]!);
|
||||
assert((first.isGetter && second.isSetter) ||
|
||||
(first.isSetter && second.isGetter));
|
||||
}
|
||||
for (var interopDescriptor in descriptors) {
|
||||
var dartMemberName = descriptorName;
|
||||
// Distinguish getters and setters for overriding conformance.
|
||||
if (interopDescriptor.isSetter) dartMemberName += '=';
|
||||
|
||||
// Determine whether the Dart instance member with the same name as the
|
||||
// `@staticInterop` procedure is the right type of member such that it
|
||||
// can be considered an override.
|
||||
bool validOverridingMemberType() {
|
||||
var dartMember = dartMemberMap[dartMemberName]!;
|
||||
if (interopDescriptor.isGetter &&
|
||||
dartMember is! Field &&
|
||||
!(dartMember as Procedure).isGetter) {
|
||||
return false;
|
||||
} else if (interopDescriptor.isSetter &&
|
||||
dartMember is! Field &&
|
||||
!(dartMember as Procedure).isSetter) {
|
||||
return false;
|
||||
} else if (interopDescriptor.isMethod && dartMember is! Procedure) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!dartMemberMap.containsKey(dartMemberName) ||
|
||||
!validOverridingMemberType()) {
|
||||
_diagnosticReporter.report(
|
||||
templateJsInteropStaticInteropMockMissingOverride.withArguments(
|
||||
staticInteropClass.name, dartMemberName, dartClass.name),
|
||||
node.fileOffset,
|
||||
node.name.text.length,
|
||||
node.location?.file);
|
||||
conformanceError = true;
|
||||
continue;
|
||||
}
|
||||
var dartMember = dartMemberMap[dartMemberName]!;
|
||||
|
||||
// Determine if the given type of the Dart member is a valid subtype of
|
||||
// the given type of the `@staticInterop` member. If not, report an
|
||||
// error to the user.
|
||||
bool overrideIsSubtype(DartType? dartType, DartType? interopType) {
|
||||
if (dartType == null ||
|
||||
interopType == null ||
|
||||
!_typeEnvironment.isSubtypeOf(
|
||||
dartType, interopType, SubtypeCheckMode.withNullabilities)) {
|
||||
_diagnosticReporter.report(
|
||||
templateJsInteropStaticInteropMockMemberNotSubtype
|
||||
.withArguments(
|
||||
dartClass.name,
|
||||
dartMemberName,
|
||||
dartType ?? NullType(),
|
||||
staticInteropClass.name,
|
||||
dartMemberName,
|
||||
interopType ?? NullType(),
|
||||
true),
|
||||
node.fileOffset,
|
||||
node.name.text.length,
|
||||
node.location?.file);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// CFE creates static procedures for each extension member.
|
||||
var interopMember = interopDescriptor.member.node as Procedure;
|
||||
DartType getGetterFunctionType(DartType getterType) {
|
||||
return FunctionType([], getterType, Nullability.nonNullable);
|
||||
}
|
||||
|
||||
DartType getSetterFunctionType(DartType setterType) {
|
||||
return FunctionType(
|
||||
[setterType], VoidType(), Nullability.nonNullable);
|
||||
}
|
||||
|
||||
if (interopDescriptor.isGetter &&
|
||||
!overrideIsSubtype(getGetterFunctionType(dartMember.getterType),
|
||||
getGetterFunctionType(interopMember.function.returnType))) {
|
||||
conformanceError = true;
|
||||
continue;
|
||||
} else if (interopDescriptor.isSetter &&
|
||||
!overrideIsSubtype(
|
||||
getSetterFunctionType(dartMember.setterType),
|
||||
// Ignore the first argument `this` in the generated procedure.
|
||||
getSetterFunctionType(
|
||||
interopMember.function.positionalParameters[1].type))) {
|
||||
conformanceError = true;
|
||||
continue;
|
||||
} else if (interopDescriptor.isMethod) {
|
||||
var interopMemberType = interopMember.function
|
||||
.computeFunctionType(Nullability.nonNullable);
|
||||
// Ignore the first argument `this` in the generated procedure.
|
||||
interopMemberType = FunctionType(
|
||||
interopMemberType.positionalParameters.skip(1).toList(),
|
||||
interopMemberType.returnType,
|
||||
interopMemberType.declaredNullability,
|
||||
namedParameters: interopMemberType.namedParameters,
|
||||
typeParameters: interopMemberType.typeParameters,
|
||||
requiredParameterCount:
|
||||
interopMemberType.requiredParameterCount - 1);
|
||||
if (!overrideIsSubtype(
|
||||
(dartMember as Procedure)
|
||||
.function
|
||||
.computeFunctionType(Nullability.nonNullable),
|
||||
interopMemberType)) {
|
||||
conformanceError = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The interfaces do not conform and therefore we can't create a mock.
|
||||
if (conformanceError) return node;
|
||||
// TODO(srujzs): Create a mocking object.
|
||||
return super.visitStaticInvocation(node);
|
||||
}
|
||||
}
|
||||
|
||||
extension _DartClassExtension on Class {
|
||||
List<Procedure> get allInstanceProcedures {
|
||||
var allProcs = <Procedure>[];
|
||||
Class? cls = this;
|
||||
// We only care about instance procedures that have a body.
|
||||
bool isInstanceProcedure(Procedure proc) =>
|
||||
!proc.isAbstract &&
|
||||
!proc.isStatic &&
|
||||
!proc.isExtensionMember &&
|
||||
!proc.isFactory;
|
||||
while (cls != null) {
|
||||
allProcs.addAll(cls.procedures.where(isInstanceProcedure));
|
||||
// Mixin members override the given superclass' members, but are
|
||||
// overridden by the class' instance members, so they are inserted next.
|
||||
if (cls.isMixinApplication) {
|
||||
allProcs.addAll(cls.mixin.procedures.where(isInstanceProcedure));
|
||||
}
|
||||
cls = cls.superclass;
|
||||
}
|
||||
// We inserted procedures from subtype to supertypes, so reverse them so
|
||||
// that overridden members come first, with their overrides last.
|
||||
return allProcs.reversed.toList();
|
||||
}
|
||||
|
||||
List<Field> get allInstanceFields {
|
||||
var allFields = <Field>[];
|
||||
Class? cls = this;
|
||||
bool isInstanceField(Field field) => !field.isAbstract && !field.isStatic;
|
||||
while (cls != null) {
|
||||
allFields.addAll(cls.fields.where(isInstanceField));
|
||||
if (cls.isMixinApplication) {
|
||||
allFields.addAll(cls.mixin.fields.where(isInstanceField));
|
||||
}
|
||||
cls = cls.superclass;
|
||||
}
|
||||
return allFields.reversed.toList();
|
||||
}
|
||||
}
|
||||
|
||||
extension _StaticInteropClassExtension on Class {
|
||||
/// Sets [nameToDescriptors] to be a map between all the available external
|
||||
/// extension member names and the descriptors that have that name, and also
|
||||
/// sets [descriptorToClass] to be a mapping between every external extension
|
||||
/// member and its on-type.
|
||||
///
|
||||
/// [staticInteropClassesWithExtensions] is a map between all the
|
||||
/// `@staticInterop` classes and their singular extension. [typeEnvironment]
|
||||
/// is the current component's `TypeEnvironment`.
|
||||
///
|
||||
/// Note: The algorithm to determine the most-specific extension member in the
|
||||
/// event of name collisions does not conform to the specificity rules
|
||||
/// described here:
|
||||
/// https://github.com/dart-lang/language/blob/master/accepted/2.7/static-extension-methods/feature-specification.md#specificity.
|
||||
/// Instead, it only uses subtype checking of the on-types to find the most
|
||||
/// specific member. This is mostly benign as:
|
||||
/// 1. There's a single extension per @staticInterop class, so conflicts occur
|
||||
/// between classes and not within them.
|
||||
/// 2. Generics in the context of interop are by design supposed to be more
|
||||
/// rare, and external extension members are already disallowed from using
|
||||
/// type parameters. This lowers the importance of checking for instantiation
|
||||
/// to bounds.
|
||||
void computeAllNonStaticExternalExtensionMembers(
|
||||
Map<String, List<ExtensionMemberDescriptor>> nameToDescriptors,
|
||||
Map<ExtensionMemberDescriptor, Class> descriptorToClass,
|
||||
Map<Reference, Extension> staticInteropClassesWithExtensions,
|
||||
TypeEnvironment typeEnvironment) {
|
||||
assert(hasStaticInteropAnnotation(this));
|
||||
var classes = <Class>{};
|
||||
// Compute a map of all the possible descriptors available in this type and
|
||||
// the supertypes.
|
||||
void getAllDescriptors(Class cls) {
|
||||
if (classes.add(cls)) {
|
||||
if (staticInteropClassesWithExtensions.containsKey(cls.reference)) {
|
||||
for (var descriptor
|
||||
in staticInteropClassesWithExtensions[cls.reference]!.members) {
|
||||
if (!descriptor.isExternal || descriptor.isStatic) continue;
|
||||
// No need to handle external fields - they are transformed to
|
||||
// external getters/setters by the CFE.
|
||||
if (!descriptor.isGetter &&
|
||||
!descriptor.isSetter &&
|
||||
!descriptor.isMethod) {
|
||||
continue;
|
||||
}
|
||||
descriptorToClass[descriptor] = cls;
|
||||
nameToDescriptors
|
||||
.putIfAbsent(descriptor.name.text, () => [])
|
||||
.add(descriptor);
|
||||
}
|
||||
}
|
||||
cls.supers.forEach((Supertype supertype) {
|
||||
getAllDescriptors(supertype.classNode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getAllDescriptors(this);
|
||||
|
||||
InterfaceType getOnType(ExtensionMemberDescriptor desc) =>
|
||||
InterfaceType(descriptorToClass[desc]!, Nullability.nonNullable);
|
||||
|
||||
bool isStrictSubtypeOf(InterfaceType s, InterfaceType t) {
|
||||
if (s.className == t.className) return false;
|
||||
return typeEnvironment.isSubtypeOf(
|
||||
s, t, SubtypeCheckMode.withNullabilities);
|
||||
}
|
||||
|
||||
// Try and find the most specific member amongst duplicate names using
|
||||
// subtype checks.
|
||||
for (var name in nameToDescriptors.keys) {
|
||||
// The set of potential targets whose on-types are not strict subtypes of
|
||||
// any other target's on-type. As we iterate through the descriptors, this
|
||||
// invariant will hold true.
|
||||
var targets = <ExtensionMemberDescriptor>[];
|
||||
for (var descriptor in nameToDescriptors[name]!) {
|
||||
if (targets.isEmpty) {
|
||||
targets.add(descriptor);
|
||||
} else {
|
||||
var newOnType = getOnType(descriptor);
|
||||
// For each existing target, if the new descriptor's on-type is a
|
||||
// strict subtype of the target's on-type, then the new descriptor is
|
||||
// more specific. If any of the existing targets' on-types are a
|
||||
// strict subtype of the new descriptor's on-type, then the new
|
||||
// descriptor is never more specific, and therefore can be ignored.
|
||||
if (!targets.any(
|
||||
(target) => isStrictSubtypeOf(getOnType(target), newOnType))) {
|
||||
targets = [
|
||||
descriptor,
|
||||
// Not a supertype or a subtype, potential conflict or simply a
|
||||
// setter and getter.
|
||||
...targets.where(
|
||||
(target) => !isStrictSubtypeOf(newOnType, getOnType(target))),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
nameToDescriptors[name] = targets;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtensionMemberDescriptorExtension on ExtensionMemberDescriptor {
|
||||
bool get isGetter => this.kind == ExtensionMemberKind.Getter;
|
||||
bool get isSetter => this.kind == ExtensionMemberKind.Setter;
|
||||
bool get isMethod => this.kind == ExtensionMemberKind.Method;
|
||||
|
||||
bool get isExternal => (this.member.node as Procedure).isExternal;
|
||||
}
|
|
@ -8,6 +8,7 @@ environment:
|
|||
# Use 'any' constraints here; we get our versions from the DEPS file.
|
||||
dependencies:
|
||||
_fe_analyzer_shared: any
|
||||
front_end: any
|
||||
kernel: any
|
||||
|
||||
# Use 'any' constraints here; we get our versions from the DEPS file.
|
||||
|
|
|
@ -13,12 +13,14 @@ import 'package:_fe_analyzer_shared/src/messages/codes.dart'
|
|||
import 'package:_js_interop_checks/js_interop_checks.dart';
|
||||
import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart';
|
||||
import 'package:_js_interop_checks/src/transformations/static_interop_class_eraser.dart';
|
||||
import 'package:_js_interop_checks/src/transformations/static_interop_mock_creator.dart';
|
||||
import 'package:kernel/ast.dart' as ir;
|
||||
import 'package:kernel/class_hierarchy.dart';
|
||||
import 'package:kernel/core_types.dart';
|
||||
import 'package:kernel/reference_from_index.dart';
|
||||
import 'package:kernel/target/changed_structure_notifier.dart';
|
||||
import 'package:kernel/target/targets.dart';
|
||||
import 'package:kernel/type_environment.dart';
|
||||
|
||||
import '../options.dart';
|
||||
import 'invocation_mirror_constants.dart';
|
||||
|
@ -154,19 +156,28 @@ class Dart2jsTarget extends Target {
|
|||
coreTypes,
|
||||
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>,
|
||||
_nativeClasses!);
|
||||
var staticInteropMockCreator = StaticInteropMockCreator(
|
||||
TypeEnvironment(coreTypes, hierarchy), diagnosticReporter);
|
||||
var jsUtilOptimizer = JsUtilOptimizer(coreTypes, hierarchy);
|
||||
var staticInteropClassEraser =
|
||||
StaticInteropClassEraser(coreTypes, referenceFromIndex);
|
||||
// Cache extensions for entire component before creating mock.
|
||||
for (var library in libraries) {
|
||||
staticInteropMockCreator.processExtensions(library);
|
||||
}
|
||||
for (var library in libraries) {
|
||||
jsInteropChecks.visitLibrary(library);
|
||||
staticInteropMockCreator.visitLibrary(library);
|
||||
// TODO (rileyporter): Merge js_util optimizations with other lowerings
|
||||
// in the single pass in `transformations/lowering.dart`.
|
||||
jsUtilOptimizer.visitLibrary(library);
|
||||
}
|
||||
var staticInteropClassEraser =
|
||||
StaticInteropClassEraser(coreTypes, referenceFromIndex);
|
||||
lowering.transformLibraries(libraries, coreTypes, hierarchy, options);
|
||||
logger?.call("Lowering transformations performed");
|
||||
if (canPerformGlobalTransforms) {
|
||||
transformMixins.transformLibraries(libraries);
|
||||
// Do the erasure after any possible mock creation to avoid erasing types
|
||||
// that need to be used during mock conformance checking.
|
||||
for (var library in libraries) {
|
||||
staticInteropClassEraser.visitLibrary(library);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:_js_interop_checks/js_interop_checks.dart';
|
|||
import 'package:_js_interop_checks/src/js_interop.dart' as jsInteropHelper;
|
||||
import 'package:_js_interop_checks/src/transformations/js_util_wasm_optimizer.dart';
|
||||
import 'package:_js_interop_checks/src/transformations/static_interop_class_eraser.dart';
|
||||
import 'package:_js_interop_checks/src/transformations/static_interop_mock_creator.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/class_hierarchy.dart';
|
||||
import 'package:kernel/clone.dart';
|
||||
|
@ -15,6 +16,7 @@ import 'package:kernel/core_types.dart';
|
|||
import 'package:kernel/reference_from_index.dart';
|
||||
import 'package:kernel/target/changed_structure_notifier.dart';
|
||||
import 'package:kernel/target/targets.dart';
|
||||
import 'package:kernel/type_environment.dart';
|
||||
import 'package:vm/transformations/mixin_full_resolution.dart'
|
||||
as transformMixins show transformLibraries;
|
||||
import 'package:vm/transformations/ffi/common.dart' as ffiHelper
|
||||
|
@ -102,14 +104,25 @@ class WasmTarget extends Target {
|
|||
coreTypes,
|
||||
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>,
|
||||
_nativeClasses!);
|
||||
final staticInteropMockCreator = StaticInteropMockCreator(
|
||||
TypeEnvironment(coreTypes, hierarchy), diagnosticReporter);
|
||||
final jsUtilOptimizer = JsUtilWasmOptimizer(coreTypes, hierarchy);
|
||||
// Cache extensions for entire component before creating mock.
|
||||
for (Library library in interopDependentLibraries) {
|
||||
staticInteropMockCreator.processExtensions(library);
|
||||
}
|
||||
for (Library library in interopDependentLibraries) {
|
||||
jsInteropChecks.visitLibrary(library);
|
||||
staticInteropMockCreator.visitLibrary(library);
|
||||
jsUtilOptimizer.visitLibrary(library);
|
||||
}
|
||||
// Do the erasure after any possible mock creation to avoid erasing types
|
||||
// that need to be used during mock conformance checking.
|
||||
final staticInteropClassEraser = StaticInteropClassEraser(
|
||||
coreTypes, referenceFromIndex,
|
||||
libraryForJavaScriptObject: 'dart:_js_helper',
|
||||
classNameOfJavaScriptObject: 'JSValue');
|
||||
for (Library library in interopDependentLibraries) {
|
||||
jsInteropChecks.visitLibrary(library);
|
||||
jsUtilOptimizer.visitLibrary(library);
|
||||
staticInteropClassEraser.visitLibrary(library);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:_fe_analyzer_shared/src/messages/codes.dart'
|
|||
import 'package:_js_interop_checks/js_interop_checks.dart';
|
||||
import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart';
|
||||
import 'package:_js_interop_checks/src/transformations/static_interop_class_eraser.dart';
|
||||
import 'package:_js_interop_checks/src/transformations/static_interop_mock_creator.dart';
|
||||
import 'package:kernel/class_hierarchy.dart';
|
||||
import 'package:kernel/core_types.dart';
|
||||
import 'package:kernel/kernel.dart';
|
||||
|
@ -16,6 +17,7 @@ import 'package:kernel/reference_from_index.dart';
|
|||
import 'package:kernel/target/changed_structure_notifier.dart';
|
||||
import 'package:kernel/target/targets.dart';
|
||||
import 'package:kernel/transformations/track_widget_constructor_locations.dart';
|
||||
import 'package:kernel/type_environment.dart';
|
||||
|
||||
import 'constants.dart' show DevCompilerConstantsBackend;
|
||||
import 'kernel_helpers.dart';
|
||||
|
@ -168,13 +170,24 @@ class DevCompilerTarget extends Target {
|
|||
coreTypes,
|
||||
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>,
|
||||
_nativeClasses!);
|
||||
var staticInteropMockCreator = StaticInteropMockCreator(
|
||||
TypeEnvironment(coreTypes, hierarchy), diagnosticReporter);
|
||||
var jsUtilOptimizer = JsUtilOptimizer(coreTypes, hierarchy);
|
||||
var staticInteropClassEraser =
|
||||
StaticInteropClassEraser(coreTypes, referenceFromIndex);
|
||||
// Cache extensions for entire component before creating mock.
|
||||
for (var library in libraries) {
|
||||
staticInteropMockCreator.processExtensions(library);
|
||||
}
|
||||
for (var library in libraries) {
|
||||
_CovarianceTransformer(library).transform();
|
||||
jsInteropChecks.visitLibrary(library);
|
||||
staticInteropMockCreator.visitLibrary(library);
|
||||
jsUtilOptimizer.visitLibrary(library);
|
||||
}
|
||||
// Do the erasure after any possible mock creation to avoid erasing types
|
||||
// that need to be used during mock conformance checking.
|
||||
var staticInteropClassEraser =
|
||||
StaticInteropClassEraser(coreTypes, referenceFromIndex);
|
||||
for (var library in libraries) {
|
||||
staticInteropClassEraser.visitLibrary(library);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3658,6 +3658,154 @@ Message _withArgumentsInvalidReturnPartNullability(
|
|||
});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<
|
||||
Message Function(
|
||||
String name,
|
||||
String name2,
|
||||
DartType _type,
|
||||
String name3,
|
||||
String name4,
|
||||
DartType _type2,
|
||||
bool isNonNullableByDefault)>
|
||||
templateJsInteropStaticInteropMockMemberNotSubtype = const Template<
|
||||
Message Function(
|
||||
String name,
|
||||
String name2,
|
||||
DartType _type,
|
||||
String name3,
|
||||
String name4,
|
||||
DartType _type2,
|
||||
bool isNonNullableByDefault)>(
|
||||
problemMessageTemplate:
|
||||
r"""Dart class member '#name.#name2' with type '#type' is not a subtype of `@staticInterop` external extension member '#name3.#name4' with type '#type2'.""",
|
||||
correctionMessageTemplate:
|
||||
r"""Change '#name.#name2' to be a subtype of '#name3.#name4'.""",
|
||||
withArguments:
|
||||
_withArgumentsJsInteropStaticInteropMockMemberNotSubtype);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<
|
||||
Message Function(
|
||||
String name,
|
||||
String name2,
|
||||
DartType _type,
|
||||
String name3,
|
||||
String name4,
|
||||
DartType _type2,
|
||||
bool isNonNullableByDefault)>
|
||||
codeJsInteropStaticInteropMockMemberNotSubtype = const Code<
|
||||
Message Function(
|
||||
String name,
|
||||
String name2,
|
||||
DartType _type,
|
||||
String name3,
|
||||
String name4,
|
||||
DartType _type2,
|
||||
bool isNonNullableByDefault)>(
|
||||
"JsInteropStaticInteropMockMemberNotSubtype",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArgumentsJsInteropStaticInteropMockMemberNotSubtype(
|
||||
String name,
|
||||
String name2,
|
||||
DartType _type,
|
||||
String name3,
|
||||
String name4,
|
||||
DartType _type2,
|
||||
bool isNonNullableByDefault) {
|
||||
if (name.isEmpty) throw 'No name provided';
|
||||
name = demangleMixinApplicationName(name);
|
||||
if (name2.isEmpty) throw 'No name provided';
|
||||
name2 = demangleMixinApplicationName(name2);
|
||||
TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
|
||||
List<Object> typeParts = labeler.labelType(_type);
|
||||
if (name3.isEmpty) throw 'No name provided';
|
||||
name3 = demangleMixinApplicationName(name3);
|
||||
if (name4.isEmpty) throw 'No name provided';
|
||||
name4 = demangleMixinApplicationName(name4);
|
||||
List<Object> type2Parts = labeler.labelType(_type2);
|
||||
String type = typeParts.join();
|
||||
String type2 = type2Parts.join();
|
||||
return new Message(codeJsInteropStaticInteropMockMemberNotSubtype,
|
||||
problemMessage:
|
||||
"""Dart class member '${name}.${name2}' with type '${type}' is not a subtype of `@staticInterop` external extension member '${name3}.${name4}' with type '${type2}'.""" +
|
||||
labeler.originMessages,
|
||||
correctionMessage:
|
||||
"""Change '${name}.${name2}' to be a subtype of '${name3}.${name4}'.""",
|
||||
arguments: {
|
||||
'name': name,
|
||||
'name2': name2,
|
||||
'type': _type,
|
||||
'name3': name3,
|
||||
'name4': name4,
|
||||
'type2': _type2
|
||||
});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<Message Function(DartType _type, bool isNonNullableByDefault)>
|
||||
templateJsInteropStaticInteropMockNotDartInterfaceType = const Template<
|
||||
Message Function(DartType _type, bool isNonNullableByDefault)>(
|
||||
problemMessageTemplate:
|
||||
r"""Second type argument '#type' is not a Dart interface type.""",
|
||||
correctionMessageTemplate: r"""Use a Dart class instead.""",
|
||||
withArguments:
|
||||
_withArgumentsJsInteropStaticInteropMockNotDartInterfaceType);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(DartType _type, bool isNonNullableByDefault)>
|
||||
codeJsInteropStaticInteropMockNotDartInterfaceType =
|
||||
const Code<Message Function(DartType _type, bool isNonNullableByDefault)>(
|
||||
"JsInteropStaticInteropMockNotDartInterfaceType",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArgumentsJsInteropStaticInteropMockNotDartInterfaceType(
|
||||
DartType _type, bool isNonNullableByDefault) {
|
||||
TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
|
||||
List<Object> typeParts = labeler.labelType(_type);
|
||||
String type = typeParts.join();
|
||||
return new Message(codeJsInteropStaticInteropMockNotDartInterfaceType,
|
||||
problemMessage:
|
||||
"""Second type argument '${type}' is not a Dart interface type.""" +
|
||||
labeler.originMessages,
|
||||
correctionMessage: """Use a Dart class instead.""",
|
||||
arguments: {'type': _type});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<Message Function(DartType _type, bool isNonNullableByDefault)>
|
||||
templateJsInteropStaticInteropMockNotStaticInteropType = const Template<
|
||||
Message Function(DartType _type, bool isNonNullableByDefault)>(
|
||||
problemMessageTemplate:
|
||||
r"""First type argument '#type' is not a `@staticInterop` type.""",
|
||||
correctionMessageTemplate: r"""Use a `@staticInterop` class instead.""",
|
||||
withArguments:
|
||||
_withArgumentsJsInteropStaticInteropMockNotStaticInteropType);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(DartType _type, bool isNonNullableByDefault)>
|
||||
codeJsInteropStaticInteropMockNotStaticInteropType =
|
||||
const Code<Message Function(DartType _type, bool isNonNullableByDefault)>(
|
||||
"JsInteropStaticInteropMockNotStaticInteropType",
|
||||
);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArgumentsJsInteropStaticInteropMockNotStaticInteropType(
|
||||
DartType _type, bool isNonNullableByDefault) {
|
||||
TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
|
||||
List<Object> typeParts = labeler.labelType(_type);
|
||||
String type = typeParts.join();
|
||||
return new Message(codeJsInteropStaticInteropMockNotStaticInteropType,
|
||||
problemMessage:
|
||||
"""First type argument '${type}' is not a `@staticInterop` type.""" +
|
||||
labeler.originMessages,
|
||||
correctionMessage: """Use a `@staticInterop` class instead.""",
|
||||
arguments: {'type': _type});
|
||||
}
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<
|
||||
Message Function(
|
||||
|
|
|
@ -579,6 +579,20 @@ JsInteropNonExternalMember/analyzerCode: Fail # Web compiler specific
|
|||
JsInteropNonExternalMember/example: Fail # Web compiler specific
|
||||
JsInteropOperatorsNotSupported/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropOperatorsNotSupported/example: Fail # Web compiler specific
|
||||
JsInteropStaticInteropMockExternalExtensionMemberConflict/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropStaticInteropMockExternalExtensionMemberConflict/example: Fail # Web compiler specific
|
||||
JsInteropStaticInteropMockMemberNotSubtype/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropStaticInteropMockMemberNotSubtype/example: Fail # Web compiler specific
|
||||
JsInteropStaticInteropMockMissingOverride/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropStaticInteropMockMissingOverride/example: Fail # Web compiler specific
|
||||
JsInteropStaticInteropMockNotDartInterfaceType/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropStaticInteropMockNotDartInterfaceType/example: Fail # Web compiler specific
|
||||
JsInteropStaticInteropMockNotStaticInteropType/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropStaticInteropMockNotStaticInteropType/example: Fail # Web compiler specific
|
||||
JsInteropStaticInteropWithInstanceMembers/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropStaticInteropWithInstanceMembers/example: Fail # Web compiler specific
|
||||
JsInteropStaticInteropWithNonStaticSupertype/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropStaticInteropWithNonStaticSupertype/example: Fail # Web compiler specific
|
||||
JsInteropStaticInteropTrustTypesUsageNotAllowed/analyzerCode: Fail # Web compiler specific
|
||||
JsInteropStaticInteropTrustTypesUsageNotAllowed/example: Fail # Web compiler specific
|
||||
JsInteropStaticInteropTrustTypesUsedWithoutStaticInterop/analyzerCode: Fail # Web compiler specific
|
||||
|
|
|
@ -5231,6 +5231,26 @@ JsInteropOperatorsNotSupported:
|
|||
JsInteropInvalidStaticClassMemberName:
|
||||
problemMessage: "JS interop static class members cannot have '.' in their JS name."
|
||||
|
||||
JsInteropStaticInteropMockExternalExtensionMemberConflict:
|
||||
problemMessage: "External extension member with name '#name' is defined in the following extensions and none are more specific: #string."
|
||||
correctionMessage: "Try using the `@JS` annotation to rename conflicting members."
|
||||
|
||||
JsInteropStaticInteropMockMemberNotSubtype:
|
||||
problemMessage: "Dart class member '#name.#name2' with type '#type' is not a subtype of `@staticInterop` external extension member '#name3.#name4' with type '#type2'."
|
||||
correctionMessage: "Change '#name.#name2' to be a subtype of '#name3.#name4'."
|
||||
|
||||
JsInteropStaticInteropMockMissingOverride:
|
||||
problemMessage: "`@staticInterop` class '#name' has external extension member '#name2', but Dart class '#name3' does not have an overriding instance member."
|
||||
correctionMessage: "Add a Dart instance member in '#name3' that overrides '#name.#name2'."
|
||||
|
||||
JsInteropStaticInteropMockNotDartInterfaceType:
|
||||
problemMessage: "Second type argument '#type' is not a Dart interface type."
|
||||
correctionMessage: "Use a Dart class instead."
|
||||
|
||||
JsInteropStaticInteropMockNotStaticInteropType:
|
||||
problemMessage: "First type argument '#type' is not a `@staticInterop` type."
|
||||
correctionMessage: "Use a `@staticInterop` class instead."
|
||||
|
||||
JsInteropStaticInteropWithInstanceMembers:
|
||||
problemMessage: "JS interop class '#name' with `@staticInterop` annotation cannot declare instance members."
|
||||
correctionMessage: "Try moving the instance member to a static extension."
|
||||
|
|
|
@ -54,6 +54,7 @@ migrate
|
|||
n
|
||||
name.#name
|
||||
name.stack
|
||||
name3.#name
|
||||
nameokempty
|
||||
native('native
|
||||
nativefieldwrapperclass
|
||||
|
|
|
@ -159,3 +159,39 @@ external bool isJavaScriptSimpleObject(value);
|
|||
/// converts it to a Dart based object. Only JS primitives, arrays, or 'map'
|
||||
/// like JS objects are supported.
|
||||
external Object? dartify(Object? o);
|
||||
|
||||
/// DO NOT USE - THIS IS UNIMPLEMENTED.
|
||||
///
|
||||
/// Given a `@staticInterop` type T and an instance [dartMock] of a Dart class
|
||||
/// U that implements the external extension members of T, creates a forwarding
|
||||
/// mock.
|
||||
///
|
||||
/// When external extension members are called, they will forward to the
|
||||
/// corresponding implementing member in [dartMock]. If U does not implement all
|
||||
/// the external extension members of T, or if U does not properly override
|
||||
/// them, it will be considered a compile-time error.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```
|
||||
/// @JS()
|
||||
/// @staticInterop
|
||||
/// class JSClass {}
|
||||
///
|
||||
/// extension JSClassExtension on JSClass {
|
||||
/// external String stringify(int param);
|
||||
/// }
|
||||
///
|
||||
/// class DartClass {
|
||||
/// String stringify(num param) => param.toString();
|
||||
/// }
|
||||
///
|
||||
/// ...
|
||||
///
|
||||
/// JSClass mock = createStaticInteropMock<JSClass, DartClass>(DartClass());
|
||||
/// ```
|
||||
///
|
||||
/// TODO(srujzs): Add more detail on how inherited extension members need to be
|
||||
/// implemented, as well as how conflicts are resolved (if they are resolvable).
|
||||
/// The semantics here tries to conform to the view type specification.
|
||||
external T createStaticInteropMock<T, U>(U dartMock);
|
||||
|
|
Loading…
Reference in a new issue