blob: d46b622a5c7ec9084ad57c67680aa78612c0b5d0 [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`.
// TODO(johnniwinther): Change this to `InstanceGet` when the old method
// invocation encoding is no longer used.
final ir.Expression 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`.
// TODO(johnniwinther): Change this to `InstanceGet` when the old method
// invocation encoding is no longer used.
final ir.Expression 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 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.
// TODO(johnniwinther): Change [cache] key and [node] to `InstanceGet` when the
// old method invocation encoding is no longer used.
RuntimeTypeUseData computeRuntimeTypeUse(
Map<ir.Expression, RuntimeTypeUseData> cache, ir.Expression 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.InstanceGet &&
node.name.text == Identifiers.runtimeType_ ||
node is ir.DynamicGet && node.name.text == Identifiers.runtimeType_;
}
/// Returns `true` if [node] is of the form `e.toString()`.
bool isInvokeToString(ir.TreeNode node) {
return node is ir.InstanceInvocation && node.name.text == 'toString';
}
assert(isGetRuntimeType(node));
// TODO(johnniwinther): Replace this with `node.receiver` when the old method
// invocation encoding is no longer used.
_RuntimeTypeAccess runtimeTypeAccess = _getRuntimeTypeAccess(node);
assert(runtimeTypeAccess != null);
// TODO(johnniwinther): Change [receiverGet] and [argumentGet] to
// `InstanceGet` when the old method invocation encoding is no longer used.
// TODO(johnniwinther): Special-case `this.runtimeType`.
ir.Expression receiverGet;
ir.Expression receiver;
ir.Expression argumentGet;
ir.Expression argument;
RuntimeTypeUseKind kind;
if (runtimeTypeAccess.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 (_isObjectMethodInvocation(nullAware.parent)) {
_EqualsInvocation equalsInvocation =
_getEqualsInvocation(nullAware.parent);
if (equalsInvocation != null &&
equalsInvocation.left == nullAware.let) {
// Detected
//
// e0?.runtimeType == other
_RuntimeTypeAccess otherGetRuntimeType =
_getRuntimeTypeAccess(equalsInvocation.right);
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 = otherGetRuntimeType.node;
}
NullAwareExpression otherNullAware =
getNullAwareExpression(equalsInvocation.right);
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 &&
_isObjectMethodInvocation(nullAware.parent.parent)) {
_EqualsInvocation equalsInvocation =
_getEqualsInvocation(nullAware.parent.parent);
if (equalsInvocation != null &&
equalsInvocation.right == nullAware.let) {
// [nullAware] is the right hand side of ==.
_RuntimeTypeAccess otherGetRuntimeType =
_getRuntimeTypeAccess(equalsInvocation.left);
NullAwareExpression otherNullAware =
getNullAwareExpression(equalsInvocation.left);
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.node;
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 = runtimeTypeAccess.receiver;
receiverGet = node;
}
} else if (_isObjectMethodInvocation(node.parent)) {
_EqualsInvocation equalsInvocation = _getEqualsInvocation(node.parent);
if (equalsInvocation != null && equalsInvocation.left == node) {
// [node] is the left hand side of ==.
_RuntimeTypeAccess otherGetRuntimeType =
_getRuntimeTypeAccess(equalsInvocation.right);
NullAwareExpression nullAware =
getNullAwareExpression(equalsInvocation.right);
if (otherGetRuntimeType != null) {
// Detected
//
// e0.runtimeType == e1.runtimeType
// ^
// encoded as
//
// e0.runtimeType.==(e1.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = runtimeTypeAccess.receiver;
receiverGet = node;
argument = otherGetRuntimeType.receiver;
argumentGet = otherGetRuntimeType.node;
} 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 = runtimeTypeAccess.receiver;
receiverGet = node;
argument = nullAware.receiver;
argumentGet = nullAware.expression;
}
} else if (isInvokeToString(node.parent)) {
// Detected
//
// e.runtimeType.toString()
// ^
kind = RuntimeTypeUseKind.string;
receiver = runtimeTypeAccess.receiver;
receiverGet = node;
}
} else if (node.parent is ir.Arguments &&
_isObjectMethodInvocation(node.parent.parent)) {
_EqualsInvocation _equalsInvocation =
_getEqualsInvocation(node.parent.parent);
if (_equalsInvocation != null && _equalsInvocation.right == node) {
// [node] is the right hand side of ==.
_RuntimeTypeAccess otherGetRuntimeType =
_getRuntimeTypeAccess(_equalsInvocation.left);
NullAwareExpression nullAware =
getNullAwareExpression(_equalsInvocation.left);
if (otherGetRuntimeType != null) {
// Detected
//
// e0.runtimeType == e1.runtimeType
// ^
// encoded as
//
// e0.runtimeType.==(e1.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = otherGetRuntimeType.receiver;
receiverGet = otherGetRuntimeType.node;
argument = runtimeTypeAccess.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 = runtimeTypeAccess.receiver;
argumentGet = node;
}
}
} else if (node.parent is ir.StringConcatenation) {
// Detected
//
// '${e.runtimeType}'
// ^
kind = RuntimeTypeUseKind.string;
receiver = runtimeTypeAccess.receiver;
receiverGet = node;
}
if (kind == null) {
// Default to unknown
//
// e.runtimeType
// ^
kind = RuntimeTypeUseKind.unknown;
receiver = runtimeTypeAccess.receiver;
receiverGet = node;
}
RuntimeTypeUseData data =
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;
}
/// Returns `true` if [node] is a potential invocation of an Object method.
bool _isObjectMethodInvocation(ir.TreeNode node) {
return node is ir.InstanceInvocation || node is ir.EqualsCall;
}
/// Returns the [_RuntimeTypeAccess] corresponding to [node] if it is an access
/// of `.runtimeType`, and `null` otherwise.
_RuntimeTypeAccess _getRuntimeTypeAccess(ir.TreeNode node) {
if (node is ir.InstanceGet && node.name.text == 'runtimeType') {
return _RuntimeTypeAccess(node, node.receiver);
} else if (node is ir.DynamicGet && node.name.text == 'runtimeType') {
return _RuntimeTypeAccess(node, node.receiver);
}
return null;
}
class _RuntimeTypeAccess {
final ir.Expression node;
final ir.Expression receiver;
_RuntimeTypeAccess(this.node, this.receiver);
}
/// Returns the [_EqualsInvocation] corresponding to [node] if it is a call to
/// of `==`, and `null` otherwise.
_EqualsInvocation _getEqualsInvocation(ir.TreeNode node) {
if (node is ir.EqualsCall) {
return _EqualsInvocation(node, node.left, node.right);
}
return null;
}
class _EqualsInvocation {
final ir.Expression node;
final ir.Expression left;
final ir.Expression right;
_EqualsInvocation(this.node, this.left, this.right);
}