blob: d7e1d758fcedaa105cf9e93a16e9388d8fc5a6ed [file] [log] [blame]
// Copyright (c) 2019, 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:kernel/ast.dart' as ir;
import '../common/names.dart';
import 'util.dart';
/// Enum for recognized use kinds of `Object.runtimeType`.
enum RuntimeTypeUseKind {
/// Unknown use of `Object.runtimeType`. This is the fallback value if the
/// usage didn't match any of the recognized patterns.
unknown,
/// `Object.runtimeType` used in a pattern like
/// `a.runtimeType == b.runtimeType`.
equals,
/// `Object.runtimeType` used in a pattern like `'${e.runtimeType}'` or
/// `e.runtimeType.toString()`.
string,
}
/// Data object use for computing static type information on uses of
/// `Object.runtimeType`.
class RuntimeTypeUseData {
/// The use kind of `Object.runtimeType`.
final RuntimeTypeUseKind kind;
/// The property get for the left (or single) occurrence of `.runtimeType`.
final ir.PropertyGet leftRuntimeTypeExpression;
/// The receiver expression.
final ir.Expression receiver;
/// The static type of the receiver expression. This is set in the static type
/// visitor.
ir.DartType receiverType;
/// The property get for the right occurrence of `.runtimeType` when [kind]
/// is `RuntimeTypeUseKind.equals`.
final ir.PropertyGet rightRuntimeTypeExpression;
/// The argument expression if [kind] is `RuntimeTypeUseKind.equals`.
final ir.Expression argument;
/// The static type of the argument expression. This is set in the static type
/// visitor.
ir.DartType argumentType;
RuntimeTypeUseData(this.kind, this.leftRuntimeTypeExpression, this.receiver,
this.rightRuntimeTypeExpression, this.argument);
bool get isComplete {
switch (kind) {
case RuntimeTypeUseKind.unknown:
case RuntimeTypeUseKind.string:
return receiverType != null;
case RuntimeTypeUseKind.equals:
return receiverType != null && argumentType != null;
}
throw new UnsupportedError("Unexpected RuntimeTypeUseKind $kind.");
}
@override
String toString() {
return "RuntimeTypeUseData(kind=$kind,"
"receiverGet=$leftRuntimeTypeExpression,receiver=$receiver,"
"receiverType=$receiverType,argumentGet=$rightRuntimeTypeExpression,"
"argument=$argument,argumentType=$argumentType)";
}
}
/// Computes the [RuntimeTypeUseData] corresponding to the `e.runtimeType`
/// [node].
///
/// [cache] is used to ensure that only one [RuntimeTypeUseData] object is
/// created per case, even for the `==` case.
RuntimeTypeUseData computeRuntimeTypeUse(
Map<ir.PropertyGet, RuntimeTypeUseData> cache, ir.PropertyGet node) {
RuntimeTypeUseData receiverData = cache[node];
if (receiverData != null) return receiverData;
/// Returns `true` if [node] is of the form `e.runtimeType`.
bool isGetRuntimeType(ir.TreeNode node) {
return node is ir.PropertyGet && node.name.name == Identifiers.runtimeType_;
}
/// Returns [node] if [node] is of the form `e.runtimeType` and `null`
/// otherwise.
ir.PropertyGet asGetRuntimeType(ir.TreeNode node) {
return isGetRuntimeType(node) ? node : null;
}
/// Returns `true` if [node] is of the form `e.toString()`.
bool isInvokeToString(ir.TreeNode node) {
return node is ir.MethodInvocation && node.name.name == 'toString';
}
assert(isGetRuntimeType(node));
// TODO(johnniwinther): Special-case `this.runtimeType`.
ir.PropertyGet receiverGet;
ir.Expression receiver;
ir.PropertyGet argumentGet;
ir.Expression argument;
RuntimeTypeUseKind kind;
if (node.receiver is ir.VariableGet &&
node.parent is ir.ConditionalExpression &&
node.parent.parent is ir.Let) {
NullAwareExpression nullAware = getNullAwareExpression(node.parent.parent);
if (nullAware != null) {
// The node is of the form:
//
// let #t1 = e in #t1 == null ? null : #t1.runtimeType
// ^
if (nullAware.parent is ir.VariableDeclaration &&
nullAware.parent.parent is ir.Let) {
NullAwareExpression outer =
getNullAwareExpression(nullAware.parent.parent);
if (outer != null &&
outer.receiver == nullAware.let &&
isInvokeToString(outer.expression)) {
// Detected
//
// e?.runtimeType?.toString()
// ^
// encoded as
//
// let #t2 = (let #t1 = e in #t1 == null ? null : #t1.runtimeType)
// ^
// in #t2 == null ? null : #t2.toString()
//
kind = RuntimeTypeUseKind.string;
receiver = nullAware.receiver;
receiverGet = node;
}
} else if (nullAware.parent is ir.MethodInvocation) {
ir.MethodInvocation methodInvocation = nullAware.parent;
if (methodInvocation.receiver == nullAware.let &&
methodInvocation.name.name == '==') {
// Detected
//
// e0?.runtimeType == other
ir.PropertyGet otherGetRuntimeType =
asGetRuntimeType(methodInvocation.arguments.positional.first);
if (otherGetRuntimeType != null) {
// Detected
//
// e0?.runtimeType == e1.runtimeType
// ^
// encoded as
//
// (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
// ^
// .==(e1.runtimeType)
kind = RuntimeTypeUseKind.equals;
receiver = nullAware.receiver;
receiverGet = node;
argument = otherGetRuntimeType.receiver;
argumentGet = methodInvocation.arguments.positional.first;
}
NullAwareExpression otherNullAware = getNullAwareExpression(
methodInvocation.arguments.positional.first);
if (otherNullAware != null &&
isGetRuntimeType(otherNullAware.expression)) {
// Detected
//
// e0?.runtimeType == e1?.runtimeType
// ^
// encoded as
//
// (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
// ^
// .==(let #t2 = e1 in #t2 == null ? null : #t2.runtimeType)
//
kind = RuntimeTypeUseKind.equals;
receiver = nullAware.receiver;
receiverGet = node;
argument = otherNullAware.receiver;
argumentGet = otherNullAware.expression;
}
} else if (isInvokeToString(nullAware.parent)) {
// Detected
//
// e?.runtimeType.toString()
// ^
// encoded as
//
// (let #t1 = e in #t1 == null ? null : #t1.runtimeType)
// ^
// .toString()
//
kind = RuntimeTypeUseKind.string;
receiver = nullAware.receiver;
receiverGet = node;
}
} else if (nullAware.parent is ir.Arguments &&
nullAware.parent.parent is ir.MethodInvocation) {
ir.MethodInvocation methodInvocation = nullAware.parent.parent;
if (methodInvocation.name.name == '==' &&
methodInvocation.arguments.positional.first == nullAware.let) {
// [nullAware] is the right hand side of ==.
ir.PropertyGet otherGetRuntimeType =
asGetRuntimeType(methodInvocation.receiver);
NullAwareExpression otherNullAware =
getNullAwareExpression(methodInvocation.receiver);
if (otherGetRuntimeType != null) {
// Detected
//
// e0.runtimeType == e1?.runtimeType
// ^
// encoded as
//
// e0.runtimeType.==(
// let #t1 = e1 in #t1 == null ? null : #t1.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = otherGetRuntimeType.receiver;
receiverGet = otherGetRuntimeType;
argument = nullAware.receiver;
argumentGet = node;
}
if (otherNullAware != null &&
isGetRuntimeType(otherNullAware.expression)) {
// Detected
//
// e0?.runtimeType == e1?.runtimeType
// ^
// encoded as
//
// (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
// .==(let #t2 = e1 in #t2 == null ? null : #t2.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = otherNullAware.receiver;
receiverGet = otherNullAware.expression;
argument = nullAware.receiver;
argumentGet = node;
}
}
} else if (nullAware.parent is ir.StringConcatenation) {
// Detected
//
// '${e?.runtimeType}'
// ^
// encoded as
//
// '${let #t1 = e in #t1 == null ? null : #t1.runtimeType}'
// ^
kind = RuntimeTypeUseKind.string;
receiver = nullAware.receiver;
receiverGet = node;
} else {
// Default to unknown
//
// e?.runtimeType
// ^
// encoded as
//
// let #t1 = e in #t1 == null ? null : #t1.runtimeType
// ^
kind = RuntimeTypeUseKind.unknown;
receiver = nullAware.receiver;
receiverGet = node;
}
}
} else if (node.parent is ir.VariableDeclaration &&
node.parent.parent is ir.Let) {
NullAwareExpression nullAware = getNullAwareExpression(node.parent.parent);
if (nullAware != null && isInvokeToString(nullAware.expression)) {
// Detected
//
// e.runtimeType?.toString()
// ^
// encoded as
//
// let #t1 = e.runtimeType in #t1 == null ? null : #t1.toString()
// ^
kind = RuntimeTypeUseKind.string;
receiver = node.receiver;
receiverGet = node;
}
} else if (node.parent is ir.MethodInvocation) {
ir.MethodInvocation methodInvocation = node.parent;
if (methodInvocation.name.name == '==' &&
methodInvocation.receiver == node) {
// [node] is the left hand side of ==.
ir.PropertyGet otherGetRuntimeType =
asGetRuntimeType(methodInvocation.arguments.positional.first);
NullAwareExpression nullAware =
getNullAwareExpression(methodInvocation.arguments.positional.first);
if (otherGetRuntimeType != null) {
// Detected
//
// e0.runtimeType == e1.runtimeType
// ^
// encoded as
//
// e0.runtimeType.==(e1.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = node.receiver;
receiverGet = node;
argument = otherGetRuntimeType.receiver;
argumentGet = otherGetRuntimeType;
} else if (nullAware != null && isGetRuntimeType(nullAware.expression)) {
// Detected
//
// e0.runtimeType == e1?.runtimeType
// ^
// encoded as
//
// e0.runtimeType.==(
// ^
// let #t1 = e1 in #t1 == null ? null : #t1.runtimeType)
kind = RuntimeTypeUseKind.equals;
receiver = node.receiver;
receiverGet = node;
argument = nullAware.receiver;
argumentGet = nullAware.expression;
}
} else if (isInvokeToString(node.parent)) {
// Detected
//
// e.runtimeType.toString()
// ^
kind = RuntimeTypeUseKind.string;
receiver = node.receiver;
receiverGet = node;
}
} else if (node.parent is ir.Arguments &&
node.parent.parent is ir.MethodInvocation) {
ir.MethodInvocation methodInvocation = node.parent.parent;
if (methodInvocation.name.name == '==' &&
methodInvocation.arguments.positional.first == node) {
// [node] is the right hand side of ==.
ir.PropertyGet otherGetRuntimeType =
asGetRuntimeType(methodInvocation.receiver);
NullAwareExpression nullAware =
getNullAwareExpression(methodInvocation.receiver);
if (otherGetRuntimeType != null) {
// Detected
//
// e0.runtimeType == e1.runtimeType
// ^
// encoded as
//
// e0.runtimeType.==(e1.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = otherGetRuntimeType.receiver;
receiverGet = otherGetRuntimeType;
argument = node.receiver;
argumentGet = node;
} else if (nullAware != null && isGetRuntimeType(nullAware.expression)) {
// Detected
//
// e0?.runtimeType == e1.runtimeType
// ^
// encoded as
//
// (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
// .==(e1.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = nullAware.receiver;
receiverGet = nullAware.expression;
argument = node.receiver;
argumentGet = node;
}
}
} else if (node.parent is ir.StringConcatenation) {
// Detected
//
// '${e.runtimeType}'
// ^
kind = RuntimeTypeUseKind.string;
receiver = node.receiver;
receiverGet = node;
}
if (kind == null) {
// Default to unknown
//
// e.runtimeType
// ^
kind = RuntimeTypeUseKind.unknown;
receiver = node.receiver;
receiverGet = node;
}
RuntimeTypeUseData data = new RuntimeTypeUseData(
kind, receiverGet, receiver, argumentGet, argument);
cache[receiverGet] = data;
if (argumentGet != null) {
cache[argumentGet] = data;
}
assert(receiverGet != null, "Missing receiverGet in $data for $node.");
assert(!(argument != null && argumentGet == null),
"Missing argumentGet in $data for $node.");
assert(
receiverGet != argumentGet, "Duplicate property get in $data for $node.");
return data;
}