| // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'package:js_shared/variance.dart'; | |
| import 'package:kernel/ast.dart' as ir; | |
| import '../common.dart'; | |
| import '../elements/entities.dart' show AsyncMarker, MemberEntity; | |
| import '../universe/record_shape.dart'; | |
| /// Returns a textual representation of [node] that include the runtime type and | |
| /// hash code of the node and a one line prefix of the node toString text. | |
| String nodeToDebugString(ir.Node node, [int textLength = 40]) { | |
| String blockText = node.toString().replaceAll('\n', ' '); | |
| if (blockText.length > textLength) { | |
| blockText = '${blockText.substring(0, textLength - 3)}...'; | |
| } | |
| return '(${node.runtimeType}:${node.hashCode})$blockText'; | |
| } | |
| /// Comparator for the canonical order for named parameters. | |
| // TODO(johnniwinther): Remove this when named parameters are sorted in dill. | |
| int namedOrdering(ir.NamedParameter a, ir.NamedParameter b) { | |
| return a.parameterName.compareTo(b.parameterName); | |
| } | |
| /// Comparator for the declaration order of parameters. | |
| int nativeOrdering(ir.Variable a, ir.Variable b) { | |
| return a.fileOffset.compareTo(b.fileOffset); | |
| } | |
| SourceSpan computeSourceSpanFromTreeNode(ir.TreeNode node) { | |
| // TODO(johnniwinther): Use [ir.Location] directly as a [SourceSpan]. | |
| Uri? uri; | |
| late int offset; | |
| for (ir.TreeNode? current = node; current != null; current = current.parent) { | |
| if (current.fileOffset != ir.TreeNode.noOffset) { | |
| offset = current.fileOffset; | |
| // @patch annotations have no location. | |
| uri = current.location?.file; | |
| break; | |
| } | |
| } | |
| if (uri != null) { | |
| return SourceSpan(uri, offset, offset + 1); | |
| } | |
| return SourceSpan.unknown(); | |
| } | |
| RecordShape recordShapeOfRecordType(ir.RecordType node) { | |
| return RecordShape( | |
| node.positional.length, | |
| node.named.isEmpty | |
| ? const [] | |
| : node.named.map((n) => n.name).toList(growable: false), | |
| ); | |
| } | |
| /// Computes `recordShapeOfRecordType(node).indexOfFieldName(name)` without | |
| /// creating an intermediate shape. | |
| int indexOfNameInRecordShapeOfRecordType(ir.RecordType node, String name) { | |
| final nameIndex = node.named.indexWhere((n) => n.name == name); | |
| if (nameIndex < 0) throw ArgumentError.value(name, 'name'); | |
| final index = node.positional.length + nameIndex; | |
| assert(index == recordShapeOfRecordType(node).indexOfFieldName(name)); | |
| return index; | |
| } | |
| /// Returns the `AsyncMarker` corresponding to `node.asyncMarker`. | |
| AsyncMarker getAsyncMarker(ir.FunctionNode node) { | |
| switch (node.asyncMarker) { | |
| case ir.AsyncMarker.Async: | |
| return AsyncMarker.async; | |
| case ir.AsyncMarker.AsyncStar: | |
| return AsyncMarker.asyncStar; | |
| case ir.AsyncMarker.Sync: | |
| return AsyncMarker.sync; | |
| case ir.AsyncMarker.SyncStar: | |
| return AsyncMarker.syncStar; | |
| } | |
| } | |
| /// Returns the `Variance` corresponding to `node.variance`. | |
| Variance convertVariance(ir.TypeParameter node) { | |
| if (node.isLegacyCovariant) return Variance.legacyCovariant; | |
| switch (node.variance) { | |
| case ir.Variance.covariant: | |
| return Variance.covariant; | |
| case ir.Variance.contravariant: | |
| return Variance.contravariant; | |
| case ir.Variance.invariant: | |
| return Variance.invariant; | |
| case ir.Variance.unrelated: | |
| throw UnsupportedError("Variance ${node.variance} is not supported."); | |
| } | |
| } | |
| /// Returns `true` if [node] is a null literal or a null constant. | |
| bool isNullLiteral(ir.Expression node) { | |
| return node is ir.NullLiteral || | |
| (node is ir.ConstantExpression && node.constant is ir.NullConstant); | |
| } | |
| /// Check whether [node] is immediately guarded by a | |
| /// [ir.CheckLibraryIsLoaded], and hence the node is a deferred access. | |
| ir.LibraryDependency? getDeferredImport(ir.TreeNode node) { | |
| // Note: this code relies on the CFE generating the code as we expect it here. | |
| // If one day we optimize away redundant CheckLibraryIsLoaded instructions, | |
| // we'd need to derive this information directly from the CFE (See #35005), | |
| ir.TreeNode? parent = node.parent; | |
| // TODO(sigmund): remove when CFE generates the correct tree (#35320). For | |
| // instance, it currently generates | |
| // | |
| // let _ = check(prefix) in (prefix::field.property) | |
| // | |
| // instead of: | |
| // | |
| // (let _ = check(prefix) in prefix::field).property | |
| if (node is ir.StaticGet || node is ir.ConstantExpression) { | |
| while (parent is ir.InstanceGet || | |
| parent is ir.DynamicGet || | |
| parent is ir.InstanceTearOff || | |
| parent is ir.FunctionTearOff || | |
| parent is ir.InstanceInvocation || | |
| parent is ir.InstanceGetterInvocation || | |
| parent is ir.DynamicInvocation || | |
| parent is ir.FunctionInvocation) { | |
| parent = parent!.parent; | |
| } | |
| } | |
| if (parent is ir.Let) { | |
| var initializer = parent.value; | |
| if (initializer is ir.CheckLibraryIsLoaded) { | |
| return initializer.import; | |
| } | |
| } | |
| return null; | |
| } | |
| class _FreeVariableVisitor | |
| with ir.DartTypeVisitorExperimentExclusionMixin<bool> | |
| implements ir.DartTypeVisitor<bool> { | |
| const _FreeVariableVisitor(); | |
| bool visit(ir.DartType type) { | |
| return type.accept(this); | |
| } | |
| bool visitList(List<ir.DartType> types) { | |
| for (ir.DartType type in types) { | |
| if (visit(type)) return true; | |
| } | |
| return false; | |
| } | |
| @override | |
| bool visitTypedefType(ir.TypedefType node) { | |
| return visitList(node.typeArguments); | |
| } | |
| @override | |
| bool visitTypeParameterType(ir.TypeParameterType node) { | |
| return true; | |
| } | |
| @override | |
| bool visitStructuralParameterType(ir.StructuralParameterType node) { | |
| return true; | |
| } | |
| @override | |
| bool visitIntersectionType(ir.IntersectionType node) { | |
| return true; | |
| } | |
| @override | |
| bool visitFunctionType(ir.FunctionType node) { | |
| if (visit(node.returnType)) return true; | |
| if (visitList(node.positionalParameters)) return true; | |
| for (ir.NamedType namedType in node.namedParameters) { | |
| if (visit(namedType.type)) return true; | |
| } | |
| return false; | |
| } | |
| @override | |
| bool visitRecordType(ir.RecordType node) { | |
| if (visitList(node.positional)) return true; | |
| for (ir.NamedType namedType in node.named) { | |
| if (visit(namedType.type)) return true; | |
| } | |
| return false; | |
| } | |
| @override | |
| bool visitInterfaceType(ir.InterfaceType node) { | |
| return visitList(node.typeArguments); | |
| } | |
| @override | |
| bool visitExtensionType(ir.ExtensionType node) { | |
| return visit(node.extensionTypeErasure); | |
| } | |
| @override | |
| bool visitFutureOrType(ir.FutureOrType node) { | |
| return visit(node.typeArgument); | |
| } | |
| @override | |
| bool visitNeverType(ir.NeverType node) => false; | |
| @override | |
| bool visitNullType(ir.NullType node) => false; | |
| @override | |
| bool visitVoidType(ir.VoidType node) => false; | |
| @override | |
| bool visitDynamicType(ir.DynamicType node) => false; | |
| @override | |
| bool visitInvalidType(ir.InvalidType node) => false; | |
| @override | |
| bool visitAuxiliaryType(ir.AuxiliaryType node) { | |
| throw UnsupportedError( | |
| 'Unsupported auxiliary type $node (${node.runtimeType}).', | |
| ); | |
| } | |
| } | |
| /// Returns `true` if [type] contains a type variable. | |
| /// | |
| /// All type variables (class type variables, generic method type variables, | |
| /// and function type variables) are considered. | |
| bool containsFreeVariables(ir.DartType type) => | |
| type.accept(const _FreeVariableVisitor()); | |
| /// Returns true if [importUri] corresponds to dart:html and related libraries. | |
| bool _isWebLibrary(Uri importUri) => | |
| importUri.isScheme('dart') && | |
| (importUri.path == 'html' || | |
| importUri.path == 'svg' || | |
| importUri.path == 'indexed_db' || | |
| importUri.path == 'web_audio' || | |
| importUri.path == 'web_gl' || | |
| importUri.path == 'web_sql' || | |
| importUri.path == 'html_common') || | |
| // Mock web library path for testing. | |
| importUri.path.contains( | |
| 'native_null_assertions/web_library_interfaces.dart', | |
| ); | |
| bool nodeIsInWebLibrary(ir.TreeNode? node) { | |
| if (node == null) return false; | |
| if (node is ir.Library) return _isWebLibrary(node.importUri); | |
| return nodeIsInWebLibrary(node.parent); | |
| } | |
| bool memberEntityIsInWebLibrary(MemberEntity entity) { | |
| var importUri = entity.library.canonicalUri; | |
| return _isWebLibrary(importUri); | |
| } | |
| /// Returns the effective target of a super access of [target]. | |
| /// | |
| /// If [target] is a concrete mixin stub then the stub target is returned | |
| /// instead of the concrete mixin stub. This is done to avoid unnecessary | |
| /// indirections in super accesses. | |
| /// | |
| /// See [ir.ProcedureStubKind.ConcreteMixinStub] for why concrete mixin stubs | |
| /// are inserted in the first place. | |
| ir.Member getEffectiveSuperTarget(ir.Member target) { | |
| if (target is ir.Procedure) { | |
| if (target.stubKind == ir.ProcedureStubKind.ConcreteMixinStub) { | |
| return getEffectiveSuperTarget(target.stubTarget!); | |
| } | |
| } | |
| return target; | |
| } |