dart-lang / sdk

The Dart SDK, including the VM, dart2js, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
9.94k stars 1.53k forks source link

Generic mixin is operator throws Unbound type parameter found in Function ':Eval' #55574

Open niclasEX opened 2 months ago

niclasEX commented 2 months ago

This tracker is for issues related to:

Dart info output:

General info

Project info

Process info

Memory CPU Elapsed time Command line
11 MB 0.0% 05:12:22 dart --enable-vm-service=0 --pause_isolates_on_start --disable-dart-dev -DSILENT_VM_SERVICE=true --write-service-info=file:/vm.json --pause_isolates_on_exit --enable-asserts /cancelable.dart
187 MB 0.0% 00:08 dart --enable-vm-service=0 --pause_isolates_on_start --disable-dart-dev -DSILENT_VM_SERVICE=true --write-service-info=file:/vm.json --pause_isolates_on_exit --enable-asserts /eval_bug.dart
10 MB 0.0% 05:12:23 dart debug_adapter
96 MB 0.0% 00:08 dart debug_adapter
10 MB 0.0% 22:22:37 dart devtools --machine --allow-embedding
10 MB 0.0% 56:51 dart devtools --machine --allow-embedding
11 MB 0.0% 05:39:41 dart devtools --machine --allow-embedding --port 9101
17 MB 0.0% 22:22:37 dart language-server --protocol=lsp --client-id=VS-Code --client-version=3.86.0
15 MB 0.0% 22:22:37 dart language-server --protocol=lsp --client-id=VS-Code --client-version=3.86.0
31 MB 0.0% 05:39:41 dart language-server --protocol=lsp --client-id=VS-Code --client-version=3.86.0
24 MB 0.0% 05:25:31 dart language-server --protocol=lsp --client-id=VS-Code --client-version=3.86.0
74 MB 0.0% 56:51 dart language-server --protocol=lsp --client-id=VS-Code --client-version=3.86.0
387 MB 0.0% 01:03 dart language-server --protocol=lsp --client-id=VS-Code --client-version=3.86.0
31 MB 0.4% 22:22:37 flutter_tools.snapshot daemon
39 MB 0.2% 05:39:41 flutter_tools.snapshot daemon
44 MB 0.2% 56:51 flutter_tools.snapshot daemon
18 MB 0.0% 22:22:33 flutter_tools.snapshot debug_adapter
23 MB 0.0% 37:23 flutter_tools.snapshot debug_adapter
28 MB 0.0% 06:27 flutter_tools.snapshot debug_adapter --test
22 MB 0.0% 22:22:33 flutter_tools.snapshot run --machine --start-paused -d macos --devtools-server-address http:/ --target /main.dart
27 MB 0.0% 37:22 flutter_tools.snapshot run --machine --start-paused -d macos --devtools-server-address http:/ --target /main.dart
38 MB 0.0% 06:27 flutter_tools.snapshot test --machine --start-paused --timeout 1d /asd_test.dart
8 MB 0.0% 22:22:31 frontend_server.dart.snapshot --sdk-root / --incremental --target=flutter --experimental-emit-debug-metadata -DFLUTTER_WEB_AUTO_DETECT=true -DFLUTTER_WEB_CANVASKIT_URL=https:/ --output-dill /app.dill --packages /package_config.json -Ddart.vm.profile=false -Ddart.vm.product=false --enable-asserts --track-widget-creation --filesystem-scheme org-dartlang-root --initialize-from-dill build/9ffe3fb9e2063e195ddfbcea0cabcfef.cache.dill.track.dill --source file:/dart_plugin_registrant.dart --source package:flutter/dart_plugin_registrant.dart -Dflutter.dart_plugin_registrant=file:/dart_plugin_registrant.dart --verbosity=error --enable-experiment=alternative-invalidation-strategy
31 MB 0.0% 37:21 frontend_server.dart.snapshot --sdk-root / --incremental --target=flutter --experimental-emit-debug-metadata -DFLUTTER_WEB_AUTO_DETECT=true -DFLUTTER_WEB_CANVASKIT_URL=https:/ --output-dill /app.dill --packages /package_config.json -Ddart.vm.profile=false -Ddart.vm.product=false --enable-asserts --track-widget-creation --filesystem-scheme org-dartlang-root --initialize-from-dill build/9ffe3fb9e2063e195ddfbcea0cabcfef.cache.dill.track.dill --source file:/dart_plugin_registrant.dart --source package:flutter/dart_plugin_registrant.dart -Dflutter.dart_plugin_registrant=file:/dart_plugin_registrant.dart --verbosity=error --enable-experiment=alternative-invalidation-strategy
 void main() {
  final a = A();
  final b = B();

  print(a == b); // returns true
}

abstract class Parent {}

class A extends Parent with ValueEquality<Parent> {
  @override
  Object get value => 0;
}

class B extends Parent with ValueEquality<Parent> {
  @override
  Object get value => 0;
}

mixin ValueEquality<T> {
  Object? get value;

  @override
  bool operator ==(Object other) {
    return other is T; // breakpoint here, other is T returns
    // error: Unbound type parameter found in Function ':Eval':..
    // Please report this at dartbug.com.
  }

  @override
  int get hashCode => runtimeType.hashCode ^ value.hashCode;
}

As stated in the dart info output, this is on macOS running in vscode. The dart code runs without any exceptions and returns true as expected.

I stumbled upon this in my flutter project where I tried to create a Map<Parent, dynamic> and inserting objects threw the aforementioned exception

mraleph commented 2 months ago

/cc @jensjoha seems like a problem with expression evaluation

jensjoha commented 2 months ago

Thanks for the reproducible bug report!

Technical details:

The request sent via kernel service from the VM is this:

[...]
request[10] = file:///path/to/file.dart
request[11] = ValueEquality
request[12] = ==
request[13] = false
request[14] = 395
request[15] = file:///path/to/file.dart
[...]

i.e. it' saying that we're in the library (the file) [10], in class ValueEquality [11], in method == [12] which is not static [13] at position 395 [14] in script (still the file) [15] --- so when doing the expression calculation we're told we're in ValueEquality.== --- which compiled looks like this:

abstract class ValueEquality<T extends core::Object? = dynamic> extends core::Object /*isMixinDeclaration*/  {
  [...]
  operator ==(core::Object other) → core::bool {
    return other is self::ValueEquality::T%;
  }
  [...]
}

It has a T so we're happily compiling the expresion T to return [0] [0] #lib1::ValueEquality::T%;

Though, as we can see from the "frame name" (in Observatory) we're actually in _A&Parent&ValueEquality.== with the class looking like this:

abstract class _A&Parent&ValueEquality extends self::Parent implements self::ValueEquality<self::Parent> /*isAnonymousMixin,isEliminatedMixin*/  {
  [...]
  operator ==(core::Object other) → core::bool {
    return other is self::Parent;
  }
  [...]
}

so no T actually exists. And so the VM returns the Unbound type parameter found in %s. Please report this at dartbug.com. error.

It seems the VM made the change to send the name of the mixin class in https://dart-review.googlesource.com/c/sdk/+/279238 (response to https://github.com/dart-lang/sdk/issues/51027) --- though giving the error The getter 'T' isn't defined for the class '_A&Parent&ValueEquality'. (the case without that change) probably wouldn't be more helpful.

@mraleph could a solution on the VM side be something like this:

diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.cc b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
index c841c209eb5..5a0967322d7 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.cc
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
@@ -3583,12 +3583,40 @@ void TypeTranslator::BuildTypeParameterType() {

   // If the type is from a constant, the parameter index isn't offset by the
   // enclosing context.
+  bool special_mixin_handling_for_expression_evaluation = false;
   if (!in_constant_context_) {
-    const intptr_t class_type_parameter_count =
-        active_class_->klass->NumTypeParameters();
+    auto klass = active_class_->klass;
+    if (klass->is_transformed_mixin_application() &&
+        active_class_->member != nullptr) {
+      const String& name = String::Handle(Z, active_class_->member->name());
+      if (name.Equals(Symbols::DebugProcedureName())) {
+        // In vm/service.cc we send over the "klass->Mixin()" class to the CFE.
+        // It's therefore compiled as is we're in the mixin, but we're not. If
+        // there's type arguments in play we have to handle that special.
+        // This is the first part of what "klass->Mixin()" does,
+        // only we extract the actual type arguments.
+        const Array& interfaces = Array::Handle(klass->interfaces());
+        const Type& mixin_type =
+            Type::Handle(Type::RawCast(interfaces.At(interfaces.Length() - 1)));
+        const TypeArguments& type_args =
+            TypeArguments::Handle(Z, mixin_type.arguments());
+        const intptr_t type_args_count = type_args.Length();
+        if (type_args_count > parameter_index) {
+          result_ = type_args.TypeAt(parameter_index);
+          return;
+        }
+        // Not pointing to the type arguments of the class,
+        // but pick the class with type parameters to shift the parameter_index
+        // correctly below.
+        Class& method_cls = Class::Handle(Z, klass->Mixin());
+        klass = &method_cls;
+        special_mixin_handling_for_expression_evaluation = true;
+      }
+    }
+    const intptr_t class_type_parameter_count = klass->NumTypeParameters();
     if (class_type_parameter_count > parameter_index) {
-      result_ =
-          active_class_->klass->TypeParameterAt(parameter_index, nullability);
+      ASSERT(!special_mixin_handling_for_expression_evaluation);
+      result_ = klass->TypeParameterAt(parameter_index, nullability);
       return;
     }
     parameter_index -= class_type_parameter_count;
@@ -3613,8 +3641,7 @@ void TypeTranslator::BuildTypeParameterType() {
         //   }
         //
         if (class_type_parameter_count > parameter_index) {
-          result_ = active_class_->klass->TypeParameterAt(parameter_index,
-                                                          nullability);
+          result_ = klass->TypeParameterAt(parameter_index, nullability);
           return;
         }
         parameter_index -= class_type_parameter_count;

?

mraleph commented 2 months ago

/cc @rmacnak-google @bkonyi