Index: pkg/dev_compiler/lib/src/compiler/code_generator.dart |
diff --git a/pkg/dev_compiler/lib/src/compiler/code_generator.dart b/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
index 4e5b3a264740a048f8c496e55e37fd343a54f118..0151900afd6df6da23c1411172288eb7f688fbbf 100644 |
--- a/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
+++ b/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
@@ -31,8 +31,7 @@ import 'package:analyzer/src/summary/summarize_ast.dart' |
import 'package:analyzer/src/summary/summarize_elements.dart' |
show PackageBundleAssembler; |
import 'package:analyzer/src/summary/summary_sdk.dart'; |
-import 'package:analyzer/src/task/strong/ast_properties.dart' |
- show isDynamicInvoke, setIsDynamicInvoke, getImplicitAssignmentCast; |
+import 'package:analyzer/src/task/strong/ast_properties.dart'; |
import 'package:path/path.dart' show isWithin, relative, separator; |
import '../closure/closure_annotator.dart' show ClosureAnnotator; |
@@ -195,6 +194,8 @@ class CodeGenerator extends Object |
/// unit. |
final virtualFields = new VirtualFieldModel(); |
+ final _usedCovariantPrivateMembers = new HashSet<ExecutableElement>(); |
+ |
CodeGenerator( |
AnalysisContext c, this.summaryData, this.options, this._extensionTypes) |
: context = c, |
@@ -289,6 +290,10 @@ class CodeGenerator extends Object |
throw new StateError('Can only call emitModule once.'); |
} |
+ for (var unit in compilationUnits) { |
+ _usedCovariantPrivateMembers.addAll(getCovariantPrivateMembers(unit)); |
+ } |
+ |
// Transform the AST to make coercions explicit. |
compilationUnits = CoercionReifier.reify(compilationUnits); |
@@ -679,17 +684,38 @@ class CodeGenerator extends Object |
Expression fromExpr = node.expression; |
var from = getStaticType(fromExpr); |
var to = node.type.type; |
- |
JS.Expression jsFrom = _visit(fromExpr); |
- // Skip the cast if it's not needed. |
- if (rules.isSubtypeOf(from, to)) return jsFrom; |
+ // If the check was put here by static analysis to ensure soundness, we |
+ // can't skip it. This happens because of unsound covariant generics: |
+ // |
+ // typedef F<T>(T t); |
+ // class C<T> { |
+ // F<T> f; |
+ // add(T t) { |
+ // // required check `t as T` |
+ // } |
+ // } |
+ // main() { |
+ // C<Object> c = new C<int>()..f = (int x) => x.isEven; |
+ // c.f('hi'); // required check `c.f as F<Object>` |
+ // c.add('hi); |
+ // } |
+ // |
+ // NOTE: due to implementation details, we do not currently reify the the |
+ // `C<T>.add` check in CoercionReifier, so it does not reach this point; |
+ // rather we check for it explicitly when emitting methods and fields. |
+ // However we do reify the `c.f` check, so we must not eliminate it. |
+ var isRequiredForSoundness = CoercionReifier.isRequiredForSoundness(node); |
+ if (!isRequiredForSoundness && rules.isSubtypeOf(from, to)) return jsFrom; |
// All Dart number types map to a JS double. |
if (typeRep.isNumber(from) && typeRep.isNumber(to)) { |
// Make sure to check when converting to int. |
if (from != types.intType && to == types.intType) { |
// TODO(jmesserly): fuse this with notNull check. |
+ // TODO(jmesserly): this does not correctly distinguish user casts from |
+ // required-for-soundness casts. |
return _callHelper('asInt(#)', jsFrom); |
} |
@@ -697,14 +723,8 @@ class CodeGenerator extends Object |
return jsFrom; |
} |
- var type = _emitType(to, |
- nameType: options.nameTypeTests || options.hoistTypeTests, |
- hoistType: options.hoistTypeTests); |
- if (CoercionReifier.isImplicitCast(node)) { |
- return js.call('#._check(#)', [type, jsFrom]); |
- } else { |
- return js.call('#.as(#)', [type, jsFrom]); |
- } |
+ var code = isRequiredForSoundness ? '#._check(#)' : '#.as(#)'; |
+ return js.call(code, [_emitType(to), jsFrom]); |
} |
@override |
@@ -720,9 +740,7 @@ class CodeGenerator extends Object |
} else { |
// Always go through a runtime helper, because implicit interfaces. |
- var castType = _emitType(type, |
- nameType: options.nameTypeTests || options.hoistTypeTests, |
- hoistType: options.hoistTypeTests); |
+ var castType = _emitType(type); |
result = js.call('#.is(#)', [castType, lhs]); |
} |
@@ -800,9 +818,16 @@ class CodeGenerator extends Object |
// The resulting 'class' is a mixable class in this case. |
bool isMixinAlias = supertype.isObject && classElem.mixins.length == 1; |
+ // TODO(jmesserly): what do we do if the mixin alias has implied superclass |
+ // covariance checks (due to new interfaces)? We can't add them without |
+ // messing up the inheritance chain and breaking the ability of the mixin |
+ // alias to be mixed in elsewhere. We're going to need something special, |
+ // like adding these checks when we copy in the methods. |
+ var jsMethods = <JS.Method>[]; |
+ _emitSuperclassCovarianceChecks(node, jsMethods); |
var classExpr = isMixinAlias |
? _emitClassHeritage(classElem) |
- : _emitClassExpression(classElem, []); |
+ : _emitClassExpression(classElem, jsMethods); |
var className = isGeneric |
? new JS.Identifier(classElem.name) |
: _emitTopLevelName(classElem); |
@@ -886,8 +911,12 @@ class CodeGenerator extends Object |
} |
var savedClassProperties = _classProperties; |
- _classProperties = |
- new ClassPropertyModel.build(_extensionTypes, virtualFields, classElem); |
+ _classProperties = new ClassPropertyModel.build( |
+ _extensionTypes, |
+ virtualFields, |
+ classElem, |
+ getClassCovariantParameters(node), |
+ _usedCovariantPrivateMembers); |
var jsCtors = _defineConstructors(classElem, className, fields, ctors); |
var classExpr = _emitClassExpression(classElem, _emitClassMethods(node), |
@@ -1423,9 +1452,88 @@ class CodeGenerator extends Object |
// Add all of the super helper methods |
jsMethods.addAll(_superHelpers.values); |
+ _emitSuperclassCovarianceChecks(node, jsMethods); |
return jsMethods.where((m) => m != null).toList(growable: false); |
} |
+ void _emitSuperclassCovarianceChecks( |
+ Declaration node, List<JS.Method> methods) { |
+ var covariantParams = getSuperclassCovariantParameters(node); |
+ if (covariantParams == null) return; |
+ |
+ for (var member in covariantParams.map((p) => p.enclosingElement).toSet()) { |
+ var name = _declareMemberName(member); |
+ if (member is PropertyAccessorElement) { |
+ var param = member.parameters[0]; |
+ assert(covariantParams.contains(param)); |
+ methods.add(new JS.Method( |
+ name, |
+ js.call('function(x) { return super.#(#._check(x)); }', |
+ [name, _emitType(param.type)]), |
+ isSetter: true)); |
+ methods.add(new JS.Method( |
+ name, js.call('function() { return super.#; }', [name]), |
+ isGetter: true)); |
+ } else if (member is MethodElement) { |
+ var type = member.type; |
+ |
+ var body = <JS.Statement>[]; |
+ var typeFormals = _emitTypeFormals(type.typeFormals); |
+ if (type.typeFormals.any(covariantParams.contains)) { |
+ body.add(js.statement( |
+ '#.checkBounds([#]);', [_emitType(type), typeFormals])); |
+ } |
+ |
+ var jsParams = <JS.Parameter>[]; |
+ bool foundNamedParams = false; |
+ for (var param in member.parameters) { |
+ JS.Parameter jsParam; |
+ if (param.kind == ParameterKind.NAMED) { |
+ foundNamedParams = true; |
+ if (covariantParams.contains(param)) { |
+ var name = _propertyName(param.name); |
+ body.add(js.statement('if (# in #) #._check(#.#);', [ |
+ name, |
+ namedArgumentTemp, |
+ _emitType(param.type), |
+ namedArgumentTemp, |
+ name |
+ ])); |
+ } |
+ } else { |
+ jsParam = _emitParameter(param); |
+ jsParams.add(jsParam); |
+ if (covariantParams.contains(param)) { |
+ if (param.kind == ParameterKind.POSITIONAL) { |
+ body.add(js.statement('if (# !== void 0) #._check(#);', |
+ [jsParam, _emitType(param.type), jsParam])); |
+ } else { |
+ body.add(js.statement( |
+ '#._check(#);', [_emitType(param.type), jsParam])); |
+ } |
+ } |
+ } |
+ } |
+ |
+ if (foundNamedParams) jsParams.add(namedArgumentTemp); |
+ |
+ if (typeFormals.isEmpty) { |
+ body.add(js.statement('return super.#(#);', [name, jsParams])); |
+ } else { |
+ body.add(js.statement( |
+ 'return super.#(#)(#);', [name, typeFormals, jsParams])); |
+ } |
+ var fn = new JS.Fun(jsParams, new JS.Block(body), |
+ typeParams: typeFormals, returnType: emitTypeRef(type.returnType)); |
+ methods.add(new JS.Method(name, _makeGenericFunction(fn))); |
+ } else { |
+ throw new StateError( |
+ 'unable to generate a covariant check for element: `$member` ' |
+ '(${member.runtimeType})'); |
+ } |
+ } |
+ } |
+ |
/// Emits a Dart factory constructor to a JS static method. |
JS.Method _emitFactoryConstructor(ConstructorDeclaration node) { |
var element = node.element; |
@@ -1566,9 +1674,19 @@ class CodeGenerator extends Object |
? [new JS.Super(), name] |
: [new JS.This(), virtualField]; |
- result.add(new JS.Method( |
- name, js.call('function(value) { #[#] = value; }', args), |
- isSetter: true)); |
+ String jsCode; |
+ var setter = element.setter; |
+ var covariantParams = _classProperties.covariantParameters; |
+ if (setter != null && |
+ covariantParams != null && |
+ covariantParams.contains(setter.parameters[0])) { |
+ args.add(_emitType(setter.parameters[0].type)); |
+ jsCode = 'function(value) { #[#] = #._check(value); }'; |
+ } else { |
+ jsCode = 'function(value) { #[#] = value; }'; |
+ } |
+ |
+ result.add(new JS.Method(name, js.call(jsCode, args), isSetter: true)); |
} |
return result; |
@@ -1927,8 +2045,7 @@ class CodeGenerator extends Object |
var type = _emitAnnotatedFunctionType(reifiedType, node.metadata, |
parameters: node.parameters?.parameters, |
- nameType: options.hoistSignatureTypes, |
- hoistType: options.hoistSignatureTypes, |
+ nameType: false, |
definite: true); |
if (needsSignature) { |
@@ -1969,8 +2086,7 @@ class CodeGenerator extends Object |
var memberName = _constructorName(element); |
var type = _emitAnnotatedFunctionType(element.type, node.metadata, |
parameters: node.parameters.parameters, |
- nameType: options.hoistSignatureTypes, |
- hoistType: options.hoistSignatureTypes, |
+ nameType: false, |
definite: true); |
var property = new JS.Property(memberName, type); |
tCtors.add(property); |
@@ -2271,6 +2387,8 @@ class CodeGenerator extends Object |
var parameters = _parametersOf(node); |
if (parameters == null) return null; |
+ var covariantParams = _classProperties?.covariantParameters; |
+ |
var body = <JS.Statement>[]; |
for (var param in parameters.parameters) { |
var jsParam = _emitSimpleIdentifier(param.identifier); |
@@ -2297,39 +2415,16 @@ class CodeGenerator extends Object |
} |
} |
- // TODO(jmesserly): various problems here, see: |
- // https://github.com/dart-lang/sdk/issues/27259 |
- var paramType = |
- resolutionMap.elementDeclaredByFormalParameter(param).type; |
- if (node is MethodDeclaration && |
- (resolutionMap.elementDeclaredByFormalParameter(param).isCovariant || |
- _unsoundCovariant(paramType, true))) { |
- var castType = _emitType(paramType, |
- nameType: options.nameTypeTests || options.hoistTypeTests, |
- hoistType: options.hoistTypeTests); |
+ var paramElement = resolutionMap.elementDeclaredByFormalParameter(param); |
+ if (paramElement.isCovariant || |
+ covariantParams != null && covariantParams.contains(paramElement)) { |
+ var castType = _emitType(paramElement.type); |
body.add(js.statement('#._check(#);', [castType, jsParam])); |
} |
} |
return body.isEmpty ? null : _statement(body); |
} |
- /// Given a type [t], return whether or not t is unsoundly covariant. |
- /// If [contravariant] is true, then t appears in a contravariant |
- /// position. |
- bool _unsoundCovariant(DartType t, bool contravariant) { |
- if (t is TypeParameterType) { |
- return contravariant && t.element.enclosingElement is ClassElement; |
- } |
- if (t is FunctionType) { |
- if (_unsoundCovariant(t.returnType, contravariant)) return true; |
- return t.parameters.any((p) => _unsoundCovariant(p.type, !contravariant)); |
- } |
- if (t is ParameterizedType) { |
- return t.typeArguments.any((t) => _unsoundCovariant(t, contravariant)); |
- } |
- return false; |
- } |
- |
JS.Expression _defaultParamValue(FormalParameter param) { |
if (param is DefaultFormalParameter && param.defaultValue != null) { |
return _visit(param.defaultValue); |
@@ -2600,10 +2695,21 @@ class CodeGenerator extends Object |
: new JS.Block( |
[_emitGeneratorFunctionBody(element, parameters, body).toReturn()]); |
var typeFormals = _emitTypeFormals(type.typeFormals); |
+ |
var returnType = emitTypeRef(type.returnType); |
if (type.typeFormals.isNotEmpty) { |
- code = new JS.Block( |
- [new JS.Block(_typeTable.discharge(type.typeFormals)), code]); |
+ var block = <JS.Statement>[ |
+ new JS.Block(_typeTable.discharge(type.typeFormals)) |
+ ]; |
+ |
+ var covariantParams = _classProperties?.covariantParameters; |
+ if (covariantParams != null && |
+ type.typeFormals.any(covariantParams.contains)) { |
+ block.add(js.statement('#.checkBounds(#);', |
+ [_emitType(type), new JS.ArrayInitializer(typeFormals)])); |
+ } |
+ |
+ code = new JS.Block(block..add(code)); |
} |
if (element.isOperator && element.name == '[]=' && formals.isNotEmpty) { |
@@ -2861,9 +2967,9 @@ class CodeGenerator extends Object |
} |
JS.Expression _emitAnnotatedType(DartType type, List<Annotation> metadata, |
- {bool nameType: true, bool hoistType: true}) { |
+ {bool nameType: true}) { |
metadata ??= []; |
- var typeName = _emitType(type, nameType: nameType, hoistType: hoistType); |
+ var typeName = _emitType(type, nameType: nameType); |
return _emitAnnotatedResult(typeName, metadata); |
} |
@@ -2879,7 +2985,7 @@ class CodeGenerator extends Object |
JS.ArrayInitializer _emitTypeNames( |
List<DartType> types, List<FormalParameter> parameters, |
- {bool nameType: true, bool hoistType: true}) { |
+ {bool nameType: true}) { |
var result = <JS.Expression>[]; |
for (int i = 0; i < types.length; ++i) { |
var metadata = parameters != null |
@@ -2906,16 +3012,13 @@ class CodeGenerator extends Object |
{List<FormalParameter> parameters, |
bool lowerTypedef: false, |
bool nameType: true, |
- bool hoistType: true, |
definite: false}) { |
var parameterTypes = type.normalParameterTypes; |
var optionalTypes = type.optionalParameterTypes; |
var namedTypes = type.namedParameterTypes; |
- var rt = |
- _emitType(type.returnType, nameType: nameType, hoistType: hoistType); |
+ var rt = _emitType(type.returnType, nameType: nameType); |
- var ra = _emitTypeNames(parameterTypes, parameters, |
- nameType: nameType, hoistType: hoistType); |
+ var ra = _emitTypeNames(parameterTypes, parameters, nameType: nameType); |
List<JS.Expression> typeParts; |
if (namedTypes.isNotEmpty) { |
@@ -2927,7 +3030,7 @@ class CodeGenerator extends Object |
assert(namedTypes.isEmpty); |
var oa = _emitTypeNames( |
optionalTypes, parameters?.sublist(parameterTypes.length), |
- nameType: nameType, hoistType: hoistType); |
+ nameType: nameType); |
typeParts = [rt, ra, oa]; |
} else { |
typeParts = [rt, ra]; |
@@ -2960,8 +3063,7 @@ class CodeGenerator extends Object |
} |
fullType = _callHelper(helperCall, [typeParts]); |
if (!nameType) return fullType; |
- return _typeTable.nameType(type, fullType, |
- hoistType: hoistType, definite: definite); |
+ return _typeTable.nameType(type, fullType, definite: definite); |
} |
JS.Expression _emitAnnotatedFunctionType( |
@@ -2969,13 +3071,11 @@ class CodeGenerator extends Object |
{List<FormalParameter> parameters, |
bool lowerTypedef: false, |
bool nameType: true, |
- bool hoistType: true, |
bool definite: false}) { |
var result = _emitFunctionType(type, |
parameters: parameters, |
lowerTypedef: lowerTypedef, |
nameType: nameType, |
- hoistType: hoistType, |
definite: definite); |
return _emitAnnotatedResult(result, metadata); |
} |
@@ -2984,10 +3084,8 @@ class CodeGenerator extends Object |
/// |
/// If [nameType] is true, then the type will be named. In addition, |
/// if [hoistType] is true, then the named type will be hoisted. |
- JS.Expression _emitConstructorAccess(DartType type, |
- {bool nameType: true, bool hoistType: true}) { |
- return _emitJSInterop(type.element) ?? |
- _emitType(type, nameType: nameType, hoistType: hoistType); |
+ JS.Expression _emitConstructorAccess(DartType type, {bool nameType: true}) { |
+ return _emitJSInterop(type.element) ?? _emitType(type, nameType: nameType); |
} |
/// Emits an expression that lets you access statics on a [type] from code. |
@@ -3031,7 +3129,6 @@ class CodeGenerator extends Object |
{bool lowerTypedef: false, |
bool lowerGeneric: false, |
bool nameType: true, |
- bool hoistType: true, |
ClassElement subClass, |
JS.Expression className}) { |
// The void and dynamic types are not defined in core. |
@@ -3079,7 +3176,7 @@ class CodeGenerator extends Object |
// go through use similar logic as generic classes. This makes them |
// different from universal function types. |
return _emitFunctionType(type as FunctionType, |
- lowerTypedef: lowerTypedef, nameType: nameType, hoistType: hoistType); |
+ lowerTypedef: lowerTypedef, nameType: nameType); |
} |
if (type is TypeParameterType) { |
@@ -3094,19 +3191,14 @@ class CodeGenerator extends Object |
Iterable jsArgs = null; |
if (args.any((a) => !a.isDynamic)) { |
jsArgs = args.map((x) => _emitType(x, |
- nameType: nameType, |
- hoistType: hoistType, |
- subClass: subClass, |
- className: className)); |
+ nameType: nameType, subClass: subClass, className: className)); |
} else if (lowerGeneric || element == subClass) { |
jsArgs = []; |
} |
if (jsArgs != null) { |
var genericName = _emitTopLevelNameNoInterop(element, suffix: '\$'); |
var typeRep = js.call('#(#)', [genericName, jsArgs]); |
- return nameType |
- ? _typeTable.nameType(type, typeRep, hoistType: hoistType) |
- : typeRep; |
+ return nameType ? _typeTable.nameType(type, typeRep) : typeRep; |
} |
} |
@@ -3162,7 +3254,7 @@ class CodeGenerator extends Object |
..staticElement = element |
..staticType = getStaticType(lhs); |
- var castTo = getImplicitAssignmentCast(left); |
+ var castTo = getImplicitOperationCast(left); |
if (castTo != null) inc = CoercionReifier.castExpression(inc, castTo); |
return new JS.MetaLet(vars, [_emitSet(lhs, inc)]); |
} |
@@ -3483,8 +3575,11 @@ class CodeGenerator extends Object |
return _callHelper('#(#, #)', [name, jsTarget, args]); |
} |
jsTarget = _emitTargetAccess(jsTarget, jsName, element); |
+ var castTo = getImplicitOperationCast(node); |
+ if (castTo != null) { |
+ jsTarget = js.call('#._check(#)', [_emitType(castTo), jsTarget]); |
+ } |
if (typeArgs != null) jsTarget = new JS.Call(jsTarget, typeArgs); |
- |
return new JS.Call(jsTarget, args); |
} |
@@ -3554,8 +3649,10 @@ class CodeGenerator extends Object |
/// an expression. |
JS.Expression _emitFunctionCall(InvocationExpression node, |
[Expression function]) { |
- if (function == null) { |
- function = node.function; |
+ function ??= node.function; |
+ var castTo = getImplicitOperationCast(function); |
+ if (castTo != null) { |
+ function = CoercionReifier.castExpression(function, castTo); |
} |
if (_isCoreIdentical(function)) { |
return _emitCoreIdenticalCall(node.argumentList.arguments); |
@@ -5102,9 +5199,7 @@ class CodeGenerator extends Object |
// TODO(jmesserly): this is inconsistent with [visitIsExpression], which |
// has special case for typeof. |
- var castType = _emitType(clause.exceptionType.type, |
- nameType: options.nameTypeTests || options.hoistTypeTests, |
- hoistType: options.hoistTypeTests); |
+ var castType = _emitType(clause.exceptionType.type); |
return new JS.If(js.call('#.is(#)', [castType, _visit(_catchParameter)]), |
then, otherwise); |
@@ -5360,7 +5455,7 @@ class CodeGenerator extends Object |
if (op == '&&') return shortCircuit('# && #'); |
if (op == '||') return shortCircuit('# || #'); |
} |
- if (node is AsExpression && CoercionReifier.isImplicitCast(node)) { |
+ if (node is AsExpression && CoercionReifier.isRequiredForSoundness(node)) { |
assert(node.staticType == types.boolType); |
return _callHelper('dtest(#)', _visit(node.expression)); |
} |