Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

no analyzer syntax error when using the old function type syntax inside a function type using the new syntax #30596

Closed
alsemenov opened this issue Sep 1, 2017 · 13 comments
Labels
analyzer-spec Issues with the analyzer's implementation of the language spec area-analyzer Use area-analyzer for Dart analyzer issues, including the analysis server and code completion. P2 A bug or feature request we're likely to work on type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)

Comments

@alsemenov
Copy link
Contributor

alsemenov commented Sep 1, 2017

Dart Vm and Analyzer produce different results for valid typedef declarations.
Here is a short summary:

Code Dart VM Dart Analyser (strong mode)
typedef F1 = Stream<T> Function<T>(Iterable<T> values); ok ok
typedef F2 = Stream<T> Function<T>(Iterable<T> values, {bool Function(T element) isError}); ok crash
typedef F3 = Stream<T> Function<T>(Iterable<T> values, {bool isError(T element)}); syntax error ok
typedef Stream<T> F4<T>(Iterable<T> values); ok error message
typedef Stream<T> F5<T>(Iterable<T> values, {bool isError(T element)}); ok error message

Here is a series of samples, that demonstrate the problem:

import "dart:async";

typedef F1 = Stream<T> Function<T>(Iterable<T> values); | ok  | ok

void test1(F1 f) {}

main() {
  test1(null);
  print("ok");
}

Output:

D:\dev\dart\co19.idea17\Utils>d:\dev\dart\dart-sdk\bin\dartanalyzer.bat --strong test1.dart
Analyzing test1.dart...
No issues found!

D:\dev\dart\co19.idea17\Utils>d:\dev\dart\dart-sdk\bin\dart.exe --checked test1.dart
ok

import "dart:async";

typedef F2 = Stream<T> Function<T>(Iterable<T> values, {bool Function(T element) isError});

void test2(F2 f) {}

main() {
  test2(null);
  print("ok");
}

Output:

D:\dev\dart\co19.idea17\Utils>d:\dev\dart\dart-sdk\bin\dartanalyzer.bat --strong test2.dart
Analyzing test2.dart...
Unhandled exception:
NoSuchMethodError: The getter 'element' was called on null.
Receiver: null
Tried calling: element
#0 Object._noSuchMethod (dart:core-patch/object_patch.dart:43)
#1 Object.noSuchMethod (dart:core-patch/object_patch.dart:47)
#2 TypeResolverVisitor.visitGenericFunctionType (package:analyzer/src/generated/resolver.dart:9902)
#3 GenericFunctionTypeImpl.accept (package:analyzer/src/dart/ast/ast.dart:5774)
#4 SimpleFormalParameterImpl.visitChildren (package:analyzer/src/dart/ast/ast.dart:9306)
#5 UnifyingAstVisitor.visitNode (package:analyzer/dart/ast/visitor.dart:3121)
#6 TypeResolverVisitor.visitNode (package:analyzer/src/generated/resolver.dart:10063)
#7 UnifyingAstVisitor.visitSimpleFormalParameter (package:analyzer/dart/ast/visitor.dart:3168)
#8 TypeResolverVisitor.visitSimpleFormalParameter (package:analyzer/src/generated/resolver.dart:10068)
#9 SimpleFormalParameterImpl.accept (package:analyzer/src/dart/ast/ast.dart:9301)
#10 DefaultFormalParameterImpl.visitChildren (package:analyzer/src/dart/ast/ast.dart:3494)
#11 UnifyingAstVisitor.visitNode (package:analyzer/dart/ast/visitor.dart:3121)
#12 TypeResolverVisitor.visitNode (package:analyzer/src/generated/resolver.dart:10063)
#13 UnifyingAstVisitor.visitDefaultFormalParameter (package:analyzer/dart/ast/visitor.dart:2974)
#14 DefaultFormalParameterImpl.accept (package:analyzer/src/dart/ast/ast.dart:3490)
#15 NodeListImpl.accept (package:analyzer/src/dart/ast/ast.dart:8052)
#16 FormalParameterListImpl.visitChildren (package:analyzer/src/dart/ast/ast.dart:4853)
#17 UnifyingAstVisitor.visitNode (package:analyzer/dart/ast/visitor.dart:3121)
#18 TypeResolverVisitor.visitNode (package:analyzer/src/generated/resolver.dart:10063)
#19 UnifyingAstVisitor.visitFormalParameterList (package:analyzer/dart/ast/visitor.dart:3020)
#20 ScopedVisitor.visitFormalParameterList (package:analyzer/src/generated/resolver.dart:7496)
#21 FormalParameterListImpl.accept (package:analyzer/src/dart/ast/ast.dart:4849)
#22 GenericFunctionTypeImpl.visitChildren (package:analyzer/src/dart/ast/ast.dart:5781)
#23 UnifyingAstVisitor.visitNode (package:analyzer/dart/ast/visitor.dart:3121)
#24 TypeResolverVisitor.visitNode (package:analyzer/src/generated/resolver.dart:10063)
#25 UnifyingAstVisitor.visitGenericFunctionType (package:analyzer/dart/ast/visitor.dart:3047)
#26 ScopedVisitor.visitGenericFunctionType (package:analyzer/src/generated/resolver.dart:7660)
#27 TypeResolverVisitor.visitGenericFunctionType (package:analyzer/src/generated/resolver.dart:9903)
#28 GenericFunctionTypeImpl.accept (package:analyzer/src/dart/ast/ast.dart:5774)
#29 GenericTypeAliasImpl.visitChildren (package:analyzer/src/dart/ast/ast.dart:5864)
#30 UnifyingAstVisitor.visitNode (package:analyzer/dart/ast/visitor.dart:3121)
#31 TypeResolverVisitor.visitNode (package:analyzer/src/generated/resolver.dart:10063)
#32 UnifyingAstVisitor.visitGenericTypeAlias (package:analyzer/dart/ast/visitor.dart:3050)
#33 ScopedVisitor.visitGenericTypeAlias (package:analyzer/src/generated/resolver.dart:7680)
#34 GenericTypeAliasImpl.accept (package:analyzer/src/dart/ast/ast.dart:5856)
#35 NodeListImpl.accept (package:analyzer/src/dart/ast/ast.dart:8052)
#36 CompilationUnitImpl.visitChildren (package:analyzer/src/dart/ast/ast.dart:2523)
#37 UnifyingAstVisitor.visitNode (package:analyzer/dart/ast/visitor.dart:3121)
#38 TypeResolverVisitor.visitNode (package:analyzer/src/generated/resolver.dart:10063)
#39 UnifyingAstVisitor.visitCompilationUnit (package:analyzer/dart/ast/visitor.dart:2949)
#40 CompilationUnitImpl.accept (package:analyzer/src/dart/ast/ast.dart:2516)
#41 LibraryAnalyzer._resolveFile (package:analyzer/src/dart/analysis/library_analyzer.dart:536)
#42 LibraryAnalyzer.analyze. (package:analyzer/src/dart/analysis/library_analyzer.dart:89)
#43 _HashVMBase&MapMixin&&_LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:356)
#44 LibraryAnalyzer.analyze (package:analyzer/src/dart/analysis/library_analyzer.dart:88)
#45 AnalysisDriver._computeAnalysisResult. (package:analyzer/src/dart/analysis/driver.dart:999)
#46 PerformanceLog.run (package:front_end/src/base/performace_logger.dart:34)
#47 AnalysisDriver._computeAnalysisResult (package:analyzer/src/dart/analysis/driver.dart:987)
#48 AnalysisDriver.getErrors (package:analyzer/src/dart/analysis/driver.dart:525)

#49 AnalyzerImpl.prepareErrors (package:analyzer_cli/src/analyzer_impl.dart:133)

#50 AnalyzerImpl._analyze (package:analyzer_cli/src/analyzer_impl.dart:178)

#51 AnalyzerImpl.analyze (package:analyzer_cli/src/analyzer_impl.dart:123)

#52 Driver._runAnalyzer (package:analyzer_cli/src/driver.dart:722)
#53 Driver._analyzeAllImpl (package:analyzer_cli/src/driver.dart:262)

#54 Driver._analyzeAll (package:analyzer_cli/src/driver.dart:166)

#55 Driver.start (package:analyzer_cli/src/driver.dart:145)

#56 main (file:///E:/b/build/slave/dart-sdk-windows-stable/build/sdk/pkg/analyzer_cli/bin/analyzer.dart:11)
#57 _startIsolate. (dart:isolate-patch/isolate_patch.dart:263)
#58 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:151)

#0 AnalysisDriver._computeAnalysisResult. (package:analyzer/src/dart/analysis/driver.dart:1038)
#1 PerformanceLog.run (package:front_end/src/base/performace_logger.dart:34)
#2 AnalysisDriver._computeAnalysisResult (package:analyzer/src/dart/analysis/driver.dart:987)
#3 AnalysisDriver.getErrors (package:analyzer/src/dart/analysis/driver.dart:525)

#4 AnalyzerImpl.prepareErrors (package:analyzer_cli/src/analyzer_impl.dart:133)

#5 AnalyzerImpl._analyze (package:analyzer_cli/src/analyzer_impl.dart:178)

#6 AnalyzerImpl.analyze (package:analyzer_cli/src/analyzer_impl.dart:123)

#7 Driver._runAnalyzer (package:analyzer_cli/src/driver.dart:722)
#8 Driver._analyzeAllImpl (package:analyzer_cli/src/driver.dart:262)

D:\dev\dart\co19.idea17\Utils>d:\dev\dart\dart-sdk\bin\dart.exe --checked test2.dart
ok

import "dart:async";

typedef F3 = Stream<T> Function<T>(Iterable<T> values, {bool isError(T element)});

void test3(F3 f) {}

main() {
  test3(null);
  print("ok");
}

Output:

D:\dev\dart\co19.idea17\Utils>d:\dev\dart\dart-sdk\bin\dartanalyzer.bat --strong test3.dart
Analyzing test3.dart...
No issues found!

D:\dev\dart\co19.idea17\Utils>d:\dev\dart\dart-sdk\bin\dart.exe --checked test3.dart
'file:///D:/dev/dart/co19.idea17/Utils/test3.dart': error: line 3 pos 69: ',' or '}' expected
typedef F3 = Stream Function(Iterable values, {bool isError(T element)});

import "dart:async";

typedef Stream<T> F4<T>(Iterable<T> values);

void test4(F4 f) {
  f<int>([1,2,3]);
}

main() {
  test4(<T>(Iterable<T> data) => new Stream<T>.fromIterable(data));
  print("ok");
}

Output

D:\dev\dart\co19.idea17\Utils>d:\dev\dart\dart-sdk\bin\dartanalyzer.bat --strong test4.dart
Analyzing test4.dart...
error - The method '(Iterable) → Stream' is declared with 0 type parameters, but 1 type arguments were given at test4.dart:6:3 - wrong_number_of_type_arguments_method
error - The argument type '(Iterable) → Stream' can't be assigned to the parameter type 'F4(Iterable) → Stream' at test4.dart:10:9 - argument_type_not_assignable
2 errors found.

D:\dev\dart\co19.idea17\Utils>d:\dev\dart\dart-sdk\bin\dart.exe --checked test4.dart
ok

import "dart:async";

typedef Stream<T> F5<T>(Iterable<T> values, {bool isError(T element)});

void test5(F5 f) {
  f<int>([1,2,3]);
}

main() {
  test5(<T>(Iterable<T> values, {bool isError(T element)}) => null);
  print("ok");
}

Output:

D:\dev\dart\co19.idea17\Utils>d:\dev\dart\dart-sdk\bin\dartanalyzer.bat --strong test5.dart
Analyzing test5.dart...
error - The method '(Iterable, {isError: (dynamic) → bool}) → Stream' is declared with 0 type parameters, but 1 type arguments were given at test5.dart:6:3 - wrong_number_of_type_arguments_method
error - The argument type '(Iterable, {isError: (T) → bool}) → dynamic' can't be assigned to the parameter type 'F5(Iterable, {isError: (dynamic) → bool}) → Stream' at test5.dart:10:9 - argument_type_not_assignable
2 errors found.

D:\dev\dart\co19.idea17\Utils>d:\dev\dart\dart-sdk\bin\dart.exe --checked test5.dart
ok

Dart VM version: 1.24.2 (Thu Jun 22 08:55:56 2017) on "windows_x64"

@bwilkerson bwilkerson added area-analyzer Use area-analyzer for Dart analyzer issues, including the analysis server and code completion. P2 A bug or feature request we're likely to work on type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) labels Sep 1, 2017
@eernstg
Copy link
Member

eernstg commented Sep 12, 2017

I believe there is just one issue with current tools:

The behaviors you report for the VM are correct. The syntax error in F3 arises because the type annotation on isError is using the old syntax for a function type (returnType name(ar,gu,ment,list)) rather than the new syntax (returnType Function(ar,gu,ment,list) name, i.e., the regular type name syntax where type is a new function type). The old function type syntax is not supported inside a function type using the new syntax.

I just tried to run dartanalyzer version 1.25.0-dev.7.0 on your examples, and they all check with 'No issues!'. This means that the only remaining issue is that F3 is accepted by the analyzer.

@lrhn
Copy link
Member

lrhn commented Sep 12, 2017 via email

@eernstg
Copy link
Member

eernstg commented Sep 12, 2017

PS: I took 'valid typedef declarations' literally and only checked the typedef declarations, so that's the reason why I didn't see any of the issues with the usages of those typedef'd names in the example programs further down.

The F4 and F5 examples have the issue that Lasse describes: these names do not denote generic function types, they are parameterized typedefs which will mean F4<dynamic> and F5<dynamic> as used, and then the errors you get from the analyzer are correct.

The only remaining issue is still that the analyzer does not declare a syntax error on F3.

@alsemenov
Copy link
Contributor Author

Here is an update for Dart VM version: 1.25.0-dev.16.3 (Mon Sep 11 04:15:09 2017) on "windows_x64"

Code Dart VM Dart Analyser (strong mode)
typedef F1 = Stream<T> Function<T>(Iterable<T> values); ok ok
typedef F2 = Stream<T> Function<T>(Iterable<T> values, {bool Function(T element) isError}); ok ok
typedef F3 = Stream<T> Function<T>(Iterable<T> values, {bool isError(T element)}); syntax error ok
typedef Stream<T> F4<T>(Iterable<T> values); ok error message
typedef Stream<T> F5<T>(Iterable<T> values, {bool isError(T element)}); ok error message

The Dart analyzer crash for F2 is fixed.
F3 still looks strange, dart analyzer should report the same syntax errors as Dart VM.
Regarding cases F4 and F5 there is a lack of good specification/tutorial on my side.
@lrhn, could you explain what is "a parameterized typedef" ? What kind of functions it defines?

@eernstg
Copy link
Member

eernstg commented Sep 12, 2017

A parameterized typedef is a type level function (that is, a compile-time function which maps types to types). So F4<int> is a concise notation for Stream<int> Function(Iterable<int>), which is the type of a non-generic function. Similarly, F4 means Stream<dynamic> Function(Iterable<dynamic>). Everything concerning the type argument given to F4 is performed at compile-time. There is no generic function anywhere in this setup, and no type arguments are passed at run-time.

With the type of a generic function like F1, the run-time value having type F1 will be a generic function, i.e., it will support invocations where an actual type argument is passed and the value of that type argument will be available for the body of the function (in a full implementation of generic methods, e.g., dartdevc). Different invocations can receive different actual type arguments. A generic function has a different representation than a non-generic function at run-time, so it's possible to detect that a type argument was expected but not provided.

The informal spec of the new Function types expands a bit on this as well.

@lrhn
Copy link
Member

lrhn commented Sep 12, 2017

To elaborate on Erik's distinction, we needed two kinds of type parameters - one for the typedef and one for the defined method. The old syntax couldn't support that, and we didn't want to break the existing parameterized typedefs, so to declare a generic function type, you need the new syntax.
Example:

typedef F<T> = Pair<T,S> Function<S>(T, S);

This declares a typedef named F. The typedef is parameterized (by <T>) so writing F<int> represents the function type Pair<int, S> Function<S>(int, S). That is, F is a parameterized typedef, F<int> is a function type. That function-type is itself generic (by <S>).
If you just write F, it becomes F<dynamic>, which is the function type Pair<dynamic, S> Function<S>(dynamic, S)

The old-style typedefs cannot define generic function types, so:

typedef T Id<T>(T x);

is a parameterzied typedef, and Id<int> is the function type int Function(int). If you just write Id, it is equivalent to Id<dynamic> which is the function type dynamic Function(dynamic).
The above old-style typedef is exactly equivalent to:

typedef Id<T> = T Function(T);

That is a parameterized typedef which defines a family of non-generic function types.

@alsemenov
Copy link
Contributor Author

This is quite complex for understanding.
If F4 is a parameterized typedef, then is it correct to use it as F4<T>?
For example, the same way as List<T>:

typedef Stream<T> F4<T>(Iterable<T> values);

void testList<T>(List<T> list, T x) {
   list.add(x);
}
void testF4<T>(F4<T> f, Iterable<T> data) {
   f<T>(data);
}

testList<int>([1,2,3], 4);
testF4<int>((Iterable<int> data)=>new Stream<int>.fromIterable(data), [1,2,3]);

@lrhn
Copy link
Member

lrhn commented Sep 13, 2017

Yes, a parameterized typedef can (and indeed must) be given a type argument before it can be used. You can omit writing the type argument, it then defaults to <dynamic>, just as for List.

So, testF4 above has a valid signature. When you call test4 you must supply a type argument (you use int) and you must then also pass a function of type F4<int>, which is Stream<int> Function(Iterable<int>).

Below that, you call f as f<T>(data). That is incorrect - f is not a generic function, so you can't pass a type argument to it. It's just a function from Iterable<T> to Stream<T>, so you call it as f(data).

@lrhn
Copy link
Member

lrhn commented Sep 18, 2017

It's also necessary to remember that the VM doesn't actually have generic functions yet. It's still running Dart 1 semantics with erasure of all function generics. It doesn't check whether a function type was intended to be generic or not, it just erases the type arguments, which explains why it accepts things that Dart 2/strong-mode implementations recognize as an error.

So, all in all, I think the two actual errors here are the analyzer crashing and the analyzer not catching the syntax error.

@devoncarew
Copy link
Member

Sounds like the analyzer crash is fixed, and that the remaining issue is the missing syntax error. I'll update the title of this issue to match.

@devoncarew devoncarew changed the title Multiple Dart analyzer problems related to parametrized typedef no analyzer syntax error when using the old function type syntax inside a function type using the new syntax Oct 3, 2017
@alsemenov
Copy link
Contributor Author

Could you specify the version in which the crash is fixed?

@devoncarew
Copy link
Member

I'm not sure when the fix was introduced, but I ran the F2 repro (typedef F2 = Stream<T> Function<T>(Iterable<T> values, {bool Function(T element) isError});) through the analyzer from head w/o issues today.

In terms of released SDKs, it's likely that 1.24.x versions of the SDK will have issues, and 2.0.0-dev versions will not.

@bwilkerson bwilkerson added this to the Dart2.1 milestone Sep 4, 2018
@bwilkerson bwilkerson modified the milestones: Dart2.1, PostDart2.1 Sep 4, 2018
@aadilmaan aadilmaan modified the milestones: Future, D25 Release Jun 4, 2019
@srawlins
Copy link
Member

Dart analyzer now features the syntax error for f3:

typedef f3 = Stream<T> Function<T>(Iterable<T> values, {bool isError(T element)});

results in:

Inline function types cannot be used for parameters in a generic function type.

It also still has errors for test4 and test5:

typedef Stream<T> F4<T>(Iterable<T> values);
typedef Stream<T> F5<T>(Iterable<T> values, {bool isError(T element)});
void test4(F4 f) {
  f<int>([1,2,3]);
}
void test5(F5 f) {
  f<int>([1,2,3]);
}
void main() {
  test4(<T>(Iterable<T> data) => new Stream<T>.fromIterable(data));
  test5(<T>(Iterable<T> values, {bool isError(T element)}) => null);
}

produces:

The method 'Stream<dynamic> Function(Iterable<dynamic>)' is declared with 0 type parameters, but 1 type arguments were given.

The method 'Stream<dynamic> Function(Iterable<dynamic>, {isError: bool Function(dynamic)})' is declared with 0 type parameters, but 1 type arguments were given.

The argument type 'Stream<T> Function<T>(Iterable<T>)' can't be assigned to the parameter type 'Stream<dynamic> Function(Iterable<dynamic>)'.

The argument type 'Null Function<T>(Iterable<T>, {isError: bool Function(T)})' can't be assigned to the parameter type 'Stream<dynamic> Function(Iterable<dynamic>, {isError: bool Function(dynamic)})'.

Looks like this is all fixed.

@srawlins srawlins added the analyzer-spec Issues with the analyzer's implementation of the language spec label Jun 17, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
analyzer-spec Issues with the analyzer's implementation of the language spec area-analyzer Use area-analyzer for Dart analyzer issues, including the analysis server and code completion. P2 A bug or feature request we're likely to work on type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)
Projects
None yet
Development

No branches or pull requests

7 participants