[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:
Srujan Gaddam 2022-09-16 20:13:30 +00:00 committed by Commit Bot
parent 8ebb7d70e5
commit 8a081b95c6
11 changed files with 768 additions and 6 deletions

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -54,6 +54,7 @@ migrate
n
name.#name
name.stack
name3.#name
nameokempty
native('native
nativefieldwrapperclass

View file

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