dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.09k stars 1.56k forks source link

Analyzer does not throw a compile error when old dart code imports and calls a type alias with generic function type parameter. #45836

Closed iarkh closed 1 month ago

iarkh commented 3 years ago

The following 2.9 dart code imports a non-function type alias with generic function type parameter:

test.dart:

// @dart=2.6
import "testlib.dart";

main() {
  A();
}

testlib.dart:

class D<T> {}
typedef A = D<Function<T>()>;

Generic functions as type arguments and bounds is a feature which is not supported in the version 2.9, so dart expectedly throws a compile time error here.

Analyzer silently passes here - seems like it should throw a compile error too.

Sample output is:

$> dart --enable-experiment=generic-metadata,nonfunction-type=aliases test.dart 
test.dart:5:3: Error: A generic function type can't be used as a type argument.
Try using a non-generic function type.
  A();
  ^

$> dartanalyzer --enable-experiment=generic-metadata,nonfunction-type=aliases test.dart 
Analyzing test.dart...
No issues found!
eernstg commented 3 years ago

Good catch! ;-) See https://github.com/dart-lang/language/issues/1603 for some thoughts about how to handle this situation.

My proposal for the example in this issue is that it should be allowed to use A() in a legacy library, because it does not give rise to the creation of a type with a type argument which is a generic function type in the legacy library itself, that type already exists in the opted in library (just like the superclass A<...> of B in https://github.com/dart-lang/language/issues/1603), and it is not an error to refer to such a type in a legacy library. But we need to resolve the language issue now, before we can conclude anything about this issue.

eernstg commented 3 years ago

I overlooked the fact that there is a set of rules in the 'small-features-21Q1' spec about the treatment of generic function types as type arguments or bounds in legacy libraries.

For A() as it occurs here, the relevant rule would probably be this one:

It is a compile-time error to use a GFT as a type argument anywhere. This includes: [...] Types produced by expanding references to type aliases into their aliased type.

@lrhn, did you intend this rule to be applicable in the given case (where A in the instance creation A() refers to a type alias denoting D<Function<T>()>)?

lrhn commented 3 years ago

I didn't intend it to cover generalized type aliases, since those hadn't landed at the time the text was written. I do think it should cover the situation. There will be no introducing a statically recognizable generic function type as a type argument in a legacy library. no matter how you wrangle the code.

Also, alias expansion should always be equivalent to writing the expanded term directly (if that is valid syntax at all). That includes errors. You should not be able to get around errors by introducing an alias for a type.

lrhn commented 3 years ago

To be more concrete: The goal of disallowing legacy libraries from using new language features is to ensure that they can run on old SDKs as is. A program containing only valid pre-2.14 libraries must never be able to introduce a generic function typed type argument. If a legacy library is used in a program where a new language version feature is used, and that causes it to introduce a generic function type as type argument, then that should be fine. The only thing that matters is that legacy libraries by themselves cannot take advantage of the new feature (and therefore break when run on an old SDK).

With that in mind, I'd say that no type argument which is written in a legacy library must be a generic function type. Type aliases are expanded, but if the expansion contains a generic function type argument, that's not a problem. It must have come from the declaration, which is therefore obviously non-legacy. It's only a problem if the expansion itself is a generic function type, and it occurs in a type argument position.

Would that be workable?

scheglov commented 3 years ago

@lrhn @eernstg So, what is the position of the language here? Do we accept such programs, or report errors?

co19/LanguageFeatures/Generic-functions-as-type-args/weak/legacy_A02_t02
co19/LanguageFeatures/Generic-functions-as-type-args/weak/legacy_A02_t04
eernstg commented 3 years ago

It's surprising that opted_out_lib.dart is opted in, but said tests are all matched by the following item listing one of the error cases here:

Types produced by expanding references to type aliases into their aliased type

so they should expect an error for each of the lines 42, 47, ... (t_02) respectively 42, 47, ..., 57 (t_04).

srawlins commented 1 month ago

Closing as stale; we don't support mixed mode any longer.