Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit 3760837

Browse files
committed
Output some basic type annotations for the Closure Compiler when --closure is set.
1 parent f46ecdc commit 3760837

17 files changed

+854
-26
lines changed

lib/runtime/_operations.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ dart_library.library('dart_runtime/_operations', null, /* Imports */[
9595
let names = getOwnPropertyNames(opts);
9696
// Type is something other than a map
9797
if (names.length == 0) return false;
98-
for (name of names) {
98+
for (var name of names) {
9999
if (!(hasOwnProperty.call(type.named, name))) {
100100
return false;
101101
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
library dev_compiler.src.closure.closure_annotation;
6+
7+
import 'closure_type.dart';
8+
9+
/// Set of closure annotations that can be [toString]ed to a single JsDoc comment.
10+
/// See https://developers.google.com/closure/compiler/docs/js-for-compiler
11+
///
12+
/// TODO(ochafik): Support inclusion of 'normal' comments (including @param comments).
13+
class ClosureAnnotation {
14+
final bool isConst;
15+
final bool isConstructor;
16+
final bool isFinal;
17+
final bool isNoCollapse;
18+
final bool isNoSideEffects;
19+
final bool isOverride;
20+
final bool isPrivate;
21+
final bool isProtected;
22+
final bool isStruct;
23+
final bool isTypedef;
24+
final ClosureType lendsToType;
25+
final ClosureType returnType;
26+
final ClosureType superType;
27+
final ClosureType thisType;
28+
final ClosureType throwsType;
29+
final ClosureType type;
30+
final List<ClosureType> interfaces;
31+
final List<String> templates;
32+
final Map<String, ClosureType> paramTypes;
33+
34+
ClosureAnnotation({
35+
this.interfaces : const [],
36+
this.isConst : false,
37+
this.isConstructor : false,
38+
this.isFinal : false,
39+
this.isNoCollapse : false,
40+
this.isNoSideEffects : false,
41+
this.isOverride : false,
42+
this.isPrivate : false,
43+
this.isProtected : false,
44+
this.isStruct : false,
45+
this.isTypedef : false,
46+
this.lendsToType,
47+
this.paramTypes : const {},
48+
this.returnType,
49+
this.superType,
50+
this.templates : const [],
51+
this.thisType,
52+
this.throwsType,
53+
this.type
54+
});
55+
56+
@override
57+
int get hashCode =>
58+
_cachedString.hashCode;
59+
60+
@override
61+
bool operator==(other) =>
62+
other is ClosureAnnotation
63+
&& _cachedString == other._cachedString;
64+
65+
@override
66+
String toString([String indent = '']) =>
67+
_cachedString.replaceAll('\n', '\n$indent');
68+
69+
String __cachedString;
70+
String get _cachedString {
71+
if (__cachedString == null) {
72+
bool isNonWildcard(ClosureType t) =>
73+
t != null && !t.isAll && !t.isUnknown;
74+
75+
var lines = <String>[];
76+
if (templates != null && templates.isNotEmpty) {
77+
lines.add('@template ${templates.join(', ')}');
78+
}
79+
if (thisType != null) lines.add('@this {$thisType}');
80+
if (isOverride) lines.add('@override');
81+
if (isNoSideEffects) lines.add('@nosideeffects');
82+
if (isNoCollapse) lines.add('@nocollapse');
83+
if (lendsToType != null) lines.add('@lends {$lendsToType}');
84+
85+
{
86+
var typeHolders = <String>[];
87+
if (isPrivate) typeHolders.add('@private');
88+
if (isProtected) typeHolders.add('@protected');
89+
if (isFinal) typeHolders.add('@final');
90+
if (isConst) typeHolders.add('@const');
91+
if (isTypedef) typeHolders.add('@typedef');
92+
if (isNonWildcard(type)) {
93+
if (typeHolders.isEmpty) typeHolders.add('@type');
94+
typeHolders.add('{$type}');
95+
}
96+
if (!typeHolders.isEmpty) lines.add(typeHolders.join(' '));
97+
}
98+
99+
{
100+
List constructorLine = [];
101+
if (isConstructor) constructorLine.add('@constructor');
102+
if (isStruct) constructorLine.add('@struct');
103+
if (isNonWildcard(superType)) {
104+
constructorLine.add('@extends {$superType}');
105+
}
106+
107+
if (constructorLine.isNotEmpty) lines.add(constructorLine.join(' '));
108+
}
109+
110+
for (var interface in interfaces) {
111+
if (isNonWildcard(interface)) lines.add('@implements {$interface}');
112+
}
113+
114+
paramTypes.forEach((String paramName, ClosureType paramType) {
115+
// Must output params even with wildcard type.
116+
lines.add('@param {$paramType} $paramName');
117+
});
118+
if (isNonWildcard(returnType)) lines.add('@return {$returnType}');
119+
if (isNonWildcard(throwsType)) lines.add('@throws {$throwsType}');
120+
121+
if (lines.length == 0) return '';
122+
if (lines.length == 1) return '/** ${lines.single} */';
123+
__cachedString = '/**\n' + lines.map((l) => ' * $l').join('\n') + '\n */';
124+
}
125+
return __cachedString;
126+
}
127+
}

lib/src/closure/closure_codegen.dart

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
library dev_compiler.src.closure.closure_codegen;
6+
7+
import 'package:analyzer/analyzer.dart' show ParameterKind;
8+
import 'package:analyzer/src/generated/element.dart';
9+
10+
import 'closure_annotation.dart';
11+
import 'closure_type.dart';
12+
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
13+
14+
/// Mixin that can generate [ClosureAnnotation]s for Dart elements and types.
15+
abstract class ClosureCodegen {
16+
17+
TypeProvider get types;
18+
19+
/// Must return a JavaScript qualified name that can be used to refer to [type].
20+
String getQualifiedName(ClassElement type);
21+
22+
/// Closure treats ES6 classes as @struct by default.
23+
/// Also, no need to declare @this on their instance members.
24+
bool get generatesES6Classes => true;
25+
26+
ClosureAnnotation closureAnnotationForVariable(VariableElement e) {
27+
return new ClosureAnnotation(
28+
type: _closureTypeForDartType(e.type),
29+
// Note: we don't set isConst here because Closure's constness and
30+
// Dart's are not really compatible.
31+
isFinal: e.isFinal || e.isConst);
32+
}
33+
34+
ClosureAnnotation closureAnnotationForDefaultConstructor(ClassElement e) {
35+
return new ClosureAnnotation(
36+
isConstructor: !generatesES6Classes,
37+
isStruct: !generatesES6Classes,
38+
superType: _closureTypeForDartType(e.supertype),
39+
interfaces: e.interfaces.map(_closureTypeForDartType).toList());
40+
}
41+
42+
ClosureAnnotation closureAnnotationFor(ExecutableElement e, String namedArgsMapName) {
43+
var paramTypes = <String, ClosureType>{};
44+
var namedArgs = <String, ClosureType>{};
45+
for (var param in e.parameters) {
46+
var t = _closureTypeForDartType(param.type);
47+
switch (param.parameterKind) {
48+
case ParameterKind.NAMED:
49+
namedArgs[param.name] = t.orUndefined();
50+
break;
51+
case ParameterKind.POSITIONAL:
52+
paramTypes[param.name] = t.toOptional();
53+
break;
54+
case ParameterKind.REQUIRED:
55+
paramTypes[param.name] = t;
56+
break;
57+
}
58+
}
59+
if (namedArgs.isNotEmpty) {
60+
paramTypes[namedArgsMapName] = new ClosureType.record(namedArgs).toOptional();
61+
}
62+
63+
bool isConstructor = e is ConstructorElement && !e.isFactory && !generatesES6Classes;
64+
bool isFactory = e is ConstructorElement && e.isFactory;
65+
enclosingType() => _closureTypeForClass(e.enclosingElement);
66+
67+
var returnType =
68+
isFactory ? enclosingType() : _closureTypeForDartType(e.returnType);
69+
70+
return new ClosureAnnotation(
71+
isConstructor: isConstructor,
72+
isStruct: isConstructor,
73+
isOverride: e.isOverride,
74+
thisType: e.enclosingElement is ClassElement && !e.isStatic
75+
&& !generatesES6Classes ?
76+
enclosingType() : null,
77+
// Note: Dart and Closure privacy are not compatible: don't set `isPrivate: e.isPrivate`.
78+
paramTypes: paramTypes,
79+
returnType: returnType);
80+
}
81+
82+
Map<DartType, ClosureType> __commonTypes;
83+
Map<DartType, ClosureType> get _commonTypes {
84+
if (__commonTypes == null) {
85+
var numberType = new ClosureType.number().toNullable();
86+
__commonTypes = {
87+
types.intType: numberType,
88+
types.numType: numberType,
89+
types.doubleType: numberType,
90+
types.boolType: new ClosureType.boolean().toNullable(),
91+
types.stringType: new ClosureType.string(),
92+
};
93+
}
94+
return __commonTypes;
95+
}
96+
97+
ClosureType _closureTypeForClass(ClassElement classElement, [DartType type]) {
98+
ClosureType closureType = _commonTypes[type];
99+
if (closureType != null) return closureType;
100+
101+
var fullName = _getFullName(classElement);
102+
switch (fullName) {
103+
// TODO(ochafik): Test DartTypes directly if possible.
104+
case "dart.js.JsArray":
105+
return new ClosureType.array(
106+
type is InterfaceType && type.typeArguments.length == 1
107+
? _closureTypeForDartType(type.typeArguments.single) : null);
108+
case "dart.js.JsObject":
109+
return new ClosureType.map();
110+
case "dart.js.JsFunction":
111+
return new ClosureType.function();
112+
default:
113+
return new ClosureType.type(getQualifiedName(classElement));
114+
}
115+
}
116+
117+
ClosureType _closureTypeForDartType(DartType type) {
118+
// TODO(ochafik): Work out the practical difference between all (*) and unknown (?).
119+
if (type == null) return new ClosureType.unknown();
120+
if (type.isDynamic) return new ClosureType.all();
121+
if (type.isVoid) return null;
122+
if (type is FunctionType) {
123+
var args = []
124+
..addAll(type.normalParameterTypes.map(_closureTypeForDartType))
125+
..addAll(type.optionalParameterTypes.map((t) => _closureTypeForDartType(t).toOptional()));
126+
if (type.namedParameterTypes.isNotEmpty) {
127+
var namedArgs = <String, ClosureType>{};
128+
type.namedParameterTypes.forEach((n, t) {
129+
namedArgs[n] = _closureTypeForDartType(t).orUndefined();
130+
});
131+
args.add(new ClosureType.record(namedArgs).toOptional());
132+
}
133+
return new ClosureType.function(args, _closureTypeForDartType(type.returnType));
134+
}
135+
if (type is InterfaceType) {
136+
return _closureTypeForClass(type.element, type);
137+
}
138+
return new ClosureType.all();
139+
}
140+
141+
/// TODO(ochafik): Use a package-and-file-uri-dependent naming, since libraries can collide.
142+
String _getFullName(ClassElement type) =>
143+
type.library.name == '' ? type.name : '${type.library.name}.${type.name}';
144+
}

lib/src/closure/closure_type.dart

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
library dev_compiler.src.closure.closure_type;
6+
7+
/// Poor-man's representation of a Closure type.
8+
/// See https://developers.google.com/closure/compiler/docs/js-for-compiler
9+
///
10+
/// The goal here is not to completely support Closure's type system, but to
11+
/// be able to generate just the types needed for DDC's JS output.
12+
///
13+
/// TODO(ochafik): Consider convergence with TypeScript, which has no nullability-awareness
14+
/// (see http://www.typescriptlang.org/Handbook).
15+
class ClosureType {
16+
static const ClosureType _ALL = const ClosureType._("*");
17+
static const ClosureType _UNKNOWN = const ClosureType._("?");
18+
19+
final String _representation;
20+
final bool isNullable;
21+
22+
const ClosureType._(this._representation, {this.isNullable: true});
23+
24+
bool get isAll => _representation == "*";
25+
bool get isUnknown => _representation == "?";
26+
27+
@override toString() => _representation;
28+
29+
factory ClosureType.all() => _ALL;
30+
factory ClosureType.unknown() => _UNKNOWN;
31+
32+
factory ClosureType.record(Map<String, ClosureType> fieldTypes) {
33+
var entries = <String>[];
34+
fieldTypes.forEach((n, t) => entries.add('$n: $t'));
35+
return new ClosureType._('{${entries.join(', ')}}');
36+
}
37+
factory ClosureType.function([List<ClosureType> paramTypes, ClosureType returnType]) {
38+
if (paramTypes == null && returnType == null) {
39+
return new ClosureType.type("Function");
40+
}
41+
var suffix = returnType == null ? '' : ':$returnType';
42+
return new ClosureType._(
43+
'function(${paramTypes == null ? '...*' : paramTypes.join(', ')})$suffix');
44+
}
45+
46+
factory ClosureType.map([ClosureType keyType, ClosureType valueType]) =>
47+
new ClosureType._("Object<${keyType ?? _ALL}, ${valueType ?? _ALL}>");
48+
49+
factory ClosureType.type([String className = "Object"]) =>
50+
new ClosureType._(className);
51+
52+
factory ClosureType.array([ClosureType componentType]) =>
53+
new ClosureType._("Array<${componentType ?? _ALL}>");
54+
55+
factory ClosureType.undefined() => new ClosureType._("undefined", isNullable: false);
56+
factory ClosureType.number() => new ClosureType._("number", isNullable: false);
57+
factory ClosureType.boolean() => new ClosureType._("boolean", isNullable: false);
58+
factory ClosureType.string() => new ClosureType._("string");
59+
60+
ClosureType toOptional() =>
61+
new ClosureType._("$this=");
62+
63+
ClosureType toNullable() =>
64+
isNullable ? this : new ClosureType._(
65+
_representation.startsWith('!') ? _representation.substring(1) : "?$this",
66+
isNullable: true);
67+
68+
ClosureType toNonNullable() =>
69+
!isNullable ? this : new ClosureType._(
70+
_representation.startsWith('?') ? _representation.substring(1) : "!$this",
71+
isNullable: false);
72+
73+
/// TODO(ochafik): See which optimizations make sense here (it could be that `(*|undefined)`
74+
/// cannot be optimized to `*` when used to model optional record fields).
75+
ClosureType or(ClosureType other) =>
76+
new ClosureType._("($this|$other)", isNullable: isNullable || other.isNullable);
77+
78+
ClosureType orUndefined() =>
79+
or(new ClosureType.undefined());
80+
}

0 commit comments

Comments
 (0)