blob: c0a9f723cdacd10fb60030aa21cd249d0e5675f4 [file] [log] [blame]
// Copyright (c) 2015, 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:analyzer/analyzer.dart' as analyzer;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart' show DartType;
import 'package:analyzer/src/dart/ast/ast.dart' show FunctionBodyImpl;
import 'package:analyzer/src/dart/ast/utilities.dart' show NodeReplacer;
import 'package:analyzer/src/dart/element/type.dart' show DynamicTypeImpl;
import 'package:analyzer/src/generated/parser.dart' show ResolutionCopier;
import 'package:analyzer/src/task/strong/ast_properties.dart' as ast_properties;
import 'package:logging/logging.dart' as logger;
import 'ast_builder.dart' show AstBuilder, RawAstBuilder;
import 'element_helpers.dart' show isInlineJS;
final _log = new logger.Logger('dev_compiler.reify_coercions');
// This class implements a pass which modifies (in place) the ast replacing
// abstract coercion nodes with their dart implementations.
class CoercionReifier extends analyzer.GeneralizingAstVisitor<Object> {
final cloner = new _TreeCloner();
CoercionReifier._();
/// Transforms the given compilation units, and returns a new AST with
/// explicit coercion nodes in appropriate places.
static List<CompilationUnit> reify(List<CompilationUnit> units) {
var cr = new CoercionReifier._();
return units.map(cr.visitCompilationUnit).toList(growable: false);
}
/// Returns true if the `as` [node] was created by this class.
// TODO(sra): Find a better way to recognize reified coercion, since we
// can't set the isSynthetic attribute.
static bool isImplicitCast(AsExpression node) => node.asOperator.offset == 0;
/// Creates an implicit cast for expression [e] to [toType].
static Expression castExpression(Expression e, DartType toType) {
// We use an empty name in the AST, because the JS code generator only cares
// about the target type. It does not look at the AST name.
var typeName = new TypeName(AstBuilder.identifierFromString(''), null);
typeName.type = toType;
var cast = AstBuilder.asExpression(e, typeName);
cast.staticType = toType;
return cast;
}
@override
CompilationUnit visitCompilationUnit(CompilationUnit node) {
if (ast_properties.hasImplicitCasts(node)) {
// Clone compilation unit, so we don't modify the originals.
node = _clone(node);
super.visitCompilationUnit(node);
}
return node;
}
@override
visitExpression(Expression node) {
node.visitChildren(this);
var castType = ast_properties.getImplicitCast(node);
if (castType != null) {
_replaceNode(node.parent, node, castExpression(node, castType));
}
}
@override
visitMethodInvocation(MethodInvocation node) {
if (isInlineJS(node.methodName.staticElement)) {
// Don't cast our inline-JS code in SDK.
ast_properties.setImplicitCast(node, null);
}
visitExpression(node);
}
@override
visitParenthesizedExpression(ParenthesizedExpression node) {
super.visitParenthesizedExpression(node);
node.staticType = node.expression.staticType;
}
@override
visitForEachStatement(ForEachStatement node) {
// Visit other children.
node.iterable.accept(this);
node.body.accept(this);
// If needed, assert a cast inside the body before the variable is read.
var variable = node.identifier ?? node.loopVariable.identifier;
var castType = ast_properties.getImplicitCast(variable);
if (castType != null) {
// Build the cast. We will place this cast in the body, so need to clone
// the variable's AST node and clear out its static type (otherwise we
// will optimize away the cast).
var cast = castExpression(
_clone(variable)..staticType = DynamicTypeImpl.instance, castType);
var body = node.body;
var blockBody = <Statement>[RawAstBuilder.expressionStatement(cast)];
if (body is Block) {
blockBody.addAll(body.statements);
} else {
blockBody.add(body);
}
_replaceNode(node, body, RawAstBuilder.block(blockBody));
}
}
void _replaceNode(AstNode parent, AstNode oldNode, AstNode newNode) {
if (!identical(oldNode, newNode)) {
var replaced = parent.accept(new NodeReplacer(oldNode, newNode));
// It looks like NodeReplacer will always return true.
// It does throw IllegalArgumentException though, if child is not found.
assert(replaced);
}
}
/*=T*/ _clone/*<T extends AstNode>*/(/*=T*/ node) {
var copy = node.accept(cloner) as dynamic/*=T*/;
ResolutionCopier.copyResolutionData(node, copy);
return copy;
}
}
class _TreeCloner extends analyzer.AstCloner {
void _cloneProperties(AstNode clone, AstNode node) {
if (clone is Expression) {
ast_properties.setImplicitCast(
clone, ast_properties.getImplicitCast(node));
ast_properties.setIsDynamicInvoke(
clone, ast_properties.isDynamicInvoke(node));
}
}
@override
/*=E*/ cloneNode/*<E extends AstNode>*/(/*=E*/ node) {
var clone = super.cloneNode(node);
_cloneProperties(clone, node);
return clone;
}
@override
List/*<E>*/ cloneNodeList/*<E extends AstNode>*/(List/*<E>*/ list) {
var clone = super.cloneNodeList(list);
for (int i = 0, len = list.length; i < len; i++) {
_cloneProperties(clone[i], list[i]);
}
return clone;
}
// TODO(jmesserly): ResolutionCopier is not copying this yet.
@override
BlockFunctionBody visitBlockFunctionBody(BlockFunctionBody node) {
var clone = super.visitBlockFunctionBody(node);
(clone as FunctionBodyImpl).localVariableInfo =
(node as FunctionBodyImpl).localVariableInfo;
return clone;
}
@override
ExpressionFunctionBody visitExpressionFunctionBody(
ExpressionFunctionBody node) {
var clone = super.visitExpressionFunctionBody(node);
(clone as FunctionBodyImpl).localVariableInfo =
(node as FunctionBodyImpl).localVariableInfo;
return clone;
}
// TODO(jmesserly): workaround for
// https://github.com/dart-lang/sdk/issues/26368
@override
TypeName visitTypeName(TypeName node) {
var clone = super.visitTypeName(node);
clone.type = node.type;
return clone;
}
}