Skip to content

Generic type not correctly resolved for <T, C extends Iterable<T>> #46117

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

Closed
passsy opened this issue May 24, 2021 · 5 comments
Closed

Generic type not correctly resolved for <T, C extends Iterable<T>> #46117

passsy opened this issue May 24, 2021 · 5 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 type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)

Comments

@passsy
Copy link

passsy commented May 24, 2021

While trying to create chainable extensions for Iterable, where List and Set both return itself correctly I ran into a type issue. The iterable type is correctly returned but information about T is lost, resulting in dynamic

// extension example
void main() {
  final List<int> list = [1, 2, 3];
  final List<int> listReturn = list.onEach((it) {
    // BUG: it is dynamic, abs() is not auto-completed
    print(it.abs());
  });
  final Set<int> set = {1, 2, 3};
  final Set<int> setReturn = set.onEach((it) {
    // BUG: it is dynamic, abs() is not auto-completed
    print(it.abs());
  });
}

extension OnEach<T, C extends Iterable<T>> on C {
  C onEach(void Function(T) action) {
    for (final item in this) {
      action(item);
    }
    return this;
  }
}

This bug is not related to extensions. It also occurs for plain top-level functions

// top-level example
void main() {
  final List<int> list = [1, 2, 3];
  final List<int> listReturn = topLevelOnEach(list, (it) {
    // BUG: it is dynamic, abs() is not auto-completed
    print(it.abs());
  });
  final Set<int> set = {1, 2, 3};
  final Set<int> setReturn = topLevelOnEach(set, (it) {
    // BUG: it is dynamic, abs() is not auto-completed
    print(it.abs());
  });
}

C topLevelOnEach<T, C extends Iterable<T>>(
    C iterable, void Function(T) action) {
  for (final item in iterable) {
    action(item);
  }
  return iterable;
}

Adding a type manually allows compilation for topLevel functions

  final List<int> list = [1, 2, 3];
  final List<int> listReturn = topLevelOnEach(list, (int it) 
    print(it.abs());
  });

But adding a type for extensions crashes

  final List<int> list = [1, 2, 3];
  final List<int> listReturn = list.onEach((int it) {
    // error: The argument type 'void Function(int)' can't be assigned to the parameter type 'void Function(dynamic)'. (argument_type_not_assignable at example/bug.dart:3)
    print(it.abs());
  });

Expected:
// extension example compiles without errors and returns int as parameter of onEach

Tested with

Dart SDK version: 2.13.0-211.14.beta (beta) (Mon May 3 08:08:14 2021 +0200) on "macos_x64"

Related: #35518

@a-siva a-siva added area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) labels May 24, 2021
@a-siva a-siva added the P2 A bug or feature request we're likely to work on label May 24, 2021
@crelier
Copy link
Contributor

crelier commented May 24, 2021

  1. The first example ("// extension example") runs fine on the VM.
  2. The second example ("// top-level example") results in compile time errors:
../../bug.dart:6:14: Error: The method 'abs' isn't defined for the class 'Object?'.
 - 'Object' is from 'dart:core'.
Try correcting the name to the name of an existing method, or defining a method named 'abs'.
    print(it.abs());
             ^^^
../../bug.dart:11:14: Error: The method 'abs' isn't defined for the class 'Object?'.
 - 'Object' is from 'dart:core'.
Try correcting the name to the name of an existing method, or defining a method named 'abs'.
    print(it.abs());
             ^^^

This is a front-end issue related to inference.

  1. Adding type int to example 1) results in compile time errors (inference issue):
../../bug.dart:4:44: Error: The argument type 'void Function(int)' can't be assigned to the parameter type 'void Function(dynamic)'.
  final List<int> listReturn = list.onEach((int it) {
                                           ^
../../bug.dart:9:41: Error: The argument type 'void Function(int)' can't be assigned to the parameter type 'void Function(dynamic)'.
  final Set<int> setReturn = set.onEach((int it) {
                                        ^
  1. Adding type int to example 2) solves the inference issue and the program runs.

All issues are inference issues and not VM issues. Reassigning.

@crelier crelier removed their assignment May 24, 2021
@crelier crelier added legacy-area-front-end Legacy: Use area-dart-model instead. and removed area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. labels May 24, 2021
@eernstg
Copy link
Member

eernstg commented May 26, 2021

This is a known property of the Dart inference algorithm (cf. dart-lang/language#731). The core issue is that the inference does not support flow of information from one argument to another in a generic function call.

Note that the extension example can be resolved by providing the type arguments explicitly, which is possible in the case where the extension method is invoked explicitly (that is, using the name of the extension in a way that resembles a constructor invocation):

void main() {
  final List<int> list = [1, 2, 3];
  final List<int> listReturn = OnEach<int, List<int>>(list).onEach((it) {
    print(it.abs());
  });
}

extension OnEach<T, C extends Iterable<T>> on C {
  C onEach(void Function(T) action) {
    for (final item in this) {
      action(item);
    }
    return this;
  }
}

@MarvinHannott
Copy link

Ran into the same issue today. It would be really, really cool if the Dart team would solve this.

@eernstg
Copy link
Member

eernstg commented Feb 28, 2025

Dart 3.7.0 includes the feature 'inference-using-bounds' (dart-lang/language#3009). This feature generalizes type inference such that it is able to extract more information from the declared bounds. In particular, the following will now work:

// ignore_for_file: unused_local_variable

// extension example
void main() {
  final List<int> list = [1, 2, 3];
  final List<int> listReturn = list.onEach((it) {
    // `it` has type `int`.
    print(it.abs());
  });
  final Set<int> set = {1, 2, 3};
  final Set<int> setReturn = set.onEach((it) {
    // `it` has type `int`.
    print(it.abs());
  });
}

extension OnEach<C extends Iterable<T>, T> on C {
  C onEach(void Function(T) action) {
    for (final item in this) {
      action(item);
    }
    return this;
  }
}

This program compiles and runs without issues. To see that the type of it is now int rather than dynamic, you can do it.whatever (which is now an error) or use completion.

So in that sense the problem has been solved already (unless your tool chain is older than 3.7.0).

However, it doesn't work on the original example program: It requires the type parameters to be declared in the right order, such that the information from the bound is available at the time where the type parameter that needs this information is being inferred. So we need to declare them as OnEach<C extends Iterable<T>, T> rather than OnEach<T, C extends Iterable<T>>, such that we know that C extends Iterable<T> when we're computing the value of T.

This is actually a different topic (namely: type inference depends on the ordering of type parameter declarations), which is already the topic of #40423.

So I'll close this issue as resolved.

@eernstg eernstg closed this as completed Feb 28, 2025
@eernstg
Copy link
Member

eernstg commented Feb 28, 2025

@MarvinHannott, if your use case isn't handled by declaring the type parameters in a suitable order and using Dart 3.7.0 or higher then it's a different thing—in that case, please create a new issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in 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 type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)
Projects
None yet
Development

No branches or pull requests

6 participants