blob: 18c13ee7194114d1790937394aa9eaf32e8e5aea [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/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
import '../js_ast/js_ast.dart' as JS;
import 'module_compiler.dart' show CompilerOptions;
import 'js_interop.dart';
/// Mixin with logic to generate [TypeRef]s out of [DartType]s.
abstract class JSTypeRefCodegen {
final _resolved = <DartType, JS.TypeRef>{};
// Mixin dependencies:
CompilerOptions get options;
TypeProvider get types;
LibraryElement get dartJSLibrary;
JS.Identifier get namedArgumentTemp;
JS.Identifier emitLibraryName(LibraryElement e);
/// Finds the qualified path to the type.
JS.TypeRef _emitTopLevelTypeRef(DartType type) {
var e = type.element;
return new JS.TypeRef.qualified([
emitLibraryName(e.library),
new JS.Identifier(getJSExportName(e) ?? e.name)
]);
}
JS.TypeRef emitTypeRef(DartType type) {
if (!options.closure) return null;
return _resolved.putIfAbsent(type, () {
if (type == null) new JS.TypeRef.unknown();
// TODO(ochafik): Consider calling _loader.declareBeforeUse(type.element).
if (type.isBottom || type.isDynamic) new JS.TypeRef.any();
if (type.isVoid) return new JS.TypeRef.void_();
if (type == types.intType) return new JS.TypeRef.number().orNull();
if (type == types.numType) return new JS.TypeRef.number().orNull();
if (type == types.doubleType) return new JS.TypeRef.number().orNull();
if (type == types.boolType) return new JS.TypeRef.boolean().orNull();
if (type == types.stringType) return new JS.TypeRef.string();
if (type is TypeParameterType) return new JS.TypeRef.named(type.name);
if (type is ParameterizedType) {
JS.TypeRef rawType;
if (type is FunctionType && type.name == null) {
var args = <JS.Identifier, JS.TypeRef>{};
for (var param in type.parameters) {
if (param.isNamed) break;
var type = emitTypeRef(param.type);
args[new JS.Identifier(param.name)] =
param.isPositional ? type.toOptional() : type;
}
var namedParamType = emitNamedParamsArgType(type.parameters);
if (namedParamType != null) {
args[namedArgumentTemp] = namedParamType.toOptional();
}
rawType = new JS.TypeRef.function(emitTypeRef(type.returnType), args);
} else {
var jsTypeRef = _getDartJsTypeRef(type);
if (jsTypeRef != null) return jsTypeRef;
rawType = _emitTopLevelTypeRef(type);
}
var typeArgs = _getOwnTypeArguments(type).map(emitTypeRef);
return typeArgs.isEmpty
? rawType
: new JS.TypeRef.generic(rawType, typeArgs);
}
return new JS.TypeRef.unknown();
});
}
JS.TypeRef emitNamedParamsArgType(Iterable<ParameterElement> params) {
if (!options.closure) return null;
var namedArgs = <JS.Identifier, JS.TypeRef>{};
for (ParameterElement param in params) {
if (param.isPositional) continue;
namedArgs[new JS.Identifier(param.name)] =
emitTypeRef(param.type).toOptional();
}
if (namedArgs.isEmpty) return null;
return new JS.TypeRef.record(namedArgs);
}
/// Gets the "own" type arguments of [type].
///
/// Method argument with adhoc unnamed [FunctionType] inherit any type params
/// from their enclosing class:
///
/// class Foo<T> {
/// void method(f()); // f has [T] as type arguments,
/// } // but [] as its "own" type arguments.
Iterable<DartType> _getOwnTypeArguments(ParameterizedType type) sync* {
for (int i = 0, n = type.typeParameters.length; i < n; i++) {
if (type.typeParameters[i].enclosingElement == type.element) {
yield type.typeArguments[i];
}
}
}
/// Special treatment of types from dart:js
/// TODO(ochafik): Is this the right thing to do? And what about package:js?
JS.TypeRef _getDartJsTypeRef(DartType type) {
if (type.element.library == dartJSLibrary) {
switch (type.name) {
case 'JsArray':
return new JS.TypeRef.array(
type is InterfaceType && type.typeArguments.length == 1
? emitTypeRef(type.typeArguments.single)
: null);
case 'JsObject':
return new JS.TypeRef.object();
case 'JsFunction':
return new JS.TypeRef.function();
}
}
return null;
}
}