Skip to content

Commit 8ebe4c8

Browse files
committed
Merge pull request #286 from dart-lang/closure-annotations
Output Closure annotations when --closure (very experimental feature)
2 parents 798076d + 59cf5dd commit 8ebe4c8

17 files changed

+886
-28
lines changed

pkg/dev_compiler/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: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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({this.interfaces: const [], this.isConst: false,
35+
this.isConstructor: false, this.isFinal: false, this.isNoCollapse: false,
36+
this.isNoSideEffects: false, this.isOverride: false,
37+
this.isPrivate: false, this.isProtected: false, this.isStruct: false,
38+
this.isTypedef: false, this.lendsToType, this.paramTypes: const {},
39+
this.returnType, this.superType, this.templates: const [], this.thisType,
40+
this.throwsType, this.type});
41+
42+
@override
43+
int get hashCode => _cachedString.hashCode;
44+
45+
@override
46+
bool operator ==(other) =>
47+
other is ClosureAnnotation && _cachedString == other._cachedString;
48+
49+
@override
50+
String toString([String indent = '']) =>
51+
_cachedString.replaceAll('\n', '\n$indent');
52+
53+
String __cachedString;
54+
String get _cachedString {
55+
if (__cachedString == null) {
56+
bool isNonWildcard(ClosureType t) =>
57+
t != null && !t.isAll && !t.isUnknown;
58+
59+
var lines = <String>[];
60+
if (templates != null && templates.isNotEmpty) {
61+
lines.add('@template ${templates.join(', ')}');
62+
}
63+
if (thisType != null) lines.add('@this {$thisType}');
64+
if (isOverride) lines.add('@override');
65+
if (isNoSideEffects) lines.add('@nosideeffects');
66+
if (isNoCollapse) lines.add('@nocollapse');
67+
if (lendsToType != null) lines.add('@lends {$lendsToType}');
68+
69+
{
70+
var typeHolders = <String>[];
71+
if (isPrivate) typeHolders.add('@private');
72+
if (isProtected) typeHolders.add('@protected');
73+
if (isFinal) typeHolders.add('@final');
74+
if (isConst) typeHolders.add('@const');
75+
if (isTypedef) typeHolders.add('@typedef');
76+
if (isNonWildcard(type)) {
77+
if (typeHolders.isEmpty) typeHolders.add('@type');
78+
typeHolders.add('{$type}');
79+
}
80+
if (!typeHolders.isEmpty) lines.add(typeHolders.join(' '));
81+
}
82+
83+
{
84+
List constructorLine = [];
85+
if (isConstructor) constructorLine.add('@constructor');
86+
if (isStruct) constructorLine.add('@struct');
87+
if (isNonWildcard(superType)) {
88+
constructorLine.add('@extends {$superType}');
89+
}
90+
91+
if (constructorLine.isNotEmpty) lines.add(constructorLine.join(' '));
92+
}
93+
94+
for (var interface in interfaces) {
95+
if (isNonWildcard(interface)) lines.add('@implements {$interface}');
96+
}
97+
98+
paramTypes.forEach((String paramName, ClosureType paramType) {
99+
// Must output params even with wildcard type.
100+
lines.add('@param {$paramType} $paramName');
101+
});
102+
if (isNonWildcard(returnType)) lines.add('@return {$returnType}');
103+
if (isNonWildcard(throwsType)) lines.add('@throws {$throwsType}');
104+
105+
if (lines.length == 0) return '';
106+
if (lines.length == 1) return '/** ${lines.single} */';
107+
__cachedString = '/**\n' + lines.map((l) => ' * $l').join('\n') + '\n */';
108+
}
109+
return __cachedString;
110+
}
111+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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 ClosureAnnotator {
16+
TypeProvider get types;
17+
18+
/// Must return a JavaScript qualified name that can be used to refer to [type].
19+
String getQualifiedName(TypeDefiningElement type);
20+
21+
ClosureAnnotation closureAnnotationForVariable(VariableElement e) =>
22+
new ClosureAnnotation(type: _closureTypeForDartType(e.type),
23+
// Note: we don't set isConst here because Closure's constness and
24+
// Dart's are not really compatible.
25+
isFinal: e.isFinal || e.isConst);
26+
27+
/// We don't use Closure's `@typedef` annotations
28+
ClosureAnnotation closureAnnotationForTypeDef(FunctionTypeAliasElement e) =>
29+
new ClosureAnnotation(
30+
type: _closureTypeForDartType(e.type, forceTypeDefExpansion: true),
31+
isTypedef: true);
32+
33+
ClosureAnnotation closureAnnotationForDefaultConstructor(ClassElement e) =>
34+
new ClosureAnnotation(
35+
superType: _closureTypeForDartType(e.supertype),
36+
interfaces: e.interfaces.map(_closureTypeForDartType).toList());
37+
38+
ClosureAnnotation closureAnnotationFor(
39+
ExecutableElement e, String namedArgsMapName) {
40+
var paramTypes = <String, ClosureType>{};
41+
var namedArgs = <String, ClosureType>{};
42+
for (var param in e.parameters) {
43+
var t = _closureTypeForDartType(param.type);
44+
switch (param.parameterKind) {
45+
case ParameterKind.NAMED:
46+
namedArgs[param.name] = t.orUndefined();
47+
break;
48+
case ParameterKind.POSITIONAL:
49+
paramTypes[param.name] = t.toOptional();
50+
break;
51+
case ParameterKind.REQUIRED:
52+
paramTypes[param.name] = t;
53+
break;
54+
}
55+
}
56+
if (namedArgs.isNotEmpty) {
57+
paramTypes[namedArgsMapName] =
58+
new ClosureType.record(namedArgs).toOptional();
59+
}
60+
61+
var returnType = e is ConstructorElement
62+
? (e.isFactory ? _closureTypeForClass(e.enclosingElement) : null)
63+
: _closureTypeForDartType(e.returnType);
64+
65+
return new ClosureAnnotation(isOverride: e.isOverride,
66+
// Note: Dart and Closure privacy are not compatible: don't set `isPrivate: e.isPrivate`.
67+
paramTypes: paramTypes, returnType: returnType);
68+
}
69+
70+
Map<DartType, ClosureType> __commonTypes;
71+
Map<DartType, ClosureType> get _commonTypes {
72+
if (__commonTypes == null) {
73+
var numberType = new ClosureType.number().toNullable();
74+
__commonTypes = {
75+
types.intType: numberType,
76+
types.numType: numberType,
77+
types.doubleType: numberType,
78+
types.boolType: new ClosureType.boolean().toNullable(),
79+
types.stringType: new ClosureType.string(),
80+
};
81+
}
82+
return __commonTypes;
83+
}
84+
85+
ClosureType _closureTypeForClass(ClassElement classElement, [DartType type]) {
86+
ClosureType closureType = _commonTypes[type];
87+
if (closureType != null) return closureType;
88+
89+
var fullName = _getFullName(classElement);
90+
switch (fullName) {
91+
// TODO(ochafik): Test DartTypes directly if possible.
92+
case "dart.js.JsArray":
93+
return new ClosureType.array(
94+
type is InterfaceType && type.typeArguments.length == 1
95+
? _closureTypeForDartType(type.typeArguments.single)
96+
: null);
97+
case "dart.js.JsObject":
98+
return new ClosureType.map();
99+
case "dart.js.JsFunction":
100+
return new ClosureType.function();
101+
default:
102+
return new ClosureType.type(getQualifiedName(classElement));
103+
}
104+
}
105+
106+
ClosureType _closureTypeForDartType(DartType type,
107+
{bool forceTypeDefExpansion: false}) {
108+
if (type == null || type.isBottom || type.isDynamic) {
109+
return new ClosureType.unknown();
110+
}
111+
if (type.isVoid) return null;
112+
if (type is FunctionType) {
113+
if (!forceTypeDefExpansion && type.element.name != '') {
114+
return new ClosureType.type(getQualifiedName(type.element));
115+
}
116+
117+
var args = []
118+
..addAll(type.normalParameterTypes.map(_closureTypeForDartType))
119+
..addAll(type.optionalParameterTypes
120+
.map((t) => _closureTypeForDartType(t).toOptional()));
121+
if (type.namedParameterTypes.isNotEmpty) {
122+
var namedArgs = <String, ClosureType>{};
123+
type.namedParameterTypes.forEach((n, t) {
124+
namedArgs[n] = _closureTypeForDartType(t).orUndefined();
125+
});
126+
args.add(new ClosureType.record(namedArgs).toOptional());
127+
}
128+
return new ClosureType.function(
129+
args, _closureTypeForDartType(type.returnType));
130+
}
131+
if (type is InterfaceType) {
132+
return _closureTypeForClass(type.element, type);
133+
}
134+
return new ClosureType.unknown();
135+
}
136+
137+
/// TODO(ochafik): Use a package-and-file-uri-dependent naming, since libraries can collide.
138+
String _getFullName(ClassElement type) =>
139+
type.library.name == '' ? type.name : '${type.library.name}.${type.name}';
140+
}
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)