| // Copyright (c) 2017, 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:js_shared/variance.dart'; | |
| import 'package:kernel/ast.dart' as ir; | |
| import 'package:kernel/class_hierarchy.dart' as ir; | |
| import 'package:kernel/core_types.dart' as ir; | |
| // ignore: implementation_imports | |
| import 'package:kernel/src/bounds_checks.dart' as ir; | |
| import 'package:kernel/text/debug_printer.dart'; | |
| import 'package:kernel/type_environment.dart' as ir; | |
| import '../closure.dart' show BoxLocal; | |
| import '../common.dart'; | |
| import '../common/elements.dart'; | |
| import '../common/names.dart'; | |
| import '../constants/values.dart'; | |
| import '../deferred_load/output_unit.dart' show LateOutputUnitDataBuilder; | |
| import '../elements/entities.dart'; | |
| import '../elements/entity_map.dart'; | |
| import '../elements/entity_utils.dart' as utils; | |
| import '../elements/names.dart'; | |
| import '../elements/types.dart'; | |
| import '../ir/closure.dart'; | |
| import '../ir/element_map.dart'; | |
| import '../ir/types.dart'; | |
| import '../ir/util.dart'; | |
| import '../ir/visitors.dart'; | |
| import '../js_backend/annotations.dart'; | |
| import '../js_backend/native_data.dart'; | |
| import '../kernel/dart2js_target.dart' show allowedNativeTest; | |
| import '../kernel/element_map.dart'; | |
| import '../kernel/env.dart'; | |
| import '../native/behavior.dart'; | |
| import '../options.dart'; | |
| import '../ordered_typeset.dart'; | |
| import '../serialization/serialization.dart'; | |
| import '../universe/call_structure.dart'; | |
| import '../universe/member_usage.dart'; | |
| import '../universe/record_shape.dart'; | |
| import '../universe/selector.dart'; | |
| import 'class_type_variable_access.dart'; | |
| import 'closure.dart'; | |
| import 'element_map.dart'; | |
| import 'elements.dart'; | |
| import 'env.dart'; | |
| import 'locals.dart'; | |
| import 'records.dart' | |
| show JRecordClass, RecordClassData, JRecordGetter, RecordGetterData; | |
| class JsKernelToElementMap implements JsToElementMap, IrToElementMap { | |
| /// Tag used for identifying serialized [JsKernelToElementMap] objects in a | |
| /// debugging data stream. | |
| static const String tag = 'js-kernel-to-element-map'; | |
| /// Tags used for identifying serialized subsections of a | |
| /// [JsKernelToElementMap] object in a debugging data stream. | |
| static const String libraryTag = 'libraries'; | |
| static const String classTag = 'classes'; | |
| static const String memberTag = 'members'; | |
| static const String typeVariableTag = 'type-variables'; | |
| static const String nestedClosuresTag = 'nested-closures'; | |
| final CompilerOptions options; | |
| @override | |
| final DiagnosticReporter reporter; | |
| late final JCommonElements _commonElements; | |
| late final JsElementEnvironment _elementEnvironment; | |
| late final DartTypeConverter _typeConverter; | |
| late final KernelDartTypes _types; | |
| late final ConstantValuefier _constantValuefier; | |
| /// Library environment. Used for fast lookup. | |
| late final JProgramEnv programEnv; | |
| final EntityDataEnvMap<JLibrary, JLibraryData, JLibraryEnv> libraries = | |
| EntityDataEnvMap<JLibrary, JLibraryData, JLibraryEnv>(); | |
| final EntityDataEnvMap<JClass, JClassData, JClassEnv> classes = | |
| EntityDataEnvMap<JClass, JClassData, JClassEnv>(); | |
| final EntityDataMap<JMember, JMemberData> members = | |
| EntityDataMap<JMember, JMemberData>(); | |
| final EntityDataMap<JTypeVariable, JTypeVariableData> typeVariables = | |
| EntityDataMap<JTypeVariable, JTypeVariableData>(); | |
| final Map<ir.Library, JLibrary> libraryMap = {}; | |
| final Map<ir.Class, JClass> classMap = {}; | |
| /// Map from [ir.TypeParameter] nodes to the corresponding | |
| /// [TypeVariableEntity]. | |
| /// | |
| /// Normally the type variables are [JTypeVariable]s, but for type | |
| /// parameters on local function (in the frontend) these are _not_ since | |
| /// their type declaration is neither a class nor a member. In the backend, | |
| /// these type parameters belong to the call-method and are therefore indexed. | |
| final Map<ir.TypeParameter, TypeVariableEntity> typeVariableMap = {}; | |
| final Map<ir.Member, JConstructor> constructorMap = {}; | |
| final Map<ir.Procedure, JFunction> methodMap = {}; | |
| final Map<ir.Field, JField> fieldMap = {}; | |
| final Map<ir.TreeNode, Local> localFunctionMap = {}; | |
| /// Map from members to the call methods created for their nested closures. | |
| final Map<JMember, List<JFunction>> _nestedClosureMap = {}; | |
| /// NativeData is need for computation of the default super class and | |
| /// parameter ordering. | |
| late final NativeData nativeData; | |
| final Map<JFunction, JGeneratorBody> _generatorBodies = {}; | |
| final Map<JClass, List<JMember>> _injectedClassMembers = {}; | |
| late final LateOutputUnitDataBuilder lateOutputUnitDataBuilder; | |
| final Map<JMember, JMember> kToJMembers = {}; | |
| JsKernelToElementMap( | |
| this.reporter, | |
| KernelToElementMap _elementMap, | |
| Map<MemberEntity, MemberUsage> liveMemberUsage, | |
| Iterable<MemberEntity> liveAbstractMembers, | |
| AnnotationsData annotations, | |
| ) : options = _elementMap.options { | |
| _elementEnvironment = JsElementEnvironment(this); | |
| _typeConverter = DartTypeConverter(this); | |
| _types = KernelDartTypes(this); | |
| _commonElements = JCommonElements(_types, _elementEnvironment); | |
| _constantValuefier = ConstantValuefier(this); | |
| programEnv = _elementMap.env.convert(); | |
| _elementMap.libraries.forEach(( | |
| JLibrary library, | |
| KLibraryData data, | |
| KLibraryEnv oldEnv, | |
| ) { | |
| JLibraryEnv newEnv = oldEnv.convert(_elementMap, liveMemberUsage); | |
| libraryMap[oldEnv.library] = libraries | |
| .register<JLibrary, JLibraryData, JLibraryEnv>( | |
| library, | |
| data.convert(), | |
| newEnv, | |
| ); | |
| programEnv.registerLibrary(newEnv); | |
| }); | |
| // TODO(johnniwinther): Filter unused classes. | |
| _elementMap.classes.forEach((JClass cls, KClassData data, KClassEnv env) { | |
| final library = cls.library; | |
| JClassEnv newEnv = env.convert( | |
| _elementMap, | |
| liveMemberUsage, | |
| liveAbstractMembers, | |
| (ir.Library library) => libraryMap[library]!, | |
| ); | |
| classMap[env.cls] = classes.register(cls, data.convert(), newEnv); | |
| libraries.getEnv(library).registerClass(cls.name, newEnv); | |
| }); | |
| _elementMap.members.forEach((JMember oldMember, KMemberData data) { | |
| MemberUsage? memberUsage = liveMemberUsage[oldMember]; | |
| if (memberUsage == null && !liveAbstractMembers.contains(oldMember)) { | |
| // Ensure indices are consistent across both K- and J- entity maps since | |
| // some K- maps are queried after registration in J- world maps. | |
| members.skipIndex(); | |
| return; | |
| } | |
| final library = oldMember.library; | |
| final cls = oldMember.enclosingClass; | |
| JMember? newMember; | |
| Name memberName = oldMember.memberName; | |
| // Only create a new entity if some parameters are unused and can be | |
| // elided. | |
| if (!annotations.hasNoElision(oldMember) && | |
| memberUsage != null && | |
| oldMember is JFunction && | |
| !identical( | |
| oldMember.parameterStructure, | |
| memberUsage.invokedParameters, | |
| )) { | |
| if (oldMember is ConstructorEntity) { | |
| final newParameters = memberUsage.invokedParameters!; | |
| final constructor = oldMember as ConstructorEntity; | |
| if (constructor.isFactoryConstructor) { | |
| // TODO(redemption): This should be a JFunction. | |
| newMember = JFactoryConstructor( | |
| cls!, | |
| memberName, | |
| newParameters, | |
| isExternal: constructor.isExternal, | |
| isConst: constructor.isConst, | |
| isFromEnvironmentConstructor: | |
| constructor.isFromEnvironmentConstructor, | |
| ); | |
| } else { | |
| newMember = JGenerativeConstructor( | |
| cls!, | |
| memberName, | |
| newParameters, | |
| isExternal: constructor.isExternal, | |
| isConst: constructor.isConst, | |
| ); | |
| } | |
| } else if (oldMember.isFunction && !oldMember.isAbstract) { | |
| final newParameters = memberUsage.invokedParameters!; | |
| newMember = JMethod( | |
| library, | |
| cls, | |
| memberName, | |
| newParameters, | |
| oldMember.asyncMarker, | |
| isStatic: oldMember.isStatic, | |
| isExternal: oldMember.isExternal, | |
| isAbstract: oldMember.isAbstract, | |
| ); | |
| } | |
| } | |
| members.register(newMember ?? oldMember, data.convert()); | |
| newMember ??= oldMember; | |
| kToJMembers[oldMember] = newMember; | |
| if (newMember is JField) { | |
| fieldMap[data.node as ir.Field] = newMember; | |
| } else if (newMember is ConstructorEntity) { | |
| constructorMap[data.node] = newMember as JConstructor; | |
| } else { | |
| methodMap[data.node as ir.Procedure] = newMember as JFunction; | |
| } | |
| }); | |
| _elementMap.typeVariables.forEach(( | |
| JTypeVariable oldTypeVariable, | |
| KTypeVariableData oldTypeVariableData, | |
| ) { | |
| // [JLocalTypeVariable] can have [Local] as a `typeDeclaration` but those | |
| // should never be inserted into the TypeVariable entity map. | |
| assert( | |
| oldTypeVariable.typeDeclaration is ClassEntity || | |
| oldTypeVariable.typeDeclaration is MemberEntity, | |
| ); | |
| MemberEntity? newTypeDeclaration; | |
| // TODO(johnniwinther): Skip type variables of unused classes. | |
| if (oldTypeVariable.typeDeclaration is MemberEntity) { | |
| final member = oldTypeVariable.typeDeclaration as JMember; | |
| newTypeDeclaration = kToJMembers[member]; | |
| if (newTypeDeclaration == null) { | |
| // Ensure indices are consistent across both K- and J- entity maps | |
| // since some K- maps are queried after registration in J- world maps. | |
| typeVariables.skipIndex(); | |
| return; | |
| } | |
| } | |
| JTypeVariable? newTypeVariable; | |
| if (newTypeDeclaration != null) { | |
| newTypeVariable = createTypeVariable( | |
| newTypeDeclaration, | |
| oldTypeVariable.name, | |
| oldTypeVariable.index, | |
| ); | |
| } | |
| typeVariableMap[oldTypeVariableData.node] = typeVariables | |
| .register<JTypeVariable, JTypeVariableData>( | |
| newTypeVariable ?? oldTypeVariable, | |
| oldTypeVariableData.copy(), | |
| ); | |
| }); | |
| // TODO(johnniwinther): We should close the environment in the beginning of | |
| // this constructor but currently we need the [MemberEntity] to query if the | |
| // member is live, thus potentially creating the [MemberEntity] in the | |
| // process. Avoid this. | |
| _elementMap.envIsClosed = true; | |
| } | |
| JsKernelToElementMap.readFromDataSource( | |
| this.options, | |
| this.reporter, | |
| ir.Component component, | |
| DataSourceReader source, | |
| ) { | |
| _elementEnvironment = JsElementEnvironment(this); | |
| _typeConverter = DartTypeConverter(this); | |
| _types = KernelDartTypes(this); | |
| _commonElements = JCommonElements(_types, _elementEnvironment); | |
| _constantValuefier = ConstantValuefier(this); | |
| source.registerComponentLookup(ComponentLookup(component)); | |
| programEnv = JProgramEnv([component]); | |
| source.begin(tag); | |
| source.begin(libraryTag); | |
| int libraryCount = source.readInt(); | |
| for (int i = 0; i < libraryCount; i++) { | |
| JLibrary library = source.readLibrary() as JLibrary; | |
| JLibraryData data = JLibraryData.readFromDataSource(source); | |
| JLibraryEnv env = JLibraryEnv.readFromDataSource(source); | |
| libraryMap[env.library] = libraries.register(library, data, env); | |
| programEnv.registerLibrary(env); | |
| } | |
| source.end(libraryTag); | |
| source.begin(classTag); | |
| int classCount = source.readInt(); | |
| for (int i = 0; i < classCount; i++) { | |
| JClass cls = source.readClass() as JClass; | |
| JClassData data = JClassData.readFromDataSource(source); | |
| JClassEnv env = JClassEnv.readFromDataSource(source); | |
| classes.register<JClass, JClassData, JClassEnv>(cls, data, env); | |
| if (env.cls != null) { | |
| classMap[env.cls!] = cls; | |
| } | |
| if (cls is! JContext && cls is! JClosureClass && cls is! JRecordClass) { | |
| // Synthesized classes are not part of the library environment. | |
| libraries.getEnv(cls.library).registerClass(cls.name, env); | |
| } | |
| } | |
| source.end(classTag); | |
| source.begin(memberTag); | |
| int memberCount = source.readInt(); | |
| for (int i = 0; i < memberCount; i++) { | |
| JMember member = source.readMember() as JMember; | |
| JMemberData data = JMemberData.readFromDataSource(source); | |
| members.register(member, data); | |
| switch (data.definition.kind) { | |
| case MemberKind.regular: | |
| case MemberKind.constructor: | |
| final node = data.definition.node as ir.Member; | |
| if (member is JField) { | |
| fieldMap[node as ir.Field] = member; | |
| } else if (member is ConstructorEntity) { | |
| constructorMap[node] = member as JConstructor; | |
| } else { | |
| methodMap[node as ir.Procedure] = member as JFunction; | |
| } | |
| break; | |
| case MemberKind.closureCall: | |
| case MemberKind.closureField: | |
| case MemberKind.constructorBody: | |
| case MemberKind.generatorBody: | |
| case MemberKind.recordGetter: | |
| case MemberKind.signature: | |
| case MemberKind.parameterStub: | |
| break; | |
| } | |
| } | |
| source.end(memberTag); | |
| source.begin(typeVariableTag); | |
| int typeVariableCount = source.readInt(); | |
| for (int i = 0; i < typeVariableCount; i++) { | |
| JTypeVariable typeVariable = source.readTypeVariable() as JTypeVariable; | |
| // TODO(natebiggs): Defer reading these type variables as they trigger | |
| // loading of some method bodies in the Kernel AST. | |
| JTypeVariableData data = JTypeVariableData.readFromDataSource(source); | |
| typeVariableMap[data.node] = typeVariables.register(typeVariable, data); | |
| } | |
| source.end(typeVariableTag); | |
| source.begin(nestedClosuresTag); | |
| _nestedClosureMap.addAll( | |
| source.readMemberMap( | |
| (MemberEntity member) => source.readMembers<JFunction>(), | |
| ), | |
| ); | |
| source.end(nestedClosuresTag); | |
| _symbolLibraries.addAll(source.readConstantMap(() => source.readUri())); | |
| source.end(tag); | |
| } | |
| /// Prepares the entity maps for codegen serialization by creating all lazy | |
| /// member bodies and returning a list of them for serialization on the side. | |
| List<MemberEntity> prepareForCodegenSerialization() { | |
| final lazyMemberBodies = <MemberEntity>[]; | |
| members.forEach((JMember member, JMemberData data) { | |
| if (member is JGenerativeConstructor) { | |
| lazyMemberBodies.add( | |
| getConstructorBody(data.definition.node as ir.Constructor), | |
| ); | |
| } | |
| if (member is JFunction && member.asyncMarker != AsyncMarker.sync) { | |
| lazyMemberBodies.add(getGeneratorBody(member)); | |
| } | |
| }); | |
| return lazyMemberBodies; | |
| } | |
| void registerLazyMemberBodies(List<MemberEntity> lazyMemberBodies) { | |
| for (var member in lazyMemberBodies) { | |
| if (member is JConstructorBody) { | |
| final constructor = member.constructor; | |
| final data = members.getData(constructor) as JConstructorData; | |
| final constructorBody = data.constructorBody; | |
| if (constructorBody == null) { | |
| _registerConstructorBody(constructor, data, member); | |
| } else { | |
| // The same member can be created by different codegen shards but each | |
| // should point to the same member data. | |
| members.markAsCopy(original: constructorBody, copy: member); | |
| } | |
| } else if (member is JGeneratorBody) { | |
| final function = member.function; | |
| final generatorBody = _generatorBodies[function]; | |
| if (generatorBody == null) { | |
| final data = members.getData(function) as FunctionData; | |
| _registerGeneratorBody(function, data, member); | |
| } else { | |
| // The same member can be created by different codegen shards but each | |
| // should point to the same member data. | |
| members.markAsCopy(original: generatorBody, copy: member); | |
| } | |
| } | |
| } | |
| } | |
| /// Serializes this [JsToElementMap] to [sink]. | |
| void writeToDataSink(DataSinkWriter sink) { | |
| sink.begin(tag); | |
| // Serialize the entities before serializing the data. | |
| sink.begin(libraryTag); | |
| sink.writeInt(libraries.length); | |
| libraries.forEach((JLibrary library, JLibraryData data, JLibraryEnv env) { | |
| sink.writeLibrary(library); | |
| data.writeToDataSink(sink); | |
| env.writeToDataSink(sink); | |
| }); | |
| sink.end(libraryTag); | |
| sink.begin(classTag); | |
| sink.writeInt(classes.length); | |
| classes.forEach((JClass cls, JClassData data, JClassEnv env) { | |
| sink.writeClass(cls); | |
| data.writeToDataSink(sink); | |
| env.writeToDataSink(sink); | |
| }); | |
| sink.end(classTag); | |
| sink.begin(memberTag); | |
| sink.writeInt(members.length); | |
| members.forEach((JMember member, JMemberData data) { | |
| sink.writeMember(member); | |
| data.writeToDataSink(sink); | |
| }); | |
| sink.end(memberTag); | |
| sink.begin(typeVariableTag); | |
| sink.writeInt(typeVariables.length); | |
| typeVariables.forEach((JTypeVariable typeVariable, JTypeVariableData data) { | |
| sink.writeTypeVariable(typeVariable); | |
| data.writeToDataSink(sink); | |
| }); | |
| sink.end(typeVariableTag); | |
| sink.begin(nestedClosuresTag); | |
| sink.writeMemberMap(_nestedClosureMap, (member, value) { | |
| sink.writeMembers(value); | |
| }); | |
| sink.end(nestedClosuresTag); | |
| sink.writeConstantMap(_symbolLibraries, (Uri uri) => sink.writeUri(uri)); | |
| sink.end(tag); | |
| } | |
| @override | |
| DartTypes get types => _types; | |
| @override | |
| JsElementEnvironment get elementEnvironment => _elementEnvironment; | |
| @override | |
| JCommonElements get commonElements => _commonElements; | |
| FunctionEntity? get _mainFunction { | |
| return programEnv.mainMethod != null | |
| ? getMethodInternal(programEnv.mainMethod as ir.Procedure) | |
| : null; | |
| } | |
| LibraryEntity? get _mainLibrary { | |
| return programEnv.mainMethod != null | |
| ? getLibraryInternal(programEnv.mainMethod!.enclosingLibrary) | |
| : null; | |
| } | |
| SourceSpan getSourceSpan(Spannable? spannable, Entity? currentElement) { | |
| SourceSpan fromSpannable(Spannable? spannable) { | |
| if (spannable is JLibrary) { | |
| JLibraryEnv env = libraries.getEnv(spannable); | |
| return computeSourceSpanFromTreeNode(env.library); | |
| } else if (spannable is JClass) { | |
| JClassData data = classes.getData(spannable); | |
| return data.definition.location; | |
| } else if (spannable is JMember) { | |
| JMemberData data = members.getData(spannable); | |
| return data.definition.location; | |
| } else if (spannable is JLocal) { | |
| return getSourceSpan(spannable.memberContext, currentElement); | |
| } | |
| return SourceSpan.unknown(); | |
| } | |
| SourceSpan sourceSpan = fromSpannable(spannable); | |
| if (sourceSpan.isKnown) return sourceSpan; | |
| return fromSpannable(currentElement); | |
| } | |
| LibraryEntity? lookupLibrary(Uri uri) { | |
| JLibraryEnv? libraryEnv = programEnv.lookupLibrary(uri); | |
| if (libraryEnv == null) return null; | |
| return getLibraryInternal(libraryEnv.library, libraryEnv); | |
| } | |
| String _getLibraryName(JLibrary library) { | |
| assert(checkFamily(library)); | |
| JLibraryEnv libraryEnv = libraries.getEnv(library); | |
| return libraryEnv.library.name ?? ''; | |
| } | |
| MemberEntity? lookupLibraryMember( | |
| JLibrary library, | |
| String name, { | |
| bool setter = false, | |
| }) { | |
| assert(checkFamily(library)); | |
| JLibraryEnv libraryEnv = libraries.getEnv(library); | |
| ir.Member? member = libraryEnv.lookupMember(name, setter: setter); | |
| return member != null ? getMember(member) : null; | |
| } | |
| void _forEachLibraryMember( | |
| JLibrary library, | |
| void Function(MemberEntity member) f, | |
| ) { | |
| assert(checkFamily(library)); | |
| JLibraryEnv libraryEnv = libraries.getEnv(library); | |
| libraryEnv.forEachMember((ir.Member node) { | |
| f(getMember(node)); | |
| }); | |
| } | |
| ClassEntity? lookupClass(JLibrary library, String name) { | |
| assert(checkFamily(library)); | |
| JLibraryEnv libraryEnv = libraries.getEnv(library); | |
| JClassEnv? classEnv = libraryEnv.lookupClass(name); | |
| if (classEnv != null) { | |
| return getClassInternal(classEnv.cls!, classEnv); | |
| } | |
| return null; | |
| } | |
| void _forEachClass(JLibrary library, void Function(ClassEntity cls) f) { | |
| assert(checkFamily(library)); | |
| JLibraryEnv libraryEnv = libraries.getEnv(library); | |
| libraryEnv.forEachClass((JClassEnv classEnv) { | |
| if (!classEnv.isUnnamedMixinApplication) { | |
| f(getClassInternal(classEnv.cls!, classEnv)); | |
| } | |
| }); | |
| } | |
| MemberEntity? lookupClassMember(JClass cls, Name name) { | |
| assert(checkFamily(cls)); | |
| JClassEnv classEnv = classes.getEnv(cls); | |
| return classEnv.lookupMember(this, name); | |
| } | |
| ConstructorEntity? lookupConstructor(JClass cls, String name) { | |
| assert(checkFamily(cls)); | |
| JClassEnv classEnv = classes.getEnv(cls); | |
| return classEnv.lookupConstructor(this, name); | |
| } | |
| @override | |
| InterfaceType createInterfaceType( | |
| ir.Class cls, | |
| List<ir.DartType> typeArguments, | |
| ) { | |
| return types.interfaceType(getClass(cls), getDartTypes(typeArguments)); | |
| } | |
| @override | |
| LibraryEntity getLibrary(ir.Library node) => getLibraryInternal(node); | |
| @override | |
| ClassEntity getClass(ir.Class node) => getClassInternal(node); | |
| @override | |
| InterfaceType? getSuperType(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| _ensureSupertypes(cls, data); | |
| return data.supertype; | |
| } | |
| void _ensureCallType(ClassEntity cls, JClassData data) { | |
| assert(checkFamily(cls)); | |
| if (data is JClassDataImpl && !data.isCallTypeComputed) { | |
| MemberEntity? callMember = _elementEnvironment.lookupClassMember( | |
| cls, | |
| Names.call, | |
| ); | |
| if (callMember is FunctionEntity && | |
| callMember.isFunction && | |
| !callMember.isAbstract) { | |
| data.callType = _elementEnvironment.getFunctionType(callMember); | |
| } | |
| data.isCallTypeComputed = true; | |
| } | |
| } | |
| void _ensureThisAndRawType(ClassEntity cls, JClassData data) { | |
| assert(checkFamily(cls)); | |
| if (data is JClassDataImpl && data.thisType == null) { | |
| ir.Class node = data.cls; | |
| if (node.typeParameters.isEmpty) { | |
| data.thisType = data.rawType = types.interfaceType( | |
| cls, | |
| const <DartType>[], | |
| ); | |
| } else { | |
| data.thisType = types.interfaceType( | |
| cls, | |
| List<DartType>.generate(node.typeParameters.length, (int index) { | |
| return types.typeVariableType( | |
| getTypeVariableInternal(node.typeParameters[index]), | |
| ); | |
| }), | |
| ); | |
| data.rawType = types.interfaceType( | |
| cls, | |
| List<DartType>.filled( | |
| node.typeParameters.length, | |
| types.dynamicType(), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| void _ensureJsInteropType(ClassEntity cls, JClassData data) { | |
| assert(checkFamily(cls)); | |
| if (data is JClassDataImpl && data.jsInteropType == null) { | |
| ir.Class node = data.cls; | |
| if (node.typeParameters.isEmpty) { | |
| _ensureThisAndRawType(cls, data); | |
| data.jsInteropType = data.thisType; | |
| } else { | |
| data.jsInteropType = types.interfaceType( | |
| cls, | |
| List<DartType>.filled(node.typeParameters.length, types.anyType()), | |
| ); | |
| } | |
| } | |
| } | |
| void _ensureClassInstantiationToBounds(ClassEntity cls, JClassData data) { | |
| assert(checkFamily(cls)); | |
| if (data is JClassDataImpl && data.instantiationToBounds == null) { | |
| ir.Class node = data.cls; | |
| if (node.typeParameters.isEmpty) { | |
| _ensureThisAndRawType(cls, data); | |
| data.instantiationToBounds = data.thisType; | |
| } else { | |
| data.instantiationToBounds = getInterfaceType( | |
| ir.instantiateToBounds( | |
| coreTypes.nonNullableRawType(node), | |
| coreTypes.objectClass, | |
| ) as ir.InterfaceType, | |
| ); | |
| } | |
| } | |
| } | |
| @override | |
| TypeVariableEntity getTypeVariable(ir.TypeParameter node) => | |
| getTypeVariableInternal(node); | |
| void _ensureSupertypes(ClassEntity cls, JClassData data) { | |
| assert(checkFamily(cls)); | |
| if (data is JClassDataImpl && data.orderedTypeSet == null) { | |
| _ensureThisAndRawType(cls, data); | |
| ir.Class node = data.cls; | |
| if (node.supertype == null) { | |
| data.orderedTypeSet = OrderedTypeSet.singleton(data.thisType!); | |
| data.isMixinApplication = false; | |
| data.interfaces = const <InterfaceType>[]; | |
| } else { | |
| // Set of canonical supertypes. | |
| // | |
| // This is necessary to support when a class implements the same | |
| // supertype in multiple non-conflicting ways, like implementing A<int*> | |
| // and A<int?> or B<Object?> and B<dynamic>. | |
| Set<InterfaceType> canonicalSupertypes = {}; | |
| InterfaceType processSupertype(ir.Supertype supertypeNode) { | |
| supertypeNode = classHierarchy.getClassAsInstanceOf( | |
| node, | |
| supertypeNode.classNode, | |
| )!; | |
| InterfaceType supertype = _typeConverter.visitSupertype( | |
| supertypeNode, | |
| ); | |
| canonicalSupertypes.add(supertype); | |
| final superclass = supertype.element as JClass; | |
| JClassData superdata = classes.getData(superclass); | |
| _ensureSupertypes(superclass, superdata); | |
| for (InterfaceType supertype | |
| in superdata.orderedTypeSet!.supertypes!) { | |
| ClassDefinition definition = getClassDefinition(supertype.element); | |
| if (definition.kind == ClassKind.regular) { | |
| ir.Supertype? canonicalSupertype = classHierarchy | |
| .getClassAsInstanceOf(node, definition.node as ir.Class); | |
| if (canonicalSupertype != null) { | |
| supertype = _typeConverter.visitSupertype(canonicalSupertype); | |
| } else { | |
| assert( | |
| supertype.typeArguments.isEmpty, | |
| "Generic synthetic supertypes are not supported", | |
| ); | |
| } | |
| } | |
| canonicalSupertypes.add(supertype); | |
| } | |
| return supertype; | |
| } | |
| InterfaceType supertype; | |
| List<InterfaceType> interfaces = <InterfaceType>[]; | |
| if (node.isMixinDeclaration) { | |
| // A mixin declaration | |
| // | |
| // mixin M on A, B, C {} | |
| // | |
| // is encoded by CFE as | |
| // | |
| // abstract class M extends A implements B, C {} | |
| // abstract class M extends A&B&C {} | |
| // | |
| // but we encode it as | |
| // | |
| // abstract class M extends Object implements A, B, C {} | |
| // | |
| // so we need get the superclasses from the on-clause, A, B, and C, | |
| // through [superclassConstraints]. | |
| for (ir.Supertype constraint in node.onClause) { | |
| interfaces.add(processSupertype(constraint)); | |
| } | |
| // Set superclass to `Object`. | |
| supertype = _commonElements.objectType; | |
| } else { | |
| supertype = processSupertype(node.supertype!); | |
| } | |
| if (supertype == _commonElements.objectType) { | |
| ClassEntity defaultSuperclass = _commonElements.getDefaultSuperclass( | |
| cls, | |
| nativeData, | |
| ); | |
| InterfaceType defaultSupertype = data.supertype = _elementEnvironment | |
| .getRawType(defaultSuperclass); | |
| assert( | |
| defaultSupertype.typeArguments.isEmpty, | |
| "Generic default supertypes are not supported", | |
| ); | |
| canonicalSupertypes.add(defaultSupertype); | |
| } else { | |
| data.supertype = supertype; | |
| } | |
| if (node.mixedInType != null) { | |
| data.isMixinApplication = true; | |
| interfaces.add( | |
| data.mixedInType = processSupertype(node.mixedInType!), | |
| ); | |
| } else { | |
| data.isMixinApplication = false; | |
| } | |
| for (var supertype in node.implementedTypes) { | |
| interfaces.add(processSupertype(supertype)); | |
| } | |
| OrderedTypeSetBuilder setBuilder = KernelOrderedTypeSetBuilder( | |
| this, | |
| cls, | |
| ); | |
| data.orderedTypeSet = setBuilder.createOrderedTypeSet( | |
| canonicalSupertypes, | |
| ); | |
| data.interfaces = interfaces; | |
| } | |
| } | |
| } | |
| @override | |
| MemberEntity getMember(ir.Member node) { | |
| if (node is ir.Field) { | |
| return getFieldInternal(node); | |
| } else if (node is ir.Constructor) { | |
| return getConstructorInternal(node); | |
| } else if (node is ir.Procedure) { | |
| if (node.kind == ir.ProcedureKind.Factory) { | |
| return getConstructorInternal(node); | |
| } else { | |
| return getMethodInternal(node); | |
| } | |
| } | |
| throw UnsupportedError("Unexpected member: $node"); | |
| } | |
| @override | |
| ConstructorEntity getConstructor(ir.Member node) => | |
| getConstructorInternal(node); | |
| ConstructorEntity getSuperConstructor( | |
| ir.Constructor sourceNode, | |
| ir.Member targetNode, | |
| ) { | |
| ConstructorEntity source = getConstructor(sourceNode); | |
| final sourceClass = source.enclosingClass as JClass; | |
| ConstructorEntity target = getConstructor(targetNode); | |
| ClassEntity targetClass = target.enclosingClass; | |
| JClass superClass = getSuperType(sourceClass)!.element as JClass; | |
| if (superClass == targetClass) return target; | |
| JClassEnv env = classes.getEnv(superClass); | |
| return env.lookupConstructor(this, target.name!)!; | |
| } | |
| @override | |
| FunctionEntity getMethod(ir.Procedure node) => getMethodInternal(node); | |
| @override | |
| bool containsMethod(ir.Procedure node) => methodMap.containsKey(node); | |
| @override | |
| FieldEntity getField(ir.Field node) => getFieldInternal(node); | |
| @override | |
| DartType getDartType(ir.DartType type) => _typeConverter.visitType(type); | |
| @override | |
| TypeVariableType getTypeVariableType(ir.TypeParameterType type) => | |
| getDartType(type).withoutNullability as TypeVariableType; | |
| @override | |
| List<DartType> getDartTypes(List<ir.DartType> types) { | |
| List<DartType> list = <DartType>[]; | |
| for (var type in types) { | |
| list.add(getDartType(type)); | |
| } | |
| return list; | |
| } | |
| @override | |
| InterfaceType getInterfaceType(ir.InterfaceType type) => | |
| _typeConverter.visitType(type).withoutNullability as InterfaceType; | |
| @override | |
| FunctionType getFunctionType(ir.FunctionNode node) { | |
| DartType returnType; | |
| if (node.parent is ir.Constructor) { | |
| // The return type on generative constructors is `void`, but we need | |
| // `dynamic` type to match the element model. | |
| returnType = types.dynamicType(); | |
| } else { | |
| returnType = getDartType(node.returnType); | |
| } | |
| List<DartType> parameterTypes = <DartType>[]; | |
| List<DartType> optionalParameterTypes = <DartType>[]; | |
| DartType getParameterType(ir.Variable variable) { | |
| // isCovariant implies this FunctionNode is a class Procedure. | |
| var isCovariant = | |
| variable.isCovariantByDeclaration || variable.isCovariantByClass; | |
| return types.getTearOffParameterType( | |
| getDartType(variable.type), | |
| isCovariant, | |
| ); | |
| } | |
| for (ir.PositionalParameter parameter in node.positionalParameters) { | |
| if (parameterTypes.length == node.requiredParameterCount) { | |
| optionalParameterTypes.add(getParameterType(parameter)); | |
| } else { | |
| parameterTypes.add(getParameterType(parameter)); | |
| } | |
| } | |
| var namedParameters = <String>[]; | |
| var requiredNamedParameters = <String>{}; | |
| List<DartType> namedParameterTypes = []; | |
| List<ir.NamedParameter> sortedNamedParameters = | |
| node.namedParameters.toList() | |
| ..sort((a, b) => a.parameterName.compareTo(b.parameterName)); | |
| for (ir.NamedParameter parameter in sortedNamedParameters) { | |
| namedParameters.add(parameter.parameterName); | |
| namedParameterTypes.add(getParameterType(parameter)); | |
| if (parameter.isRequired) { | |
| requiredNamedParameters.add(parameter.parameterName); | |
| } | |
| } | |
| List<FunctionTypeVariable> typeVariables; | |
| if (node.typeParameters.isNotEmpty) { | |
| List<DartType> typeParameters = <DartType>[]; | |
| for (ir.TypeParameter typeParameter in node.typeParameters) { | |
| typeParameters.add( | |
| getDartType( | |
| ir.TypeParameterType(typeParameter, ir.Nullability.nonNullable), | |
| ), | |
| ); | |
| } | |
| typeVariables = List<FunctionTypeVariable>.generate( | |
| node.typeParameters.length, | |
| (int index) => types.functionTypeVariable(index), | |
| ); | |
| DartType subst(DartType type) { | |
| return types.subst(typeVariables, typeParameters, type); | |
| } | |
| returnType = subst(returnType); | |
| parameterTypes = parameterTypes.map(subst).toList(); | |
| optionalParameterTypes = optionalParameterTypes.map(subst).toList(); | |
| namedParameterTypes = namedParameterTypes.map(subst).toList(); | |
| for (int index = 0; index < typeVariables.length; index++) { | |
| typeVariables[index].bound = subst( | |
| getDartType(node.typeParameters[index].bound), | |
| ); | |
| } | |
| } else { | |
| typeVariables = const <FunctionTypeVariable>[]; | |
| } | |
| return types.functionType( | |
| returnType, | |
| parameterTypes, | |
| optionalParameterTypes, | |
| namedParameters, | |
| requiredNamedParameters, | |
| namedParameterTypes, | |
| typeVariables, | |
| ); | |
| } | |
| @override | |
| DartType substByContext(DartType type, InterfaceType context) { | |
| return types.subst( | |
| context.typeArguments, | |
| getThisType(context.element as JClass).typeArguments, | |
| type, | |
| ); | |
| } | |
| /// Returns the type of the `call` method on 'type'. | |
| /// | |
| /// If [type] doesn't have a `call` member or has a non-method `call` member, | |
| /// `null` is returned. | |
| @override | |
| FunctionType? getCallType(InterfaceType type) { | |
| final cls = type.element as JClass; | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| _ensureCallType(cls, data); | |
| if (data.callType != null) { | |
| return substByContext(data.callType!, type) as FunctionType; | |
| } | |
| return null; | |
| } | |
| @override | |
| InterfaceType getThisType(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| _ensureThisAndRawType(cls, data); | |
| return data.thisType!; | |
| } | |
| InterfaceType _getJsInteropType(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| _ensureJsInteropType(cls, data); | |
| return data.jsInteropType!; | |
| } | |
| InterfaceType _getRawType(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| _ensureThisAndRawType(cls, data); | |
| return data.rawType!; | |
| } | |
| InterfaceType _getClassInstantiationToBounds(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| _ensureClassInstantiationToBounds(cls, data); | |
| return data.instantiationToBounds!; | |
| } | |
| FunctionType _getFunctionType(JFunction function) { | |
| assert(checkFamily(function)); | |
| final data = members.getData(function) as FunctionData; | |
| return data.getFunctionType(this); | |
| } | |
| List<TypeVariableType> _getFunctionTypeVariables(JFunction function) { | |
| assert(checkFamily(function)); | |
| final data = members.getData(function) as FunctionData; | |
| return data.getFunctionTypeVariables(this); | |
| } | |
| DartType _getFieldType(JField field) { | |
| assert(checkFamily(field)); | |
| final data = members.getData(field) as JFieldData; | |
| return data.getFieldType(this); | |
| } | |
| bool _isFieldCovariantByDeclaration(JField field) { | |
| assert(checkFamily(field)); | |
| final data = members.getData(field) as JFieldData; | |
| return data.isCovariantByDeclaration; | |
| } | |
| @override | |
| DartType getTypeVariableBound(JTypeVariable typeVariable) { | |
| assert(checkFamily(typeVariable)); | |
| JTypeVariableData data = typeVariables.getData(typeVariable); | |
| return data.getBound(this); | |
| } | |
| @override | |
| List<Variance> getTypeVariableVariances(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| return data.getVariances(); | |
| } | |
| final Map<ConstantValue, Uri> _symbolLibraries = {}; | |
| @override | |
| void registerSymbolLibrary(ConstantValue value, Uri libraryUri) { | |
| _symbolLibraries[value] = libraryUri; | |
| } | |
| @override | |
| Uri? getSymbolLibraryUri(ConstantValue value) { | |
| return _symbolLibraries[value]; | |
| } | |
| DartType _getTypeVariableDefaultType(JTypeVariable typeVariable) { | |
| assert(checkFamily(typeVariable)); | |
| JTypeVariableData data = typeVariables.getData(typeVariable); | |
| return data.getDefaultType(this); | |
| } | |
| ClassEntity? getAppliedMixin(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| _ensureSupertypes(cls, data); | |
| return data.mixedInType?.element; | |
| } | |
| bool _isMixinApplication(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| _ensureSupertypes(cls, data); | |
| return data.isMixinApplication!; | |
| } | |
| bool _isUnnamedMixinApplication(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassEnv env = classes.getEnv(cls); | |
| return env.isUnnamedMixinApplication; | |
| } | |
| bool _isMixinApplicationWithMembers(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassEnv env = classes.getEnv(cls); | |
| return env.isMixinApplicationWithMembers; | |
| } | |
| void _forEachSupertype(JClass cls, void Function(InterfaceType supertype) f) { | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| _ensureSupertypes(cls, data); | |
| data.orderedTypeSet!.supertypes!.forEach(f); | |
| } | |
| void _forEachConstructor( | |
| JClass cls, | |
| void Function(ConstructorEntity member) f, | |
| ) { | |
| assert(checkFamily(cls)); | |
| JClassEnv env = classes.getEnv(cls); | |
| env.forEachConstructor(this, f); | |
| } | |
| void _forEachLocalClassMember( | |
| JClass cls, | |
| void Function(MemberEntity member) f, | |
| ) { | |
| assert(checkFamily(cls)); | |
| JClassEnv env = classes.getEnv(cls); | |
| env.forEachMember(this, (MemberEntity member) { | |
| f(member); | |
| }); | |
| } | |
| void _forEachClassMember( | |
| JClass cls, | |
| void Function(ClassEntity cls, MemberEntity member) f, | |
| ) { | |
| assert(checkFamily(cls)); | |
| JClassEnv env = classes.getEnv(cls); | |
| env.forEachMember(this, (MemberEntity member) { | |
| f(cls, member); | |
| }); | |
| JClassData data = classes.getData(cls); | |
| _ensureSupertypes(cls, data); | |
| if (data.supertype != null) { | |
| _forEachClassMember(data.supertype!.element as JClass, f); | |
| } | |
| } | |
| @override | |
| InterfaceType? asInstanceOf(InterfaceType type, ClassEntity cls) { | |
| assert(checkFamily(cls)); | |
| OrderedTypeSet orderedTypeSet = getOrderedTypeSet(type.element as JClass); | |
| InterfaceType? supertype = orderedTypeSet.asInstanceOf( | |
| cls, | |
| getHierarchyDepth(cls as JClass), | |
| ); | |
| if (supertype != null) { | |
| supertype = substByContext(supertype, type) as InterfaceType; | |
| } | |
| return supertype; | |
| } | |
| @override | |
| OrderedTypeSet getOrderedTypeSet(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| _ensureSupertypes(cls, data); | |
| return data.orderedTypeSet!; | |
| } | |
| @override | |
| int getHierarchyDepth(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| _ensureSupertypes(cls, data); | |
| return data.orderedTypeSet!.maxDepth; | |
| } | |
| @override | |
| Iterable<InterfaceType> getInterfaces(JClass cls) { | |
| assert(checkFamily(cls)); | |
| JClassData data = classes.getData(cls); | |
| _ensureSupertypes(cls, data); | |
| return data.interfaces; | |
| } | |
| MemberDefinition getMemberDefinitionInternal(JMember member) { | |
| assert(checkFamily(member)); | |
| return members.getData(member).definition; | |
| } | |
| ClassDefinition getClassDefinitionInternal(JClass cls) { | |
| assert(checkFamily(cls)); | |
| return classes.getData(cls).definition; | |
| } | |
| @override | |
| ImportEntity getImport(ir.LibraryDependency node) { | |
| ir.Library library = node.enclosingLibrary; | |
| JLibraryData data = libraries.getData( | |
| getLibraryInternal(library) as JLibrary, | |
| ); | |
| return data.imports[node]!; | |
| } | |
| @override | |
| late final ir.CoreTypes coreTypes = ir.CoreTypes(programEnv.mainComponent); | |
| late final ir.TypeEnvironment typeEnvironment = ir.TypeEnvironment( | |
| coreTypes, | |
| classHierarchy, | |
| ); | |
| late final ir.ClassHierarchy classHierarchy = ir.ClassHierarchy( | |
| programEnv.mainComponent, | |
| coreTypes, | |
| ); | |
| @override | |
| Name getName(ir.Name name, {bool setter = false}) { | |
| return Name( | |
| name.text, | |
| name.isPrivate ? name.library!.importUri : null, | |
| isSetter: setter, | |
| ); | |
| } | |
| @override | |
| CallStructure getCallStructure(ir.Arguments arguments) { | |
| int argumentCount = arguments.positional.length + arguments.named.length; | |
| List<String> namedArguments = arguments.named.map((e) => e.name).toList(); | |
| return CallStructure(argumentCount, namedArguments, arguments.types.length); | |
| } | |
| @override | |
| Selector getSelector(ir.Expression node) { | |
| // TODO(efortuna): This is screaming for a common interface between | |
| // PropertyGet and SuperPropertyGet (and same for *Get). Talk to kernel | |
| // folks. | |
| if (node is ir.InstanceGet) { | |
| return getGetterSelector(node.name); | |
| } | |
| if (node is ir.InstanceTearOff) { | |
| return getGetterSelector(node.name); | |
| } | |
| if (node is ir.DynamicGet) { | |
| return getGetterSelector(node.name); | |
| } | |
| if (node is ir.FunctionTearOff) { | |
| return getGetterSelector(ir.Name.callName); | |
| } | |
| if (node is ir.SuperPropertyGet) { | |
| return getGetterSelector(node.name); | |
| } | |
| if (node is ir.InstanceSet) { | |
| return getSetterSelector(node.name); | |
| } | |
| if (node is ir.DynamicSet) { | |
| return getSetterSelector(node.name); | |
| } | |
| if (node is ir.SuperPropertySet) { | |
| return getSetterSelector(node.name); | |
| } | |
| if (node is ir.InvocationExpression) { | |
| return getInvocationSelector(node); | |
| } | |
| throw failedAt( | |
| currentElementSpannable, | |
| "Can only get the selector for a property get or an invocation: " | |
| "$node", | |
| ); | |
| } | |
| Selector getInvocationSelector(ir.InvocationExpression invocation) { | |
| Name name = getName(invocation.name); | |
| SelectorKind kind; | |
| if (Selector.isOperatorName(name.text)) { | |
| if (name == Names.indexName || name == Names.indexSetName) { | |
| kind = SelectorKind.index_; | |
| } else { | |
| kind = SelectorKind.operator; | |
| } | |
| } else { | |
| kind = SelectorKind.call; | |
| } | |
| CallStructure callStructure = getCallStructure(invocation.arguments); | |
| return Selector(kind, name, callStructure); | |
| } | |
| Selector getGetterSelector(ir.Name irName) { | |
| Name name = Name( | |
| irName.text, | |
| irName.isPrivate ? irName.library!.importUri : null, | |
| ); | |
| return Selector.getter(name); | |
| } | |
| Selector getSetterSelector(ir.Name irName) { | |
| Name name = Name( | |
| irName.text, | |
| irName.isPrivate ? irName.library!.importUri : null, | |
| ); | |
| return Selector.setter(name); | |
| } | |
| /// Looks up [typeName] for use in the spec-string of a `JS` call. | |
| // TODO(johnniwinther): Use this in [native.NativeBehavior] instead of calling | |
| // the `ForeignResolver`. | |
| TypeLookup typeLookup({bool resolveAsRaw = true}) { | |
| return resolveAsRaw | |
| ? (_cachedTypeLookupRaw ??= _typeLookup(resolveAsRaw: true)) | |
| : (_cachedTypeLookupFull ??= _typeLookup(resolveAsRaw: false)); | |
| } | |
| TypeLookup? _cachedTypeLookupRaw; | |
| TypeLookup? _cachedTypeLookupFull; | |
| TypeLookup _typeLookup({required bool resolveAsRaw}) { | |
| bool? cachedMayLookupInMain; | |
| DartType? lookup(String typeName, {bool required = false}) { | |
| DartType? findInLibrary(LibraryEntity? library) { | |
| if (library != null) { | |
| ClassEntity? cls = elementEnvironment.lookupClass(library, typeName); | |
| if (cls != null) { | |
| // TODO(johnniwinther): Align semantics. | |
| return resolveAsRaw | |
| ? elementEnvironment.getRawType(cls) | |
| : elementEnvironment.getThisType(cls); | |
| } | |
| } | |
| return null; | |
| } | |
| DartType? findIn(Uri uri) { | |
| return findInLibrary(elementEnvironment.lookupLibrary(uri)); | |
| } | |
| // TODO(johnniwinther): Narrow the set of lookups based on the depending | |
| // library. | |
| // TODO(johnniwinther): Cache more results to avoid redundant lookups? | |
| cachedMayLookupInMain ??= | |
| // Tests permit lookup outside of dart: libraries. | |
| allowedNativeTest(elementEnvironment.mainLibrary!.canonicalUri); | |
| DartType? type; | |
| if (cachedMayLookupInMain!) { | |
| type ??= findInLibrary(elementEnvironment.mainLibrary); | |
| } | |
| type ??= findIn(Uris.dartCore); | |
| type ??= findIn(Uris.dartJSHelper); | |
| type ??= findIn(Uris.dartLateHelper); | |
| type ??= findIn(Uris.dartInterceptors); | |
| type ??= findIn(Uris.dartNativeTypedData); | |
| type ??= findIn(Uris.dartCollection); | |
| type ??= findIn(Uris.dartMath); | |
| type ??= findIn(Uris.dartHtml); | |
| type ??= findIn(Uris.dartHtmlCommon); | |
| type ??= findIn(Uris.dartSvg); | |
| type ??= findIn(Uris.dartWebAudio); | |
| type ??= findIn(Uris.dartWebGL); | |
| type ??= findIn(Uris.dartIndexedDB); | |
| type ??= findIn(Uris.dartTypedData); | |
| type ??= findIn(Uris.dartRti); | |
| type ??= findIn(Uris.dartMirrors); | |
| if (type == null && required) { | |
| reporter.reportErrorMessage( | |
| currentElementSpannable, | |
| MessageKind.generic, | |
| {'text': "Type '$typeName' not found."}, | |
| ); | |
| } | |
| return type; | |
| } | |
| return lookup; | |
| } | |
| String? _getStringArgument(ir.StaticInvocation node, int index) { | |
| return node.arguments.positional[index].accept(Stringifier()); | |
| } | |
| // TODO(johnniwinther): Cache this for later use. | |
| @override | |
| NativeBehavior getNativeBehaviorForJsCall(ir.StaticInvocation node) { | |
| if (node.arguments.positional.length < 2 || | |
| node.arguments.named.isNotEmpty) { | |
| reporter.reportErrorMessage( | |
| currentElementSpannable, | |
| MessageKind.wrongArgumentForJS, | |
| ); | |
| return NativeBehavior(); | |
| } | |
| String? specString = _getStringArgument(node, 0); | |
| if (specString == null) { | |
| reporter.reportErrorMessage( | |
| currentElementSpannable, | |
| MessageKind.wrongArgumentForJSFirst, | |
| ); | |
| return NativeBehavior(); | |
| } | |
| String? codeString = _getStringArgument(node, 1); | |
| if (codeString == null) { | |
| reporter.reportErrorMessage( | |
| currentElementSpannable, | |
| MessageKind.wrongArgumentForJSSecond, | |
| ); | |
| return NativeBehavior(); | |
| } | |
| return NativeBehavior.ofJsCall( | |
| specString, | |
| codeString, | |
| typeLookup(resolveAsRaw: true), | |
| currentElementSpannable, | |
| reporter, | |
| commonElements, | |
| ); | |
| } | |
| // TODO(johnniwinther): Cache this for later use. | |
| @override | |
| NativeBehavior getNativeBehaviorForJsBuiltinCall(ir.StaticInvocation node) { | |
| if (node.arguments.positional.isEmpty) { | |
| reporter.internalError( | |
| currentElementSpannable, | |
| "JS builtin expression has no type.", | |
| ); | |
| } | |
| if (node.arguments.positional.length < 2) { | |
| reporter.internalError( | |
| currentElementSpannable, | |
| "JS builtin is missing name.", | |
| ); | |
| } | |
| String? specString = _getStringArgument(node, 0); | |
| if (specString == null) { | |
| reporter.internalError( | |
| currentElementSpannable, | |
| "Unexpected first argument.", | |
| ); | |
| } | |
| return NativeBehavior.ofJsBuiltinCall( | |
| specString, | |
| typeLookup(resolveAsRaw: true), | |
| currentElementSpannable, | |
| reporter, | |
| commonElements, | |
| ); | |
| } | |
| // TODO(johnniwinther): Cache this for later use. | |
| @override | |
| NativeBehavior getNativeBehaviorForJsEmbeddedGlobalCall( | |
| ir.StaticInvocation node, | |
| ) { | |
| if (node.arguments.positional.isEmpty) { | |
| reporter.internalError( | |
| currentElementSpannable, | |
| "JS embedded global expression has no type.", | |
| ); | |
| } | |
| if (node.arguments.positional.length < 2) { | |
| reporter.internalError( | |
| currentElementSpannable, | |
| "JS embedded global is missing name.", | |
| ); | |
| } | |
| if (node.arguments.positional.length > 2 || | |
| node.arguments.named.isNotEmpty) { | |
| reporter.internalError( | |
| currentElementSpannable, | |
| "JS embedded global has more than 2 arguments.", | |
| ); | |
| } | |
| String? specString = _getStringArgument(node, 0); | |
| if (specString == null) { | |
| reporter.internalError( | |
| currentElementSpannable, | |
| "Unexpected first argument.", | |
| ); | |
| } | |
| return NativeBehavior.ofJsEmbeddedGlobalCall( | |
| specString, | |
| typeLookup(resolveAsRaw: true), | |
| currentElementSpannable, | |
| reporter, | |
| commonElements, | |
| ); | |
| } | |
| @override | |
| ConstantValue? getConstantValue( | |
| ir.Expression? node, { | |
| bool requireConstant = true, | |
| bool implicitNull = false, | |
| }) { | |
| if (node == null) { | |
| if (!implicitNull) { | |
| throw failedAt(currentElementSpannable, 'No expression for constant.'); | |
| } | |
| return NullConstantValue(); | |
| } else if (node is ir.ConstantExpression) { | |
| return _constantValuefier.visitConstant(node.constant); | |
| } else if (requireConstant) { | |
| throw UnsupportedError( | |
| 'No constant for ${DebugPrinter.prettyPrint(node)}', | |
| ); | |
| } else { | |
| return null; | |
| } | |
| } | |
| @override | |
| ConstantValue getRequiredSentinelConstantValue() { | |
| return ConstructedConstantValue(_commonElements.requiredSentinelType, {}); | |
| } | |
| JTypeVariable createTypeVariable( | |
| Entity typeDeclaration, | |
| String name, | |
| int index, | |
| ) { | |
| return JTypeVariable(typeDeclaration, name, index); | |
| } | |
| JConstructorBody createConstructorBody( | |
| ConstructorEntity constructor, | |
| ParameterStructure parameterStructure, | |
| ) { | |
| return JConstructorBody(constructor as JConstructor, parameterStructure); | |
| } | |
| JGeneratorBody createGeneratorBody( | |
| FunctionEntity function, | |
| DartType elementType, | |
| ) { | |
| return JGeneratorBody(function as JFunction, elementType); | |
| } | |
| void forEachNestedClosure( | |
| MemberEntity member, | |
| void Function(FunctionEntity closure) f, | |
| ) { | |
| assert(checkFamily(member)); | |
| _nestedClosureMap[member]?.forEach(f); | |
| } | |
| @override | |
| InterfaceType? getMemberThisType(MemberEntity member) { | |
| return members.getData(member as JMember).getMemberThisType(this); | |
| } | |
| @override | |
| ClassTypeVariableAccess getClassTypeVariableAccessForMember( | |
| MemberEntity member, | |
| ) { | |
| return members.getData(member as JMember).classTypeVariableAccess; | |
| } | |
| bool checkFamily(Entity entity) { | |
| assert( | |
| '$entity'.startsWith(jsElementPrefix), | |
| failedAt( | |
| entity, | |
| "Unexpected entity $entity, expected family $jsElementPrefix.", | |
| ), | |
| ); | |
| return true; | |
| } | |
| @override | |
| Spannable getSpannable(MemberEntity member, ir.Node node) => | |
| node is ir.TreeNode | |
| ? computeSourceSpanFromTreeNode(node) | |
| : getSourceSpan(member, null); | |
| Iterable<LibraryEntity> get libraryListInternal { | |
| return libraryMap.values; | |
| } | |
| LibraryEntity getLibraryInternal(ir.Library node, [JLibraryEnv? env]) => | |
| libraryMap[node]!; | |
| ClassEntity getClassInternal(ir.Class node, [JClassEnv? env]) => | |
| classMap[node]!; | |
| FieldEntity getFieldInternal(ir.Field node) => fieldMap[node]!; | |
| FunctionEntity getMethodInternal(ir.Procedure node) => methodMap[node]!; | |
| ConstructorEntity getConstructorInternal(ir.Member node) => | |
| constructorMap[node]!; | |
| TypeVariableEntity getTypeVariableInternal(ir.TypeParameter node) { | |
| TypeVariableEntity? typeVariable = typeVariableMap[node]; | |
| if (typeVariable == null) { | |
| final declaration = node.declaration; | |
| if (declaration is ir.Procedure) { | |
| int index = declaration.typeParameters.indexOf(node); | |
| if (declaration.kind == ir.ProcedureKind.Factory) { | |
| ir.Class cls = declaration.enclosingClass!; | |
| typeVariableMap[node] = typeVariable = getTypeVariableInternal( | |
| cls.typeParameters[index], | |
| ); | |
| } | |
| } | |
| } | |
| if (typeVariable == null) { | |
| throw failedAt( | |
| currentElementSpannable, | |
| "No type variable entity for $node on " | |
| "${node.declaration}", | |
| ); | |
| } | |
| return typeVariable; | |
| } | |
| @override | |
| FunctionEntity getConstructorBody(ir.Constructor node) { | |
| ConstructorEntity constructor = getConstructor(node); | |
| return _getConstructorBody(constructor as JConstructor); | |
| } | |
| JConstructorBody _getConstructorBody(JConstructor constructor) { | |
| JConstructorData data = members.getData(constructor) as JConstructorData; | |
| JConstructorBody? constructorBody = data.constructorBody; | |
| if (constructorBody == null) { | |
| /// The constructor calls the constructor body with all parameters. | |
| // TODO(johnniwinther): Remove parameters that are not used in the | |
| // constructor body. | |
| ParameterStructure parameterStructure = | |
| _getParameterStructureFromFunctionNode(data.node.function!); | |
| constructorBody = createConstructorBody(constructor, parameterStructure); | |
| _registerConstructorBody(constructor, data, constructorBody); | |
| } | |
| return constructorBody; | |
| } | |
| void _registerConstructorBody( | |
| JConstructor constructor, | |
| JConstructorData data, | |
| JConstructorBody constructorBody, | |
| ) { | |
| members.register<JFunction, FunctionData>( | |
| constructorBody, | |
| ConstructorBodyDataImpl( | |
| data.node, | |
| data.node.function!, | |
| SpecialMemberDefinition(data.node, MemberKind.constructorBody), | |
| ), | |
| ); | |
| final cls = constructor.enclosingClass; | |
| final classEnv = classes.getEnv(cls) as JClassEnvImpl; | |
| // TODO(johnniwinther): Avoid this by only including live members in the | |
| // js-model. | |
| classEnv.addConstructorBody(constructorBody); | |
| lateOutputUnitDataBuilder.registerColocatedMembers( | |
| constructor, | |
| constructorBody, | |
| ); | |
| data.constructorBody = constructorBody; | |
| } | |
| @override | |
| MemberDefinition getMemberDefinition(MemberEntity member) { | |
| return getMemberDefinitionInternal(member as JMember); | |
| } | |
| @override | |
| ir.Member? getMemberContextNode(MemberEntity member) { | |
| ir.Member? getParentMember(ir.TreeNode? node) { | |
| while (node != null) { | |
| if (node is ir.Member) { | |
| return node; | |
| } | |
| node = node.parent; | |
| } | |
| return null; | |
| } | |
| MemberDefinition definition = getMemberDefinition(member); | |
| switch (definition.kind) { | |
| case MemberKind.regular: | |
| case MemberKind.constructor: | |
| case MemberKind.constructorBody: | |
| return definition.node as ir.Member; | |
| case MemberKind.closureCall: | |
| case MemberKind.closureField: | |
| case MemberKind.signature: | |
| case MemberKind.generatorBody: | |
| return getParentMember(definition.node as ir.TreeNode?); | |
| case MemberKind.parameterStub: | |
| case MemberKind.recordGetter: | |
| return null; | |
| } | |
| } | |
| @override | |
| ClassDefinition getClassDefinition(ClassEntity cls) { | |
| return getClassDefinitionInternal(cls as JClass); | |
| } | |
| /// Calls [f] for each parameter of [function] providing the type and name of | |
| /// the parameter and the [defaultValue] if the parameter is optional. | |
| void forEachParameter( | |
| JFunction function, | |
| void Function(DartType type, String? name, ConstantValue? defaultValue) f, { | |
| bool isNative = false, | |
| }) { | |
| final data = members.getData(function) as FunctionData; | |
| data.forEachParameter( | |
| this, | |
| function.parameterStructure, | |
| f, | |
| isNative: isNative, | |
| ); | |
| } | |
| void forEachConstructorBody( | |
| JClass cls, | |
| void Function(ConstructorBodyEntity member) f, | |
| ) { | |
| JClassEnv env = classes.getEnv(cls); | |
| env.forEachConstructorBody(f); | |
| } | |
| void forEachInjectedClassMember( | |
| JClass cls, | |
| void Function(MemberEntity member) f, | |
| ) { | |
| _injectedClassMembers[cls]?.forEach(f); | |
| } | |
| JContextField _constructContextFieldEntry( | |
| InterfaceType? memberThisType, | |
| ir.Variable variable, | |
| BoxLocal boxLocal, | |
| Map<Name, MemberEntity> memberMap, | |
| ) { | |
| JContextField boxedField = JContextField( | |
| variable.cosmeticName!, | |
| boxLocal, | |
| isConst: variable.isConst, | |
| ); | |
| members.register( | |
| boxedField, | |
| ClosureFieldData( | |
| ClosureMemberDefinition( | |
| computeSourceSpanFromTreeNode(variable), | |
| MemberKind.closureField, | |
| variable, | |
| ), | |
| memberThisType, | |
| ), | |
| ); | |
| memberMap[boxedField.memberName] = boxedField; | |
| return boxedField; | |
| } | |
| /// Make a container controlling access to contexts, that is, variables that | |
| /// are accessed in different scopes. This function creates the container | |
| /// and returns a map of locals to the corresponding records created. | |
| @override | |
| Map<ir.Variable, JContextField> makeContextContainer( | |
| KernelScopeInfo info, | |
| MemberEntity member, | |
| ) { | |
| Map<ir.Variable, JContextField> boxedFields = {}; | |
| if (info.boxedVariables.isNotEmpty) { | |
| NodeBox box = info.capturedVariablesAccessor!; | |
| Map<Name, JMember> memberMap = {}; | |
| JContext container = JContext(member.library, box.name); | |
| BoxLocal boxLocal = BoxLocal(container); | |
| InterfaceType thisType = types.interfaceType( | |
| container, | |
| const <DartType>[], | |
| ); | |
| InterfaceType supertype = commonElements.objectType; | |
| JClassData containerData = ContextClassData( | |
| ContextContainerDefinition(getMemberDefinition(member).location), | |
| thisType, | |
| supertype, | |
| getOrderedTypeSet(supertype.element as JClass) | |
| .extendClass(types, thisType), | |
| ); | |
| classes.register(container, containerData, ContextEnv(memberMap)); | |
| InterfaceType? memberThisType = member.enclosingClass != null | |
| ? elementEnvironment.getThisType(member.enclosingClass!) | |
| : null; | |
| for (ir.Variable variable in info.boxedVariables) { | |
| boxedFields[variable] = _constructContextFieldEntry( | |
| memberThisType, | |
| variable, | |
| boxLocal, | |
| memberMap, | |
| ); | |
| } | |
| } | |
| return boxedFields; | |
| } | |
| ParameterStructure _getParameterStructureFromFunctionNode( | |
| ir.FunctionNode node, | |
| ) { | |
| int requiredPositionalParameters = node.requiredParameterCount; | |
| int positionalParameters = node.positionalParameters.length; | |
| int typeParameters = node.typeParameters.length; | |
| var namedParameters = <String>[]; | |
| var requiredNamedParameters = <String>{}; | |
| for (var p | |
| in node.namedParameters.toList() | |
| ..sort((a, b) => a.parameterName.compareTo(b.parameterName))) { | |
| namedParameters.add(p.parameterName); | |
| if (p.isRequired) { | |
| requiredNamedParameters.add(p.parameterName); | |
| } | |
| } | |
| return ParameterStructure( | |
| requiredPositionalParameters, | |
| positionalParameters, | |
| namedParameters, | |
| requiredNamedParameters, | |
| typeParameters, | |
| ); | |
| } | |
| JsClosureClassInfo constructClosureClass( | |
| MemberEntity member, | |
| ir.FunctionNode node, | |
| JLibrary enclosingLibrary, | |
| Map<ir.Variable, JContextField> contextFieldsVisibleInScope, | |
| KernelScopeInfo info, | |
| InterfaceType supertype, { | |
| required bool createSignatureMethod, | |
| }) { | |
| InterfaceType? memberThisType = member.enclosingClass != null | |
| ? elementEnvironment.getThisType(member.enclosingClass!) | |
| : null; | |
| ClassTypeVariableAccess typeVariableAccess = members | |
| .getData(member as JMember) | |
| .classTypeVariableAccess; | |
| if (typeVariableAccess == ClassTypeVariableAccess.instanceField) { | |
| // A closure in a field initializer will only be executed in the | |
| // constructor and type variables are therefore accessed through | |
| // parameters. | |
| typeVariableAccess = ClassTypeVariableAccess.parameter; | |
| } | |
| String name = _computeClosureName(node); | |
| SourceSpan location = computeSourceSpanFromTreeNode(node); | |
| Map<Name, JMember> memberMap = {}; | |
| JClass classEntity = JClosureClass(enclosingLibrary, name); | |
| // Create a classData and set up the interfaces and subclass | |
| // relationships that _ensureSupertypes and _ensureThisAndRawType are doing | |
| InterfaceType thisType = types.interfaceType( | |
| classEntity, | |
| const <DartType>[], | |
| ); | |
| ClosureClassData closureData = ClosureClassData( | |
| ClosureClassDefinition(location), | |
| thisType, | |
| supertype, | |
| getOrderedTypeSet(supertype.element as JClass) | |
| .extendClass(types, thisType), | |
| ); | |
| classes.register(classEntity, closureData, ClosureClassEnv(memberMap)); | |
| Local? closureEntity; | |
| ir.Variable? closureEntityNode; | |
| if (node.parent is ir.FunctionDeclaration) { | |
| final parent = node.parent as ir.FunctionDeclaration; | |
| closureEntityNode = parent.variable; | |
| } else if (node.parent is ir.FunctionExpression) { | |
| closureEntity = AnonymousClosureLocal(classEntity as JClosureClass); | |
| } | |
| JFunction callMethod = JClosureCallMethod( | |
| classEntity, | |
| _getParameterStructureFromFunctionNode(node), | |
| getAsyncMarker(node), | |
| ); | |
| _nestedClosureMap.putIfAbsent(member, () => <JFunction>[]).add(callMethod); | |
| // We need create the type variable here - before we try to make local | |
| // variables from them (in `JsScopeInfo.from` called through | |
| // `KernelClosureClassInfo.fromScopeInfo` below). | |
| int index = 0; | |
| for (ir.TypeParameter typeParameter in node.typeParameters) { | |
| typeVariableMap[typeParameter] = typeVariables.register( | |
| createTypeVariable(callMethod, typeParameter.name!, index), | |
| JTypeVariableData(typeParameter), | |
| ); | |
| index++; | |
| } | |
| JsClosureClassInfo closureClassInfo = JsClosureClassInfo.fromScopeInfo( | |
| classEntity, | |
| node, | |
| <ir.Variable, JContextField>{}, | |
| info, | |
| member.enclosingClass, | |
| closureEntity, | |
| closureEntityNode, | |
| ); | |
| _buildClosureClassFields( | |
| closureClassInfo, | |
| member, | |
| memberThisType, | |
| info, | |
| contextFieldsVisibleInScope, | |
| memberMap, | |
| ); | |
| if (createSignatureMethod) { | |
| _constructSignatureMethod( | |
| closureClassInfo, | |
| memberMap, | |
| node, | |
| memberThisType, | |
| location, | |
| typeVariableAccess, | |
| ); | |
| } | |
| closureData.callType = getFunctionType(node); | |
| members.register<JFunction, FunctionData>( | |
| callMethod, | |
| ClosureFunctionData( | |
| ClosureMemberDefinition(location, MemberKind.closureCall, node.parent!), | |
| memberThisType, | |
| closureData.callType!, | |
| node, | |
| typeVariableAccess, | |
| ), | |
| ); | |
| memberMap[callMethod.memberName] = closureClassInfo.callMethod = callMethod; | |
| return closureClassInfo; | |
| } | |
| void _buildClosureClassFields( | |
| JsClosureClassInfo closureClassInfo, | |
| MemberEntity member, | |
| InterfaceType? memberThisType, | |
| KernelScopeInfo info, | |
| Map<ir.Variable, JContextField> contextFieldsVisibleInScope, | |
| Map<Name, MemberEntity> memberMap, | |
| ) { | |
| // TODO(efortuna): Limit field number usage to when we need to distinguish | |
| // between two variables with the same name from different scopes. | |
| int fieldNumber = 0; | |
| // For the captured variables that are boxed, ensure this closure has a | |
| // field to reference the box. This puts the boxes first in the closure like | |
| // the AST front-end, but otherwise there is no reason to separate this loop | |
| // from the one below. | |
| // TODO(redemption): Merge this loop and the following. | |
| for (ir.Node variable in info.freeVariables) { | |
| if (variable is ir.Variable) { | |
| if (contextFieldsVisibleInScope.containsKey(variable)) { | |
| bool constructedField = _constructClosureFieldForRecord( | |
| variable, | |
| closureClassInfo, | |
| memberThisType, | |
| memberMap, | |
| variable, | |
| contextFieldsVisibleInScope, | |
| fieldNumber, | |
| ); | |
| if (constructedField) fieldNumber++; | |
| } | |
| } | |
| } | |
| // Add a field for the captured 'this'. | |
| if (info.thisUsedAsFreeVariable) { | |
| closureClassInfo.registerFieldForLocal( | |
| closureClassInfo.thisLocal!, | |
| _constructClosureField( | |
| closureClassInfo.thisLocal!.name!, | |
| closureClassInfo, | |
| memberThisType, | |
| memberMap, | |
| getClassDefinition(member.enclosingClass!).node as ir.TreeNode, | |
| true, | |
| false, | |
| fieldNumber, | |
| ), | |
| ); | |
| fieldNumber++; | |
| } | |
| for (ir.Node variable in info.freeVariables) { | |
| // Make a corresponding field entity in this closure class for the | |
| // free variables in the KernelScopeInfo.freeVariable. | |
| if (variable is ir.Variable) { | |
| if (!contextFieldsVisibleInScope.containsKey(variable)) { | |
| closureClassInfo.registerFieldForVariable( | |
| variable, | |
| _constructClosureField( | |
| variable.cosmeticName!, | |
| closureClassInfo, | |
| memberThisType, | |
| memberMap, | |
| variable, | |
| variable.isConst, | |
| false, // Closure field is never assigned (only box fields). | |
| fieldNumber, | |
| ), | |
| ); | |
| fieldNumber++; | |
| } | |
| } else if (variable is TypeVariableTypeWithContext) { | |
| TypeVariableEntity typeVariable = getTypeVariable( | |
| variable.type.parameter, | |
| ); | |
| // We can have distinct TypeVariableTypeWithContexts that have the same | |
| // local variable but with different nullabilities. We only want to | |
| // construct a closure field once for each local variable. | |
| if (closureClassInfo.hasFieldForTypeVariable( | |
| typeVariable as JTypeVariable, | |
| )) { | |
| continue; | |
| } | |
| closureClassInfo.registerFieldForTypeVariable( | |
| typeVariable, | |
| _constructClosureField( | |
| variable.type.parameter.name!, | |
| closureClassInfo, | |
| memberThisType, | |
| memberMap, | |
| variable.type.parameter, | |
| true, | |
| false, | |
| fieldNumber, | |
| ), | |
| ); | |
| fieldNumber++; | |
| } else { | |
| throw UnsupportedError("Unexpected field node type: $variable"); | |
| } | |
| } | |
| } | |
| /// Contexts point to one or more local variables declared in another scope | |
| /// that are captured in a scope. Access to those variables goes entirely | |
| /// through the context container, so we only create a field for the *context* | |
| /// holding [capturedLocal] and not the individual local variables accessed | |
| /// through the context. Contexts, by definition, are not mutable (though the | |
| /// locals they contain may be). Returns `true` if we constructed a new field | |
| /// in the closure class. | |
| bool _constructClosureFieldForRecord( | |
| ir.Variable capturedLocal, | |
| JsClosureClassInfo closureClassInfo, | |
| InterfaceType? memberThisType, | |
| Map<Name, MemberEntity> memberMap, | |
| ir.TreeNode sourceNode, | |
| Map<ir.Variable, JContextField> contextFieldsVisibleInScope, | |
| int fieldNumber, | |
| ) { | |
| JContextField contextField = contextFieldsVisibleInScope[capturedLocal]!; | |
| // Don't construct a new field if the box that holds this local already has | |
| // a field in the closure class. | |
| if (closureClassInfo.hasFieldForLocal(contextField.box)) { | |
| closureClassInfo.registerFieldForBoxedVariable( | |
| capturedLocal, | |
| contextField, | |
| ); | |
| return false; | |
| } | |
| final closureField = JClosureField( | |
| '_box_$fieldNumber', | |
| closureClassInfo, | |
| contextField.box.name, | |
| isConst: true, | |
| isAssignable: false, | |
| ); | |
| members.register<JField, JFieldData>( | |
| closureField, | |
| ClosureFieldData( | |
| ClosureMemberDefinition( | |
| computeSourceSpanFromTreeNode(sourceNode), | |
| MemberKind.closureField, | |
| sourceNode, | |
| ), | |
| memberThisType, | |
| ), | |
| ); | |
| memberMap[closureField.memberName] = closureField; | |
| closureClassInfo.registerFieldForLocal(contextField.box, closureField); | |
| closureClassInfo.registerFieldForBoxedVariable(capturedLocal, contextField); | |
| return true; | |
| } | |
| void _constructSignatureMethod( | |
| JsClosureClassInfo closureClassInfo, | |
| Map<Name, MemberEntity> memberMap, | |
| ir.FunctionNode closureSourceNode, | |
| InterfaceType? memberThisType, | |
| SourceSpan location, | |
| ClassTypeVariableAccess typeVariableAccess, | |
| ) { | |
| final signatureMethod = JSignatureMethod( | |
| closureClassInfo.closureClassEntity, | |
| ); | |
| members.register<JFunction, FunctionData>( | |
| signatureMethod, | |
| SignatureFunctionData( | |
| SpecialMemberDefinition( | |
| closureSourceNode.parent!, | |
| MemberKind.signature, | |
| ), | |
| memberThisType, | |
| closureSourceNode.typeParameters, | |
| typeVariableAccess, | |
| ), | |
| ); | |
| memberMap[signatureMethod.memberName] = closureClassInfo.signatureMethod = | |
| signatureMethod; | |
| } | |
| JField _constructClosureField( | |
| String name, | |
| JsClosureClassInfo closureClassInfo, | |
| InterfaceType? memberThisType, | |
| Map<Name, MemberEntity> memberMap, | |
| ir.TreeNode sourceNode, | |
| bool isConst, | |
| bool isAssignable, | |
| int fieldNumber, | |
| ) { | |
| JField closureField = JClosureField( | |
| _getClosureVariableName(name, fieldNumber), | |
| closureClassInfo, | |
| name, | |
| isConst: isConst, | |
| isAssignable: isAssignable, | |
| ); | |
| members.register<JField, JFieldData>( | |
| closureField, | |
| ClosureFieldData( | |
| ClosureMemberDefinition( | |
| computeSourceSpanFromTreeNode(sourceNode), | |
| MemberKind.closureField, | |
| sourceNode, | |
| ), | |
| memberThisType, | |
| ), | |
| ); | |
| memberMap[closureField.memberName] = closureField; | |
| return closureField; | |
| } | |
| // Returns a non-unique name for the given closure element. | |
| String _computeClosureName(ir.TreeNode treeNode) { | |
| var parts = <String>[]; | |
| // First anonymous is called 'closure', outer ones called '' to give a | |
| // compound name where increasing nesting level corresponds to extra | |
| // underscores. | |
| var anonymous = 'closure'; | |
| ir.TreeNode? current = treeNode; | |
| // TODO(johnniwinther): Simplify computed names. | |
| while (current != null) { | |
| var node = current; | |
| if (node is ir.FunctionExpression) { | |
| parts.add(anonymous); | |
| anonymous = ''; | |
| } else if (node is ir.FunctionDeclaration) { | |
| String name = node.variable.name; | |
| if (name != "") { | |
| parts.add(utils.operatorNameToIdentifier(name)!); | |
| } else { | |
| parts.add(anonymous); | |
| anonymous = ''; | |
| } | |
| } else if (node is ir.Class) { | |
| // TODO(sra): Do something with abstracted mixin type names like '^#U0'. | |
| parts.add(node.name); | |
| break; | |
| } else if (node is ir.Procedure) { | |
| if (node.kind == ir.ProcedureKind.Factory) { | |
| parts.add( | |
| utils.reconstructConstructorName(getMember(node) as FunctionEntity), | |
| ); | |
| } else { | |
| parts.add(utils.operatorNameToIdentifier(node.name.text)!); | |
| } | |
| } else if (node is ir.Constructor) { | |
| parts.add( | |
| utils.reconstructConstructorName(getMember(node) as FunctionEntity), | |
| ); | |
| break; | |
| } else if (node is ir.Field) { | |
| // Add the field name for closures in field initializers. | |
| parts.add(node.name.text); | |
| } | |
| current = current.parent; | |
| } | |
| return parts.reversed.join('_'); | |
| } | |
| /// Generate a unique name for the [id]th closure field, with proposed name | |
| /// [name]. | |
| /// | |
| /// The result is used as the name of [ClosureFieldElement]s, and must | |
| /// therefore be unique to avoid breaking an invariant in the element model | |
| /// (classes cannot declare multiple fields with the same name). | |
| /// | |
| /// Also, the names should be distinct from real field names to prevent | |
| /// clashes with selectors for those fields. | |
| /// | |
| /// These names are not used in generated code, just as element name. | |
| String _getClosureVariableName(String name, int id) { | |
| return "_captured_${name}_$id"; | |
| } | |
| JParameterStub? createParameterStub( | |
| JFunction function, | |
| Selector selector, { | |
| required Selector? callSelector, | |
| required bool needsSuper, | |
| }) { | |
| CallStructure callStructure = selector.callStructure; | |
| ParameterStructure parameterStructure = function.parameterStructure; | |
| int positionalArgumentCount = callStructure.positionalArgumentCount; | |
| assert( | |
| callStructure.typeArgumentCount == 0 || | |
| callStructure.typeArgumentCount == parameterStructure.typeParameters, | |
| ); | |
| assert( | |
| callStructure.typeArgumentCount != parameterStructure.typeParameters || | |
| positionalArgumentCount != parameterStructure.totalParameters || | |
| (parameterStructure.namedParameters.isNotEmpty && | |
| callStructure.namedArgumentCount == | |
| parameterStructure.namedParameters.length), | |
| ); | |
| final newParameterStructure = ParameterStructure( | |
| callStructure.positionalArgumentCount, | |
| callStructure.positionalArgumentCount, | |
| callStructure.namedArguments, | |
| callStructure.namedArguments.toSet(), | |
| callStructure.typeArgumentCount, | |
| ); | |
| final stub = JParameterStub( | |
| function, | |
| newParameterStructure, | |
| callSelector: callSelector, | |
| needsSuper: needsSuper, | |
| ); | |
| final data = members.getData(function) as FunctionData; | |
| final definition = data.definition; | |
| switch (definition.kind) { | |
| case MemberKind.regular: | |
| case MemberKind.closureCall: | |
| final node = definition.node; | |
| final stubDefinition = SpecialMemberDefinition( | |
| node as ir.TreeNode, | |
| MemberKind.parameterStub, | |
| ); | |
| members.register(stub, ParameterStubFunctionData(data, stubDefinition)); | |
| lateOutputUnitDataBuilder.registerColocatedMembers(function, stub); | |
| // We intentionally avoid registering the stub on a class here. We don't | |
| // want the emitter to emit code for the stub as if it were a normal | |
| // member. Code will be emitted alongside the target member. | |
| break; | |
| case MemberKind.signature: | |
| case MemberKind.recordGetter: | |
| case MemberKind.closureField: | |
| case MemberKind.constructor: | |
| case MemberKind.constructorBody: | |
| case MemberKind.generatorBody: | |
| case MemberKind.parameterStub: | |
| throw UnsupportedError( | |
| 'Cannot generate stub for $function with kind ${definition.kind}', | |
| ); | |
| } | |
| return stub; | |
| } | |
| @override | |
| JGeneratorBody getGeneratorBody(covariant JFunction function) { | |
| JGeneratorBody? generatorBody = _generatorBodies[function]; | |
| if (generatorBody == null) { | |
| final functionData = members.getData(function) as FunctionData; | |
| DartType elementType = elementEnvironment | |
| .getFunctionAsyncOrSyncStarElementType(function); | |
| generatorBody = createGeneratorBody(function, elementType); | |
| _registerGeneratorBody(function, functionData, generatorBody); | |
| } | |
| return generatorBody; | |
| } | |
| void _registerGeneratorBody( | |
| JFunction function, | |
| FunctionData functionData, | |
| JGeneratorBody generatorBody, | |
| ) { | |
| members.register<JFunction, FunctionData>( | |
| generatorBody, | |
| GeneratorBodyFunctionData( | |
| functionData, | |
| SpecialMemberDefinition.from( | |
| functionData.definition, | |
| MemberKind.generatorBody, | |
| ), | |
| ), | |
| ); | |
| if (function.enclosingClass != null) { | |
| // TODO(sra): Integrate this with ClassEnvImpl.addConstructorBody ? | |
| (_injectedClassMembers[function.enclosingClass as JClass] ??= <JMember>[]) | |
| .add(generatorBody); | |
| } | |
| lateOutputUnitDataBuilder.registerColocatedMembers( | |
| generatorBody.function, | |
| generatorBody, | |
| ); | |
| _generatorBodies[function] = generatorBody; | |
| } | |
| String _nameForShape(RecordShape shape) { | |
| final sb = StringBuffer(); | |
| sb.write('_Record_'); | |
| sb.write(shape.fieldCount); | |
| for (String name in shape.fieldNames) { | |
| sb.write('_'); | |
| // 'hex' escape to remove `$` and `_`. | |
| // `send_$_bux` --> `sendx5Fx24x5Fbux78`. | |
| sb.write( | |
| name | |
| .replaceAll(r'x', r'x78') | |
| .replaceAll(r'$', r'x24') | |
| .replaceAll(r'_', r'x5F'), | |
| ); | |
| } | |
| return sb.toString(); | |
| } | |
| /// [getters] is an out parameter that gathers all the getters created for | |
| /// this shape. | |
| JClass generateRecordShapeClass( | |
| RecordShape shape, | |
| InterfaceType supertype, | |
| List<MemberEntity> getters, | |
| ) { | |
| JLibrary library = supertype.element.library as JLibrary; | |
| String name = _nameForShape(shape); | |
| SourceSpan location = SourceSpan.unknown(); // TODO(50081): What to use? | |
| Map<Name, JMember> memberMap = {}; | |
| final classEntity = JRecordClass(library, name, isAbstract: false); | |
| // Create a classData and set up the interfaces and subclass relationships | |
| // that for regular classes would be done by _ensureSupertypes and | |
| // _ensureThisAndRawType. | |
| InterfaceType thisType = types.interfaceType(classEntity, const []); | |
| RecordClassData recordData = RecordClassData( | |
| RecordClassDefinition(location), | |
| thisType, | |
| supertype, | |
| getOrderedTypeSet(supertype.element as JClass) | |
| .extendClass(types, thisType), | |
| ); | |
| classes.register(classEntity, recordData, RecordClassEnv(memberMap)); | |
| // Add field getters, which are called only from dynamic getter invocations. | |
| for (int i = 0; i < shape.fieldCount; i++) { | |
| String name = shape.getterNameOfIndex(i); | |
| Name memberName = Name(name, null); | |
| final getter = JRecordGetter(classEntity, memberName); | |
| getters.add(getter); | |
| // The function type of a dynamic getter is a function of no arguments | |
| // that returns `dynamic` (any other top would be ok too). | |
| FunctionType functionType = commonElements.dartTypes.functionType( | |
| commonElements.dartTypes.dynamicType(), | |
| const [], | |
| const [], | |
| const [], | |
| const {}, | |
| const [], | |
| const [], | |
| ); | |
| final data = RecordGetterData( | |
| RecordGetterDefinition(location, i), | |
| thisType, | |
| functionType, | |
| ); | |
| members.register<JFunction, FunctionData>(getter, data); | |
| memberMap[memberName] = getter; | |
| } | |
| // TODO(49718): Implement `==` specialized to the shape. | |
| return classEntity; | |
| } | |
| } | |
| class JsElementEnvironment extends ElementEnvironment | |
| implements JElementEnvironment { | |
| @override | |
| final JsKernelToElementMap elementMap; | |
| JsElementEnvironment(this.elementMap); | |
| @override | |
| DartType get dynamicType => elementMap.types.dynamicType(); | |
| @override | |
| LibraryEntity? get mainLibrary => elementMap._mainLibrary; | |
| @override | |
| FunctionEntity? get mainFunction => elementMap._mainFunction; | |
| @override | |
| Iterable<LibraryEntity> get libraries => elementMap.libraryListInternal; | |
| @override | |
| String getLibraryName(LibraryEntity library) { | |
| return elementMap._getLibraryName(library as JLibrary); | |
| } | |
| @override | |
| InterfaceType getThisType(ClassEntity cls) { | |
| return elementMap.getThisType(cls as JClass); | |
| } | |
| @override | |
| InterfaceType getJsInteropType(ClassEntity cls) { | |
| return elementMap._getJsInteropType(cls as JClass); | |
| } | |
| @override | |
| InterfaceType getRawType(ClassEntity cls) { | |
| return elementMap._getRawType(cls as JClass); | |
| } | |
| @override | |
| InterfaceType getClassInstantiationToBounds(ClassEntity cls) => | |
| elementMap._getClassInstantiationToBounds(cls as JClass); | |
| @override | |
| bool isGenericClass(ClassEntity cls) { | |
| return getThisType(cls).typeArguments.isNotEmpty; | |
| } | |
| @override | |
| bool isMixinApplication(ClassEntity cls) { | |
| return elementMap._isMixinApplication(cls as JClass); | |
| } | |
| @override | |
| bool isUnnamedMixinApplication(ClassEntity cls) { | |
| return elementMap._isUnnamedMixinApplication(cls as JClass); | |
| } | |
| @override | |
| bool isMixinApplicationWithMembers(ClassEntity cls) { | |
| return elementMap._isMixinApplicationWithMembers(cls as JClass); | |
| } | |
| @override | |
| ClassEntity? getEffectiveMixinClass(ClassEntity cls) { | |
| if (!isMixinApplication(cls)) return null; | |
| do { | |
| cls = elementMap.getAppliedMixin(cls as JClass)!; | |
| } while (isMixinApplication(cls)); | |
| return cls; | |
| } | |
| @override | |
| DartType getTypeVariableBound(TypeVariableEntity typeVariable) { | |
| return elementMap.getTypeVariableBound(typeVariable as JTypeVariable); | |
| } | |
| @override | |
| List<Variance> getTypeVariableVariances(ClassEntity cls) { | |
| return elementMap.getTypeVariableVariances(cls as JClass); | |
| } | |
| @override | |
| DartType getTypeVariableDefaultType(TypeVariableEntity typeVariable) { | |
| return elementMap._getTypeVariableDefaultType( | |
| typeVariable as JTypeVariable, | |
| ); | |
| } | |
| @override | |
| InterfaceType createInterfaceType( | |
| ClassEntity cls, | |
| List<DartType> typeArguments, | |
| ) { | |
| return elementMap.types.interfaceType(cls, typeArguments); | |
| } | |
| @override | |
| FunctionType getFunctionType(FunctionEntity function) { | |
| return elementMap._getFunctionType(function as JFunction); | |
| } | |
| @override | |
| List<TypeVariableType> getFunctionTypeVariables(FunctionEntity function) { | |
| return elementMap._getFunctionTypeVariables(function as JFunction); | |
| } | |
| @override | |
| DartType getFunctionAsyncOrSyncStarElementType(FunctionEntity function) { | |
| DartType returnType = getFunctionType(function).returnType; | |
| return getAsyncOrSyncStarElementType(function, returnType); | |
| } | |
| @override | |
| DartType getAsyncOrSyncStarElementType( | |
| FunctionEntity function, | |
| DartType returnType, | |
| ) { | |
| final asyncMarker = function.asyncMarker; | |
| switch (asyncMarker) { | |
| case AsyncMarker.sync: | |
| return returnType; | |
| case AsyncMarker.syncStar: | |
| case AsyncMarker.async: | |
| case AsyncMarker.asyncStar: | |
| return elementMap.getDartType( | |
| getFunctionNode(elementMap, function)!.emittedValueType!, | |
| ); | |
| } | |
| } | |
| @override | |
| DartType getFieldType(FieldEntity field) { | |
| return elementMap._getFieldType(field as JField); | |
| } | |
| @override | |
| bool isFieldCovariantByDeclaration(FieldEntity field) { | |
| return elementMap._isFieldCovariantByDeclaration(field as JField); | |
| } | |
| @override | |
| FunctionType getLocalFunctionType(covariant JLocalFunction function) { | |
| return function.functionType; | |
| } | |
| @override | |
| ConstructorEntity? lookupConstructor( | |
| ClassEntity cls, | |
| String name, { | |
| bool required = false, | |
| }) { | |
| ConstructorEntity? constructor = elementMap.lookupConstructor( | |
| cls as JClass, | |
| name, | |
| ); | |
| if (constructor == null && required) { | |
| throw failedAt( | |
| currentElementSpannable, | |
| "The constructor '$name' was not found in class '${cls.name}' " | |
| "in library ${cls.library.canonicalUri}.", | |
| ); | |
| } | |
| return constructor; | |
| } | |
| @override | |
| MemberEntity? lookupLocalClassMember( | |
| ClassEntity cls, | |
| Name name, { | |
| bool required = false, | |
| }) { | |
| MemberEntity? member = elementMap.lookupClassMember(cls as JClass, name); | |
| if (member == null && required) { | |
| throw failedAt( | |
| currentElementSpannable, | |
| "The member '$name' was not found in ${cls.name}.", | |
| ); | |
| } | |
| return member; | |
| } | |
| @override | |
| ClassEntity? getSuperClass( | |
| ClassEntity cls, { | |
| bool skipUnnamedMixinApplications = false, | |
| }) { | |
| assert(elementMap.checkFamily(cls)); | |
| JClass? superclass = | |
| elementMap.getSuperType(cls as JClass)?.element as JClass?; | |
| if (skipUnnamedMixinApplications) { | |
| while (superclass != null && | |
| elementMap._isUnnamedMixinApplication(superclass)) { | |
| superclass = elementMap.getSuperType(superclass)?.element as JClass?; | |
| } | |
| } | |
| return superclass; | |
| } | |
| @override | |
| void forEachSupertype( | |
| ClassEntity cls, | |
| void Function(InterfaceType supertype) f, | |
| ) { | |
| elementMap._forEachSupertype(cls as JClass, f); | |
| } | |
| @override | |
| void forEachLocalClassMember( | |
| ClassEntity cls, | |
| void Function(MemberEntity member) f, | |
| ) { | |
| elementMap._forEachLocalClassMember(cls as JClass, f); | |
| } | |
| @override | |
| void forEachInjectedClassMember( | |
| ClassEntity cls, | |
| void Function(MemberEntity member) f, | |
| ) { | |
| elementMap.forEachInjectedClassMember(cls as JClass, f); | |
| } | |
| @override | |
| void forEachClassMember( | |
| ClassEntity cls, | |
| void Function(ClassEntity declarer, MemberEntity member) f, | |
| ) { | |
| elementMap._forEachClassMember(cls as JClass, f); | |
| } | |
| @override | |
| void forEachConstructor( | |
| ClassEntity cls, | |
| void Function(ConstructorEntity constructor) f, | |
| ) { | |
| elementMap._forEachConstructor(cls as JClass, f); | |
| } | |
| @override | |
| void forEachConstructorBody( | |
| ClassEntity cls, | |
| void Function(ConstructorBodyEntity constructor) f, | |
| ) { | |
| elementMap.forEachConstructorBody(cls as JClass, f); | |
| } | |
| @override | |
| void forEachNestedClosure( | |
| MemberEntity member, | |
| void Function(FunctionEntity closure) f, | |
| ) { | |
| elementMap.forEachNestedClosure(member, f); | |
| } | |
| @override | |
| void forEachLibraryMember( | |
| LibraryEntity library, | |
| void Function(MemberEntity member) f, | |
| ) { | |
| elementMap._forEachLibraryMember(library as JLibrary, f); | |
| } | |
| @override | |
| MemberEntity? lookupLibraryMember( | |
| LibraryEntity library, | |
| String name, { | |
| bool setter = false, | |
| bool required = false, | |
| }) { | |
| MemberEntity? member = elementMap.lookupLibraryMember( | |
| library as JLibrary, | |
| name, | |
| setter: setter, | |
| ); | |
| if (member == null && required) { | |
| failedAt( | |
| currentElementSpannable, | |
| "The member '$name' was not found in library '${library.name}'.", | |
| ); | |
| } | |
| return member; | |
| } | |
| @override | |
| ClassEntity? lookupClass( | |
| LibraryEntity library, | |
| String name, { | |
| bool required = false, | |
| }) { | |
| ClassEntity? cls = elementMap.lookupClass(library as JLibrary, name); | |
| if (cls == null && required) { | |
| failedAt( | |
| currentElementSpannable, | |
| "The class '$name' was not found in library '${library.name}'.", | |
| ); | |
| } | |
| return cls; | |
| } | |
| @override | |
| void forEachClass(LibraryEntity library, void Function(ClassEntity cls) f) { | |
| elementMap._forEachClass(library as JLibrary, f); | |
| } | |
| @override | |
| LibraryEntity? lookupLibrary(Uri uri, {bool required = false}) { | |
| LibraryEntity? library = elementMap.lookupLibrary(uri); | |
| if (library == null && required) { | |
| failedAt(currentElementSpannable, "The library '$uri' was not found."); | |
| } | |
| return library; | |
| } | |
| @override | |
| bool isEnumClass(ClassEntity cls) { | |
| assert(elementMap.checkFamily(cls)); | |
| JClassData classData = elementMap.classes.getData(cls as JClass); | |
| return classData.isEnumClass; | |
| } | |
| @override | |
| void forEachParameter( | |
| FunctionEntity function, | |
| void Function(DartType type, String? name, ConstantValue? defaultValue) f, | |
| ) { | |
| elementMap.forEachParameter( | |
| function as JFunction, | |
| f, | |
| isNative: elementMap.nativeData.isNativeMember(function), | |
| ); | |
| } | |
| @override | |
| void forEachParameterAsLocal( | |
| GlobalLocalsMap globalLocalsMap, | |
| FunctionEntity function, | |
| void Function(Local parameter) f, | |
| ) { | |
| forEachOrderedParameterAsLocal(globalLocalsMap, elementMap, function, ( | |
| Local parameter, { | |
| required bool isElided, | |
| }) { | |
| if (!isElided) { | |
| f(parameter); | |
| } | |
| }); | |
| } | |
| @override | |
| void forEachInstanceField( | |
| ClassEntity cls, | |
| void Function(ClassEntity declarer, FieldEntity field) f, | |
| ) { | |
| forEachClassMember(cls, (ClassEntity declarer, MemberEntity member) { | |
| if (member is FieldEntity && member.isInstanceMember) { | |
| f(declarer, member); | |
| } | |
| }); | |
| } | |
| @override | |
| void forEachDirectInstanceField( | |
| ClassEntity cls, | |
| void Function(FieldEntity field) f, | |
| ) { | |
| // TODO(sra): Add ElementEnvironment.forEachDirectInstanceField or | |
| // parameterize [forEachInstanceField] to filter members to avoid a | |
| // potentially O(n^2) scan of the superclasses. | |
| forEachClassMember(cls, (ClassEntity declarer, MemberEntity member) { | |
| if (declarer != cls) return; | |
| if (member is! FieldEntity) return; | |
| if (!member.isInstanceMember) return; | |
| f(member); | |
| }); | |
| } | |
| } |