blob: 8a2c5423e38eff65589174ad4f3173304672646b [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.
library dev_compiler.src.codegen.reify_coercions;
import 'package:analyzer/analyzer.dart' as analyzer;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
import 'package:logging/logging.dart' as logger;
import '../compiler.dart' show AbstractCompiler;
import '../checker/rules.dart';
import '../info.dart';
import 'ast_builder.dart';
final _log = new logger.Logger('dev_compiler.reify_coercions');
// TODO(leafp) Factor this out or use an existing library
class Tuple2<T0, T1> {
final T0 e0;
final T1 e1;
Tuple2(this.e0, this.e1);
}
typedef T Function1<S, T>(S _);
class NewTypeIdDesc {
/// If null, then this is not a library level identifier (i.e. it's
/// a type parameter, or a special type like void, dynamic, etc)
LibraryElement importedFrom;
/// True => use/def in same library
bool fromCurrent;
/// True => not a source variable
bool synthetic;
NewTypeIdDesc({this.fromCurrent, this.importedFrom, this.synthetic});
}
class _Inference extends DownwardsInference {
TypeManager _tm;
_Inference(TypeRules rules, this._tm) : super(rules);
@override
void annotateCastFromDynamic(Expression e, DartType t) {
var cast = Coercion.cast(e.staticType, t);
var info = new DynamicCast(rules, e, cast);
CoercionInfo.set(e, info);
}
@override
void annotateListLiteral(ListLiteral e, List<DartType> targs) {
var tNames = targs.map(_tm.typeNameFromDartType).toList();
e.typeArguments = AstBuilder.typeArgumentList(tNames);
var listT = rules.provider.listType.substitute4(targs);
e.staticType = listT;
}
@override
void annotateMapLiteral(MapLiteral e, List<DartType> targs) {
var tNames = targs.map(_tm.typeNameFromDartType).toList();
e.typeArguments = AstBuilder.typeArgumentList(tNames);
var mapT = rules.provider.mapType.substitute4(targs);
e.staticType = mapT;
}
@override
void annotateInstanceCreationExpression(
InstanceCreationExpression e, List<DartType> targs) {
var tNames = targs.map(_tm.typeNameFromDartType).toList();
var cName = e.constructorName;
var id = cName.type.name;
var typeName = AstBuilder.typeName(id, tNames);
cName.type = typeName;
var newType =
(e.staticType.element as ClassElement).type.substitute4(targs);
e.staticType = newType;
typeName.type = newType;
}
@override
void annotateFunctionExpression(FunctionExpression e, DartType returnType) {
// Implicitly changes e.staticType
(e.element as ExecutableElementImpl).returnType = returnType;
}
}
// 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 CoercionManager _cm;
final TypeManager _tm;
final VariableManager _vm;
final LibraryUnit _library;
final _Inference _inferrer;
CoercionReifier._(
this._cm, this._tm, this._vm, this._library, this._inferrer);
factory CoercionReifier(LibraryUnit library, AbstractCompiler compiler) {
var vm = new VariableManager();
var tm = new TypeManager(library.library.element.enclosingElement, vm);
var cm = new CoercionManager(vm, tm);
var inferrer = new _Inference(compiler.rules, tm);
return new CoercionReifier._(cm, tm, vm, library, inferrer);
}
// This should be the entry point for this class. Entering via the
// visit functions directly may not do the right thing with respect
// to discharging the collected definitions.
// Returns the set of new type identifiers added by the reifier
Map<Identifier, NewTypeIdDesc> reify() {
_library.partsThenLibrary.forEach(visitCompilationUnit);
return _tm.addedTypes;
}
@override
Object visitExpression(Expression node) {
var info = CoercionInfo.get(node);
if (info is InferredTypeBase) {
return _visitInferredTypeBase(info, node);
} else if (info is DownCast) {
return _visitDownCast(info, node);
}
return super.visitExpression(node);
}
///////////////// Private //////////////////////////////////
Object _visitInferredTypeBase(InferredTypeBase node, Expression expr) {
var success = _inferrer.inferExpression(expr, node.type, <String>[]);
assert(success);
expr.visitChildren(this);
return null;
}
Object _visitDownCast(DownCast node, Expression expr) {
var parent = expr.parent;
expr.visitChildren(this);
Expression newE = _cm.coerceExpression(expr, node.cast);
if (!identical(expr, newE)) {
var replaced = parent.accept(new NodeReplacer(expr, newE));
// It looks like NodeReplacer will always return true.
// It does throw IllegalArgumentException though, if child is not found.
assert(replaced);
}
return null;
}
Object visitCompilationUnit(CompilationUnit unit) {
_cm.enterCompilationUnit(unit);
Object ret = super.visitCompilationUnit(unit);
_cm.exitCompilationUnit(unit);
return ret;
}
}
// This provides a placeholder variable manager. Currently it simply
// mangles names in a way unlikely (but not guaranteed) to avoid
// collisions with user variables.
// TODO(leafp): Replace this with something real.
class VariableManager {
// TODO(leafp): Hack, not for real.
int _id = 0;
SimpleIdentifier freshIdentifier(String hint) {
String n = _id.toString();
_id++;
String s = "__$hint$n";
return AstBuilder.identifierFromString(s);
}
SimpleIdentifier freshTypeIdentifier(String hint) {
return freshIdentifier(hint);
}
}
// This class manages the reification of coercions as dart code. Given a
// coercion c and an expression e it will produce an expression e' which
// is the result of coercing e using c.
class CoercionManager {
VariableManager _vm;
TypeManager _tm;
CoercionManager(this._vm, this._tm);
// Call on entry to and exit from a compilation unit in order to properly
// discharge the accumulated wrappers.
void enterCompilationUnit(CompilationUnit unit) {
_tm.enterCompilationUnit(unit);
}
void exitCompilationUnit(CompilationUnit unit) {
_tm.exitCompilationUnit(unit);
}
// The main entry point. Coerce e using c, returning a new expression,
// possibly recording additional coercions functions and typedefs to
// be discharged at a higher level.
Expression coerceExpression(Expression e, Coercion c) {
assert(c != null);
assert(c is! CoercionError);
if (e is NamedExpression) {
Expression inner = coerceExpression(e.expression, c);
return new NamedExpression(e.name, inner);
}
if (c is Cast) return _castExpression(e, c);
assert(c is Identity);
return e;
}
///////////////// Private //////////////////////////////////
Expression _castExpression(Expression e, Cast c) {
var ttName = _tm.typeNameFromDartType(c.toType);
var cast = AstBuilder.asExpression(e, ttName);
cast.staticType = c.toType;
return cast;
}
}
// A class for managing the interaction between the DartType hierarchy
// and the AST type representation. It provides utilities to translate
// a DartType to AST. In order to do so, it maintains a map of typedefs
// naming otherwise un-named types. These must be discharged at the top
// level of the compilation unit in order to produce well-formed dart code.
// Note that in order to hoist the typedefs out of parameterized classes
// we must close over any type variables.
class TypeManager {
final VariableManager _vm;
final LibraryElement _currentLibrary;
final Map<Identifier, NewTypeIdDesc> addedTypes = {};
CompilationUnitElement _currentUnit;
/// A map containing new function typedefs to be introduced at the top level
/// This uses LinkedHashMap to emit code in a consistent order.
final Map<FunctionType, FunctionTypeAlias> _typedefs = {};
TypeManager(this._currentLibrary, this._vm);
void enterCompilationUnit(CompilationUnit unit) {
_currentUnit = unit.element;
}
void exitCompilationUnit(CompilationUnit unit) {
unit.declarations.addAll(_typedefs.values);
_typedefs.clear();
}
TypeName typeNameFromDartType(DartType dType) {
return _typeNameFromDartType(dType);
}
NormalFormalParameter typedFormal(Identifier v, DartType type) {
return _typedFormal(v, type);
}
///////////////// Private //////////////////////////////////
List<TypeParameterType> _freeTypeVariables(DartType type) {
var s = new Set<TypeParameterType>();
void _ft(DartType type) {
void _ftMap(Map<String, DartType> m) {
if (m == null) return;
for (var k in m.keys) _ft(m[k]);
}
void _ftList(List<DartType> l) {
if (l == null) return;
for (int i = 0; i < l.length; i++) _ft(l[i]);
}
if (type == null) return;
if (type.isDynamic) return;
if (type.isBottom) return;
if (type.isObject) return;
if (type is TypeParameterType) {
s.add(type);
return;
}
if (type is ParameterizedType) {
if (type.name != null && type.name != "") {
_ftList(type.typeArguments);
return;
}
if (type is FunctionType) {
_ftMap(type.namedParameterTypes);
_ftList(type.normalParameterTypes);
_ftList(type.optionalParameterTypes);
_ft(type.returnType);
return;
}
assert(type is! InterfaceType);
assert(false);
}
if (type is VoidType) return;
print(type.toString());
assert(false);
}
_ft(type);
return s.toList();
}
List<FormalParameter> _formalParameterListForFunctionType(FunctionType type) {
var namedParameters = type.namedParameterTypes;
var normalParameters = type.normalParameterTypes;
var optionalParameters = type.optionalParameterTypes;
var params = new List<FormalParameter>();
for (int i = 0; i < normalParameters.length; i++) {
FormalParameter fp =
AstBuilder.requiredFormal(_anonymousFormal(normalParameters[i]));
_resolveFormal(fp, normalParameters[i]);
params.add(fp);
}
for (int i = 0; i < optionalParameters.length; i++) {
FormalParameter fp =
AstBuilder.optionalFormal(_anonymousFormal(optionalParameters[i]));
_resolveFormal(fp, optionalParameters[i]);
params.add(fp);
}
for (String k in namedParameters.keys) {
FormalParameter fp =
AstBuilder.namedFormal(_anonymousFormal(namedParameters[k]));
_resolveFormal(fp, namedParameters[k]);
params.add(fp);
}
return params;
}
void _resolveFormal(FormalParameter fp, DartType type) {
ParameterElementImpl fe = new ParameterElementImpl.forNode(fp.identifier);
fe.parameterKind = fp.kind;
fe.type = type;
fp.identifier.staticElement = fe;
fp.identifier.staticType = type;
}
FormalParameter _functionTypedFormal(Identifier v, FunctionType type) {
assert(v != null);
var params = _formalParameterListForFunctionType(type);
var ret = typeNameFromDartType(type.returnType);
return AstBuilder.functionTypedFormal(ret, v, params);
}
NormalFormalParameter _anonymousFormal(DartType type) {
Identifier u = _vm.freshIdentifier("u");
return _typedFormal(u, type);
}
NormalFormalParameter _typedFormal(Identifier v, DartType type) {
if (type is FunctionType) {
return _functionTypedFormal(v, type);
}
assert(type.name != null);
TypeName t = typeNameFromDartType(type);
return AstBuilder.simpleFormal(v, t);
}
SimpleIdentifier freshTypeDefVariable(String hint) {
var t = _vm.freshTypeIdentifier(hint);
var desc = new NewTypeIdDesc(
fromCurrent: true, importedFrom: _currentLibrary, synthetic: true);
addedTypes[t] = desc;
return t;
}
SimpleIdentifier typeParameterFromString(String name) =>
AstBuilder.identifierFromString(name);
SimpleIdentifier freshReferenceToNamedType(DartType type) {
var name = type.name;
assert(name != null);
var id = AstBuilder.identifierFromString(name);
var element = type.element;
id.staticElement = element;
var library = null;
// This can happen for types like (e.g.) void
if (element != null) library = element.library;
var desc = new NewTypeIdDesc(
fromCurrent: _currentLibrary == library,
importedFrom: library,
synthetic: false);
addedTypes[id] = desc;
return id;
}
FunctionTypeAlias _newResolvedTypedef(
FunctionType type, List<TypeParameterType> ftvs) {
// The name of the typedef (unresolved at this point)
// TODO(leafp): better naming.
SimpleIdentifier t = freshTypeDefVariable("CastType");
// The element for the new typedef
var element = new FunctionTypeAliasElementImpl(t.name, 0);
// Fresh type parameter identifiers for the free type variables
List<Identifier> tNames =
ftvs.map((x) => typeParameterFromString(x.name)).toList();
// The type parameters themselves
List<TypeParameter> tps = tNames.map(AstBuilder.typeParameter).toList();
// Allocate the elements for the type parameters, fill in their
// type (which makes no sense) and link up the various elements
// For each type parameter identifier, make an element and a type
// with that element, link the two together, set the identifier element
// to that element, and the identifier type to that type.
List<TypeParameterElement> tElements = tNames.map((x) {
var element = new TypeParameterElementImpl(x.name, 0);
var type = new TypeParameterTypeImpl(element);
element.type = type;
x.staticElement = element;
x.staticType = type;
return element;
}).toList();
// Get the types out from the elements
List<TypeParameterType> tTypes = tElements.map((x) => x.type).toList();
// Take the return type from the original type, and replace the free
// type variables with the fresh type variables
element.returnType = type.returnType.substitute2(tTypes, ftvs);
// Set the type parameter elements
element.typeParameters = tElements;
// Set the parent element to the current compilation unit
element.enclosingElement = _currentUnit;
// This is the type corresponding to the typedef. Note that
// almost all methods on this type delegate to the element, so it
// cannot be safely be used for anything until the element is fully resolved
FunctionTypeImpl substType = new FunctionTypeImpl.forTypedef(element);
element.type = substType;
// Link the type and the element into the identifier for the typedef
t.staticType = substType;
t.staticElement = element;
// Make the formal parameters for the typedef, using the original type
// with the fresh type variables substituted in.
List<FormalParameter> fps =
_formalParameterListForFunctionType(type.substitute2(tTypes, ftvs));
// Get the static elements out of the parameters, and use them to
// initialize the parameters in the element model
element.parameters = fps.map((x) => x.identifier.staticElement).toList();
// Build the return type syntax
TypeName ret = _typeNameFromDartType(substType.returnType);
// This should now be fully resolved (or at least enough so for things
// to work so far).
FunctionTypeAlias alias = AstBuilder.functionTypeAlias(ret, t, tps, fps);
return alias;
}
// I think we can avoid alpha-varying type parameters, since
// the binding forms are so limited, so we just re-use the
// the original names for the formals and the actuals.
TypeName _typeNameFromFunctionType(FunctionType type) {
if (_typedefs.containsKey(type)) {
var alias = _typedefs[type];
var ts = null;
var tpl = alias.typeParameters;
if (tpl != null) {
var ltp = tpl.typeParameters;
ts = new List<TypeName>.from(
ltp.map((t) => _mkNewTypeName(null, t.name, null)));
}
var name = alias.name;
return _mkNewTypeName(type, name, ts);
}
List<TypeParameterType> ftvs = _freeTypeVariables(type);
FunctionTypeAlias alias = _newResolvedTypedef(type, ftvs);
_typedefs[type] = alias;
List<TypeName> args = ftvs.map(_typeNameFromDartType).toList();
TypeName namedType =
_mkNewTypeName(alias.name.staticType, alias.name, args);
return namedType;
}
TypeName _typeNameFromDartType(DartType dType) {
String name = dType.name;
if (name == null || name == "" || dType.isBottom) {
if (dType is FunctionType) return _typeNameFromFunctionType(dType);
_log.severe("No name for type, casting through dynamic");
var d = AstBuilder.identifierFromString("dynamic");
var t = _mkNewTypeName(dType, d, null);
return t;
}
SimpleIdentifier id = freshReferenceToNamedType(dType);
List<TypeName> args = null;
if (dType is ParameterizedType) {
List<DartType> targs = dType.typeArguments;
args = targs.map(_typeNameFromDartType).toList();
}
var t = _mkNewTypeName(dType, id, args);
return t;
}
TypeName _mkNewTypeName(DartType type, Identifier id, List<TypeName> args) {
var t = AstBuilder.typeName(id, args);
t.type = type;
return t;
}
}