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

Commit c112b91

Browse files
committed
Call method subtyping
Allow objects with an appropriately typed call method to subtype function types. Fixes #19. BUG= [email protected] Review URL: https://chromereviews.googleplex.com/140757013
1 parent b11adc9 commit c112b91

File tree

10 files changed

+126
-36
lines changed

10 files changed

+126
-36
lines changed

bin/devc.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ void main(List<String> argv) {
7575
var useColors = stdioType(stdout) == StdioType.TERMINAL;
7676
if (!args['dump-info']) setupLogger(level, print);
7777

78-
var typeResolver = new TypeResolver(shouldMockSdk ?
79-
TypeResolver.sdkResolverFromMock(mockSdkSources) :
80-
TypeResolver.sdkResolverFromDir(dartSdkPath));
78+
var typeResolver = new TypeResolver(shouldMockSdk
79+
? TypeResolver.sdkResolverFromMock(mockSdkSources)
80+
: TypeResolver.sdkResolverFromDir(dartSdkPath));
8181

8282
var filename = args.rest.first;
8383
compile(filename, typeResolver,

bin/edit_files.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,12 @@ void main(List<String> argv) {
111111

112112
Map json = JSON.decode(new File(filename).readAsStringSync());
113113
var summary = GlobalSummary.parse(json);
114-
var excludePattern = (args['exclude-pattern'] != null) ?
115-
new RegExp(args['exclude-pattern']) :
116-
null;
117-
var includePattern = (args['include-pattern'] != null) ?
118-
new RegExp(args['include-pattern']) :
119-
null;
114+
var excludePattern = (args['exclude-pattern'] != null)
115+
? new RegExp(args['exclude-pattern'])
116+
: null;
117+
var includePattern = (args['include-pattern'] != null)
118+
? new RegExp(args['include-pattern'])
119+
: null;
120120

121121
var visitor = new EditFileSummaryVisitor(typeResolver, args['level'],
122122
args['checkout-files-executable'], args['checkout-files-arg'],

lib/devc.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ Future<bool> compile(String inputFile, TypeResolver resolver,
6060

6161
// Generate code.
6262
if (outputDir != null) {
63-
var cg = outputDart ?
64-
new DartGenerator(
65-
outputDir, uri, results.libraries, results.rules, formatOutput) :
66-
new JSGenerator(outputDir, uri, results.libraries, results.rules);
63+
var cg = outputDart
64+
? new DartGenerator(
65+
outputDir, uri, results.libraries, results.rules, formatOutput)
66+
: new JSGenerator(outputDir, uri, results.libraries, results.rules);
6767
return cg.generate().then((_) => true);
6868
}
6969

lib/src/checker/resolver.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ class TypeResolver {
3434
var resolvers = [sdkResolver];
3535
if (otherResolvers == null) {
3636
resolvers.add(new FileUriResolver());
37-
resolvers.add(_useMultipackage ?
38-
new MultiPackageResolver() :
39-
new PackageUriResolver([new JavaFile('packages/')]));
37+
resolvers.add(_useMultipackage
38+
? new MultiPackageResolver()
39+
: new PackageUriResolver([new JavaFile('packages/')]));
4040
} else {
4141
resolvers.addAll(otherResolvers);
4242
}

lib/src/checker/rules.dart

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,16 @@ class RestrictedRules extends TypeRules {
106106
return true;
107107
}
108108

109+
FunctionType getCallMethodType(DartType t) {
110+
if (t is InterfaceType) {
111+
ClassElement element = t.element;
112+
InheritanceManager manager = new InheritanceManager(element.library);
113+
FunctionType callType = manager.lookupMemberType(t, "call");
114+
return callType;
115+
}
116+
return null;
117+
}
118+
109119
/// Check that f1 is a subtype of f2. [ignoreReturn] is used in the DDC
110120
/// checker to determine whether f1 would be a subtype of f2 if the return
111121
/// type of f1 is set to match f2's return type.
@@ -249,12 +259,12 @@ class RestrictedRules extends TypeRules {
249259
if (t1 is! FunctionType && t2 is! FunctionType) return false;
250260

251261
if (t1 is InterfaceType && t2 is FunctionType) {
252-
// TODO(leafp): check t1 for a call method of the appropriate type
253-
return false;
262+
var callType = getCallMethodType(t1);
263+
if (callType == null) return false;
264+
return isFunctionSubTypeOf(callType, t2 as FunctionType);
254265
}
255266

256267
if (t1 is FunctionType && t2 is InterfaceType) {
257-
// TODO(leafp): check t2 for a call method of the appropriate type
258268
return false;
259269
}
260270

@@ -376,6 +386,16 @@ class RestrictedRules extends TypeRules {
376386
return _wrapTo(fromT, toT);
377387
}
378388

389+
// For now, reject conversions between function types and
390+
// call method objects. We could choose to allow casts here.
391+
// Wrapping a function type to assign it to a call method
392+
// object will never succeed. Wrapping the other way could
393+
// be allowed.
394+
if ((fromT is FunctionType && getCallMethodType(toT) != null) ||
395+
(toT is FunctionType && getCallMethodType(fromT) != null)) {
396+
return Coercion.error();
397+
}
398+
379399
// Downcast if toT <: fromT
380400
if (isSubTypeOf(toT, fromT)) return Coercion.cast(fromT, toT);
381401

lib/src/codegen/js_codegen.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,9 @@ var _initializer = (function ($params) {
122122
List<String> initializedFields, bool needsInitializer) {
123123
var fieldParameters = ctor == null ? [] : getFieldFormalParameters(ctor);
124124

125-
var initializers = ctor == null ?
126-
{} :
127-
new Map.fromIterable(
125+
var initializers = ctor == null
126+
? {}
127+
: new Map.fromIterable(
128128
ctor.initializers.where((i) => i is ConstructorFieldInitializer),
129129
key: (i) => i.fieldName.name);
130130

@@ -794,9 +794,9 @@ var $name = (function (_super) {
794794

795795
String getLibraryId(LibraryElement element) {
796796
var libraryName = element.name;
797-
return _builtins.containsKey(libraryName) ?
798-
_builtins[libraryName] :
799-
libraryName;
797+
return _builtins.containsKey(libraryName)
798+
? _builtins[libraryName]
799+
: libraryName;
800800
}
801801

802802
void writeQualifiedName(Expression target, SimpleIdentifier id) {

lib/src/info.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ abstract class DownCastBase extends Conversion {
167167
: super(rules, expression) {
168168
assert(_cast.toType != baseType &&
169169
_cast.fromType == baseType &&
170-
(baseType.isDynamic || baseType.isAssignableTo(_cast.toType)));
170+
(baseType.isDynamic ||
171+
// Call methods make the following non-redundant
172+
_cast.toType.isSubtypeOf(baseType) ||
173+
baseType.isAssignableTo(_cast.toType)));
171174
}
172175

173176
Cast get cast => _cast;
@@ -203,7 +206,9 @@ class DownCast extends DownCastBase {
203206
final toT = cast.toType;
204207

205208
// toT <:_R fromT => to <: fromT
206-
assert(toT.isAssignableTo(fromT));
209+
// NB: classes with call methods are subtypes of function
210+
// types, but the function type is not assignable to the class
211+
assert(toT.isSubtypeOf(fromT) || fromT.isAssignableTo(toT));
207212

208213
// Specialized casts:
209214
if (expression is Literal) {

lib/src/report.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -485,8 +485,8 @@ class _Counter extends RecursiveSummaryVisitor {
485485

486486
/// Returns a [SourceSpan] in [file] for the offsets of [node].
487487
SourceSpan _spanForNode(SourceFile file, AstNode node) {
488-
final begin = node is AnnotatedNode ?
489-
node.firstTokenAfterCommentAndMetadata.offset :
490-
node.offset;
488+
final begin = node is AnnotatedNode
489+
? node.firstTokenAfterCommentAndMetadata.offset
490+
: node.offset;
491491
return file.span(begin, node.end);
492492
}

lib/src/testing.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ CheckerResults testChecker(Map<String, String> testFiles, {bool mockSdk: true,
4747
reason: '`/main.dart` is missing in testFiles');
4848

4949
// Create a resolver that can load test files from memory.
50-
var dartUriResolver = mockSdk ?
51-
TypeResolver.sdkResolverFromMock(mockSdkSources) :
52-
TypeResolver.sdkResolverFromDir(dartSdkDirectory);
50+
var dartUriResolver = mockSdk
51+
? TypeResolver.sdkResolverFromMock(mockSdkSources)
52+
: TypeResolver.sdkResolverFromDir(dartSdkDirectory);
5353
var testUriResolver = new _TestUriResolver(testFiles);
5454
var resolver = new TypeResolver(dartUriResolver, [testUriResolver]);
5555

@@ -288,9 +288,9 @@ class _TestSource implements Source {
288288
Uri resolveRelativeUri(Uri relativeUri) => uri.resolveUri(relativeUri);
289289

290290
SourceSpan spanFor(AstNode node) {
291-
final begin = node is AnnotatedNode ?
292-
node.firstTokenAfterCommentAndMetadata.offset :
293-
node.offset;
291+
final begin = node is AnnotatedNode
292+
? node.firstTokenAfterCommentAndMetadata.offset
293+
: node.offset;
294294
return _file.span(begin, node.end);
295295
}
296296

test/checker/checker_test.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,71 @@ main() {
679679
});
680680
});
681681

682+
test('Function subtyping: objects with call methods', () {
683+
testChecker({
684+
'/main.dart': '''
685+
686+
typedef int I2I(int x);
687+
typedef num N2N(num x);
688+
class A {
689+
int call(int x) => x;
690+
}
691+
class B {
692+
num call(num x) => x;
693+
}
694+
int i2i(int x) => x;
695+
num n2n(num x) => x;
696+
void main() {
697+
{
698+
I2I f;
699+
f = new A();
700+
f = /*severe:StaticTypeError*/new B();
701+
f = i2i;
702+
f = /*warning:ClosureWrap*/n2n;
703+
f = /*warning:DownCast*/(i2i as Object);
704+
f = /*warning:DownCast*/(n2n as Function);
705+
}
706+
{
707+
N2N f;
708+
f = /*severe:StaticTypeError*/new A();
709+
f = new B();
710+
f = /*warning:ClosureWrap*/i2i;
711+
f = n2n;
712+
f = /*warning:DownCast*/(i2i as Object);
713+
f = /*warning:DownCast*/(n2n as Function);
714+
}
715+
{
716+
A f;
717+
f = new A();
718+
f = /*severe:StaticTypeError*/new B();
719+
f = /*severe:StaticTypeError*/i2i;
720+
f = /*severe:StaticTypeError*/n2n;
721+
f = /*info:DownCast*/(i2i as Object);
722+
f = /*info:DownCast*/(n2n as Function);
723+
}
724+
{
725+
B f;
726+
f = /*severe:StaticTypeError*/new A();
727+
f = new B();
728+
f = /*severe:StaticTypeError*/i2i;
729+
f = /*severe:StaticTypeError*/n2n;
730+
f = /*info:DownCast*/(i2i as Object);
731+
f = /*info:DownCast*/(n2n as Function);
732+
}
733+
{
734+
Function f;
735+
f = new A();
736+
f = new B();
737+
f = i2i;
738+
f = n2n;
739+
f = /*info:DownCast*/(i2i as Object);
740+
f = (n2n as Function);
741+
}
742+
}
743+
'''
744+
});
745+
});
746+
682747
test('Closure wrapping of literals', () {
683748
testChecker({
684749
'/main.dart': '''

0 commit comments

Comments
 (0)