Skip to content

The parameter 'x' has type 'y' which does not match the corresponding type 'y' in the overridden method. #47311

New issue

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

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

Already on GitHub? # to your account

Closed
felangel opened this issue Sep 28, 2021 · 2 comments
Assignees
Labels
legacy-area-front-end Legacy: Use area-dart-model instead. P2 A bug or feature request we're likely to work on

Comments

@felangel
Copy link

felangel commented Sep 28, 2021

  • Dart SDK Version (dart --version)
    • Dart SDK version: 2.15.0-156.0.dev (dev) (Mon Sep 27 23:15:54 2021 -0700) on "macos_x64"
  • Whether you are using Windows, MacOSX, or Linux (if applicable)
    • MacOSX
  • Whether you are using Chrome, Safari, Firefox, Edge (if applicable)
    • N/A

The following code does not report any analysis errors but does not compile:

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

class Base<T> {
  void method<S extends T>(Convert<S> convert) {}
}

class ObjectBase implements Base<Object> {
  @override
  void method<T extends Object>(Convert<T> convert) {}
}

void main() {}

The error is:

test/my_test.dart:9:44: Error: The parameter 'convert' of the method 'ObjectBase.method' has type 'T Function(T)', which does not match the corresponding type, 'T Function(T)', in the overridden method, 'Base.method'.
Change to a supertype of 'T Function(T)', or, for a covariant parameter, a subtype.
  void method<T extends Object>(Convert<T> convert) {}
                                           ^
test/my_test.dart:4:8: Context: This is the overridden method ('method').
  void method<S extends T>(Convert<S> convert) {}
       ^

Failed to load "my_test.dart": Compilation failed for testPath=my_test.dart

✖ loading my_test.dart
Exited (1)

I would expect the program to compile and run.

Additional Context

If we change the return type of Convert to T?, the code compiles:

typedef Convert<T> = T? Function(T);

class Base<T> {
  void method<S extends T>(Convert<S> convert) {}
}

class MyObject {}

class ObjectBase implements Base<MyObject> {
  @override
  void method<T extends MyObject>(Convert<T> convert) {}
}

void main() {}

Also, if we specify that T extends Object on Base, the code compiles:

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

class Base<T extends Object> {
  void method<S extends T>(Convert<S> convert) {}
}

class MyObject extends Object {}

class ObjectBase implements Base<MyObject> {
  @override
  void method<T extends MyObject>(Convert<T> convert) {}
}

void main() {}
@felangel
Copy link
Author

felangel commented Sep 28, 2021

Might be related to #47072 and #42248

@eernstg
Copy link
Member

eernstg commented Sep 29, 2021

@felangel, thanks for reporting this! I believe the program should be accepted (which the analyzer does, but the common front end does not), so that's an issue for the CFE. The variants of the program just reinforce the conclusion that the static analysis here needs further scrutiny.

However, I'd also mention in passing that the program is almost impossible to use, because it uses a type variable of a class as a bound on a type variable of a method, and that gives rise to dynamic checks. You probably want to wait until we have sound declaration site variance (dart-lang/language#524), which will allow you to express this kind of typing in a way that is statically sound.

typedef Convert<X> = X Function(X);

// With sound variance, you can specify invariance using `inout`.
class Base<inout X> {
  void method<Y extends X>(Convert<Y> convert) {}
}

class ObjectBase implements Base<Object> {
  @override
  void method<Y extends Object>(Convert<Y> convert) {}
}

void main() {
  ObjectBase ob = ...;

  int convert(int i) => i + 1;
  ob.method(convert); // Fine.

  Base<Object?> b = ob; // Compile-time error with sound variance; accepted today.

  convert2(x) => x; // Has type `dynamic Function(dynamic)`.
  b.method(convert2); // Statically accepted, throws at run time.
}

If we --enable-experiment=variance and include the keyword inout then we turn the initialization of b into a compile-time error (which is what you have to accept if you want to use a type parameter of a class as the bound of a type parameter of a method, without introducing dynamic checks).

However, if we use Dart of today (without sound variance, and omitting inout which is then a syntax error) then because of the dynamically checked covariance we can let b refer to an instance of BaseObject (or any instance of Base<T> as long as T <: Object?, that is, for all T).

In this situation, the inferred type parameter in the invocation of b.method is dynamic, and that causes the run-time error because the actual bound of the type argument is Object, but dynamic is not a subtype of Object.

@johnniwinther johnniwinther added the P2 A bug or feature request we're likely to work on label Sep 29, 2021
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
legacy-area-front-end Legacy: Use area-dart-model instead. P2 A bug or feature request we're likely to work on
Projects
None yet
Development

No branches or pull requests

4 participants