| Index: lib/src/compiler/type_utilities.dart
|
| diff --git a/lib/src/compiler/type_utilities.dart b/lib/src/compiler/type_utilities.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0614029713dd2091e1e045730f5d863a64630c12
|
| --- /dev/null
|
| +++ b/lib/src/compiler/type_utilities.dart
|
| @@ -0,0 +1,229 @@
|
| +// 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 'dart:collection' show HashMap, HashSet;
|
| +
|
| +import 'package:analyzer/dart/element/element.dart';
|
| +import 'package:analyzer/dart/element/type.dart';
|
| +
|
| +import '../js_ast/js_ast.dart' as JS;
|
| +import '../js_ast/js_ast.dart' show js;
|
| +import 'js_names.dart' as JS;
|
| +
|
| +Set<TypeParameterElement> freeTypeParameters(DartType t) {
|
| + var result = new HashSet<TypeParameterElement>();
|
| + void find(DartType t) {
|
| + if (t is TypeParameterType) {
|
| + result.add(t.element);
|
| + } else if (t is FunctionType) {
|
| + find(t.returnType);
|
| + t.parameters.forEach((p) => find(p.type));
|
| + t.typeFormals.forEach((p) => find(p.bound));
|
| + t.typeFormals.forEach(result.remove);
|
| + } else if (t is InterfaceType) {
|
| + t.typeArguments.forEach(find);
|
| + }
|
| + }
|
| + find(t);
|
| + return result;
|
| +}
|
| +
|
| +/// _CacheTable tracks cache variables for variables that
|
| +/// are emitted in place with a hoisted variable for a cache.
|
| +class _CacheTable {
|
| + /// Mapping from types to their canonical names.
|
| + final _names = new HashMap<DartType, JS.TemporaryId>();
|
| + Iterable<DartType> get keys => _names.keys.toList();
|
| +
|
| + JS.Statement _dischargeType(DartType type) {
|
| + var name = _names.remove(type);
|
| + if (name != null) {
|
| + return js.statement('let #;', [name]);
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /// Emit a list of statements declaring the cache variables for
|
| + /// types tracked by this table. If [typeFilter] is given,
|
| + /// only emit the types listed in the filter.
|
| + List<JS.Statement> discharge([Iterable<DartType> typeFilter]) {
|
| + var decls = <JS.Statement>[];
|
| + var types = typeFilter ?? keys;
|
| + for (var t in types) {
|
| + var stmt = _dischargeType(t);
|
| + if (stmt != null) decls.add(stmt);
|
| + }
|
| + return decls;
|
| + }
|
| +
|
| + bool isNamed(DartType type) => _names.containsKey(type);
|
| +
|
| + /// If [type] is not already in the table, choose a new canonical
|
| + /// variable to contain it. Emit an expression which uses [typeRep] to
|
| + /// lazily initialize the cache in place.
|
| + JS.Expression nameType(DartType type, JS.Expression typeRep) {
|
| + var temp = _names[type];
|
| + if (temp == null) {
|
| + _names[type] = temp = chooseTypeName(type);
|
| + }
|
| + return js.call('# || (# = #)', [temp, temp, typeRep]);
|
| + }
|
| +
|
| + String _typeString(DartType type, {bool flat: false}) {
|
| + if (type is ParameterizedType && type.name != null) {
|
| + var clazz = type.name;
|
| + var params = type.typeArguments;
|
| + if (params == null) return clazz;
|
| + if (params.every((p) => p.isDynamic)) return clazz;
|
| + var paramStrings = params.map(_typeString);
|
| + var paramString = paramStrings.join("\$");
|
| + return "${clazz}Of${paramString}";
|
| + }
|
| + if (type is FunctionType) {
|
| + if (flat) return "Fn";
|
| + var rType = _typeString(type.returnType, flat: true);
|
| + var paramStrings = type.normalParameterTypes
|
| + .take(3)
|
| + .map((p) => _typeString(p, flat: true));
|
| + var paramString = paramStrings.join("And");
|
| + var count = type.normalParameterTypes.length;
|
| + if (count > 3 ||
|
| + type.namedParameterTypes.isNotEmpty ||
|
| + type.optionalParameterTypes.isNotEmpty) {
|
| + paramString = "${paramString}__";
|
| + } else if (count == 0) {
|
| + paramString = "Void";
|
| + }
|
| + return "${paramString}To${rType}";
|
| + }
|
| + if (type is TypeParameterType) return type.name;
|
| + if (type.name != null) return type.name;
|
| + return "type";
|
| + }
|
| +
|
| + /// Heuristically choose a good name for the cache and generator
|
| + /// variables.
|
| + JS.Identifier chooseTypeName(DartType type) {
|
| + return new JS.TemporaryId(_typeString(type));
|
| + }
|
| +}
|
| +
|
| +/// _GeneratorTable tracks types which have been
|
| +/// named and hoisted.
|
| +class _GeneratorTable extends _CacheTable {
|
| + final _defs = new HashMap<DartType, JS.Expression>();
|
| +
|
| + JS.Statement _dischargeType(DartType t) {
|
| + var name = _names.remove(t);
|
| + if (name != null) {
|
| + JS.Expression init = _defs.remove(t);
|
| + assert(init != null);
|
| + return js.statement(
|
| + 'let # = () => ((# = dart.constFn(#))());', [name, name, init]);
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /// If [type] does not already have a generator name chosen for it,
|
| + /// assign it one, using [typeRep] as the initializer for it.
|
| + /// Emit an expression which calls the generator name.
|
| + JS.Expression nameType(DartType type, JS.Expression typeRep) {
|
| + var temp = _names[type];
|
| + if (temp == null) {
|
| + _names[type] = temp = chooseTypeName(type);
|
| + _defs[type] = typeRep;
|
| + }
|
| + return js.call('#()', [temp]);
|
| + }
|
| +}
|
| +
|
| +class TypeTable {
|
| + /// Cache variable names for types emitted in place.
|
| + final _cacheNames = new _CacheTable();
|
| +
|
| + /// Cache variable names for definite function types emitted in place.
|
| + final _definiteCacheNames = new _CacheTable();
|
| +
|
| + /// Generator variable names for hoisted types.
|
| + final _generators = new _GeneratorTable();
|
| +
|
| + /// Generator variable names for hoisted definite function types.
|
| + final _definiteGenerators = new _GeneratorTable();
|
| +
|
| + /// Mapping from type parameters to the types which must have their
|
| + /// cache/generator variables discharged at the binding site for the
|
| + /// type variable since the type definition depends on the type
|
| + /// parameter.
|
| + final _scopeDependencies =
|
| + new HashMap<TypeParameterElement, List<DartType>>();
|
| +
|
| + /// Emit a list of statements declaring the cache variables and generator
|
| + /// definitions tracked by the table. If [formals] is present, only
|
| + /// emit the definitions which depend on the formals.
|
| + List<JS.Statement> discharge([List<TypeParameterElement> formals]) {
|
| + var filter = formals?.expand((p) => _scopeDependencies[p] ?? []);
|
| + var stmts = [
|
| + _cacheNames,
|
| + _definiteCacheNames,
|
| + _generators,
|
| + _definiteGenerators
|
| + ].expand((c) => c.discharge(filter)).toList();
|
| + formals?.forEach(_scopeDependencies.remove);
|
| + return stmts;
|
| + }
|
| +
|
| + /// Record the dependencies of the type on its free variables
|
| + bool recordScopeDependencies(DartType type) {
|
| + var fvs = freeTypeParameters(type);
|
| + // TODO(leafp): This is a hack to avoid trying to hoist out of
|
| + // generic functions and generic function types. This often degrades
|
| + // readability to little or no benefit. It would be good to do this
|
| + // when we know that we can hoist it to an outer scope, but for
|
| + // now we just disable it.
|
| + if (fvs.any((i) => i.enclosingElement is FunctionTypedElement)) {
|
| + return true;
|
| + }
|
| + void addScope(TypeParameterElement i) {
|
| + List<DartType> types = _scopeDependencies[i];
|
| + if (types == null) _scopeDependencies[i] = types = [];
|
| + types.add(type);
|
| + }
|
| + fvs.forEach(addScope);
|
| + return false;
|
| + }
|
| +
|
| + /// Given a type [type], and a JS expression [typeRep] which implements it,
|
| + /// add the type and its representation to the table, returning an
|
| + /// expression which implements the type (but which caches the value).
|
| + ///
|
| + /// If [hoist] is true, then the JS representation will be hoisted up
|
| + /// as far as possible and shared between instances of the type. For
|
| + /// example, the generated code for dart.is(x, type) ends up as:
|
| + /// let cacheVar;
|
| + /// ...
|
| + /// dart.is(x, (cacheVar || cacheVar = type))
|
| + ///
|
| + /// If [hoist] is false, the cache variable will be hoisted up as
|
| + /// far as possible and shared between instances of the type, but the
|
| + /// initializer expression will be emitted in place. The generated code
|
| + /// for dart.is(x, type) in this case ends up as:
|
| + /// let generator = () => (generator = dart.constFn(type))()
|
| + /// ....
|
| + /// dart.is(x, generator())
|
| + ///
|
| + /// The boolean parameter [definite] distinguishes between definite function
|
| + /// types and other types (since the same DartType may have different
|
| + /// representations as definite and indefinite function types).
|
| + JS.Expression nameType(DartType type, JS.Expression typeRep,
|
| + {bool hoistType, bool definite: false}) {
|
| + assert(hoistType != null);
|
| + var table = hoistType
|
| + ? (definite ? _definiteGenerators : _generators)
|
| + : (definite ? _definiteCacheNames : _cacheNames);
|
| + if (!table.isNamed(type)) {
|
| + if (recordScopeDependencies(type)) return typeRep;
|
| + }
|
| + return table.nameType(type, typeRep);
|
| + }
|
| +}
|
|
|