blob: 7d1beb2c4be00dac9cc3476168c04659b2d2cc0d [file] [log] [blame]
// 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 'dart:collection';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
Constructor unnamedConstructor(Class c) =>
c.constructors.firstWhere((c) => c.name.name == '', orElse: () => null);
/// Returns the enclosing library for reference [node].
Library getLibrary(NamedNode node) {
for (TreeNode n = node; n != null; n = n.parent) {
if (n is Library) return n;
}
return null;
}
final Pattern _syntheticTypeCharacters = RegExp('[&^#.]');
String _escapeIdentifier(String identifier) {
// Remove the special characters used to encode mixin application class names
// which are legal in Kernel, but not in JavaScript.
return identifier?.replaceAll(_syntheticTypeCharacters, r'$');
}
/// Returns the escaped name for class [node].
///
/// The caller of this function has to make sure that this name is unique in
/// the current scope.
///
/// In the current encoding, generic classes are generated in a function scope
/// which avoids name clashes of the escaped class name.
String getLocalClassName(Class node) => _escapeIdentifier(node.name);
/// Returns the escaped name for the type parameter [node].
///
/// In the current encoding, generic classes are generated in a function scope
/// which avoids name clashes of the escaped parameter name.
String getTypeParameterName(TypeParameter node) => _escapeIdentifier(node.name);
String getTopLevelName(NamedNode n) {
if (n is Procedure) return n.name.name;
if (n is Class) return n.name;
if (n is Typedef) return n.name;
if (n is Field) return n.name.name;
return n.canonicalName?.name;
}
/// Given an annotated [node] and a [test] function, returns the first matching
/// constant valued annotation.
///
/// For example if we had the ClassDeclaration node for `FontElement`:
///
/// @js.JS('HTMLFontElement')
/// @deprecated
/// class FontElement { ... }
///
/// We could match `@deprecated` with a test function like:
///
/// (v) => v.type.name == 'Deprecated' && v.type.element.library.isDartCore
///
Expression findAnnotation(TreeNode node, bool test(Expression value)) {
List<Expression> annotations;
if (node is Class) {
annotations = node.annotations;
} else if (node is Typedef) {
annotations = node.annotations;
} else if (node is Procedure) {
annotations = node.annotations;
} else if (node is Member) {
annotations = node.annotations;
} else if (node is Library) {
annotations = node.annotations;
} else {
return null;
}
return annotations.firstWhere(test, orElse: () => null);
}
bool isBuiltinAnnotation(
Expression value, String libraryName, String expectedName) {
if (value is ConstructorInvocation) {
var c = value.target.enclosingClass;
if (c.name == expectedName) {
var uri = c.enclosingLibrary.importUri;
return uri.scheme == 'dart' && uri.path == libraryName;
}
}
return false;
}
/// Returns true if [name] is an operator method that is available on primitive
/// types (`int`, `double`, `num`, `String`, `bool`).
///
/// This does not include logical operators that cannot be user-defined
/// (`!`, `&&` and `||`).
bool isOperatorMethodName(String name) {
switch (name) {
case '==':
case '~':
case '^':
case '|':
case '&':
case '>>':
case '<<':
case '+':
case 'unary-':
case '-':
case '*':
case '/':
case '~/':
case '>':
case '<':
case '>=':
case '<=':
case '%':
return true;
}
return false;
}
bool isFromEnvironmentInvocation(CoreTypes coreTypes, StaticInvocation node) {
var target = node.target;
return node.isConst &&
target.name.name == 'fromEnvironment' &&
target.enclosingLibrary == coreTypes.coreLibrary;
}
/// Returns true if this class is of the form:
/// `class C = Object with M [implements I1, I2 ...];`
///
/// A mixin alias class is a mixin application, that can also be itself used as
/// a mixin.
bool isMixinAliasClass(Class c) =>
c.isMixinApplication && c.superclass.superclass == null;
List<Class> getSuperclasses(Class c) {
var result = <Class>[];
var visited = HashSet<Class>();
while (c != null && visited.add(c)) {
for (var m = c.mixedInClass; m != null; m = m.mixedInClass) {
result.add(m);
}
var superclass = c.superclass;
if (superclass == null) break;
result.add(superclass);
c = superclass;
}
return result;
}
List<Class> getImmediateSuperclasses(Class c) {
var result = <Class>[];
var m = c.mixedInClass;
if (m != null) result.add(m);
var s = c.superclass;
if (s != null) result.add(s);
return result;
}
Expression getInvocationReceiver(InvocationExpression node) =>
node is MethodInvocation
? node.receiver
: node is DirectMethodInvocation ? node.receiver : null;
bool isInlineJS(Member e) =>
e is Procedure &&
e.name.name == 'JS' &&
e.enclosingLibrary.importUri.toString() == 'dart:_foreign_helper';
// Check whether we have any covariant parameters.
// Usually we don't, so we can use the same type.
bool isCovariant(VariableDeclaration p) =>
p.isCovariant || p.isGenericCovariantImpl;
/// Returns true iff this factory constructor just throws [UnsupportedError]/
///
/// `dart:html` has many of these.
bool isUnsupportedFactoryConstructor(Procedure node) {
if (node.name.isPrivate && node.enclosingLibrary.importUri.scheme == 'dart') {
var body = node.function.body;
if (body is Block) {
var statements = body.statements;
if (statements.length == 1) {
var statement = statements[0];
if (statement is ExpressionStatement) {
var expr = statement.expression;
if (expr is Throw) {
var error = expr.expression;
if (error is ConstructorInvocation &&
error.target.enclosingClass.name == 'UnsupportedError') {
// HTML adds a lot of private constructors that are unreachable.
// Skip these.
return true;
}
}
}
}
}
}
return false;
}
/// Returns the redirecting factory constructors for the enclosing class,
/// if the field [f] is storing that information, otherwise returns `null`.
Iterable<Member> getRedirectingFactories(Field f) {
// TODO(jmesserly): this relies on implementation details in Kernel
if (f.name.name == "_redirecting#") {
assert(f.isStatic);
var list = f.initializer as ListLiteral;
return list.expressions.map((e) => (e as StaticGet).target);
}
return null;
}
/// Gets the real supertype of [c] and the list of [mixins] in reverse
/// application order (mixins will appear before ones they override).
///
/// This is used to ignore synthetic mixin application classes.
///
// TODO(jmesserly): consider replacing this with Kernel's mixin unrolling once
// we don't have the Analyzer backend to maintain.
Class getSuperclassAndMixins(Class c, List<Class> mixins) {
assert(mixins.isEmpty);
var mixedInClass = c.mixedInClass;
if (mixedInClass != null) mixins.add(mixedInClass);
var sc = c.superclass;
for (; sc.isAnonymousMixin; sc = sc.superclass) {
mixins.add(sc.mixedInClass);
}
return sc;
}
// TODO(jmesserly): replace with a flag on Class once Kernel supports it.
bool isMixinDeclaration(Class c) => false;