blob: 4344b618495a56f68e859b883fd45bf6765bd57b [file] [log] [blame] [edit]
// 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.VariableDeclaration a, ir.VariableDeclaration b) {
return a.name!.compareTo(b.name!);
}
/// Comparator for the declaration order of parameters.
int nativeOrdering(ir.VariableDeclaration a, ir.VariableDeclaration 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.variable.initializer;
if (initializer is ir.CheckLibraryIsLoaded) {
return initializer.import;
}
}
return null;
}
class _FreeVariableVisitor 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;
}