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

Extension in implicit object can no longer be used #22920

Closed
WojciechMazur opened this issue Apr 5, 2025 · 8 comments
Closed

Extension in implicit object can no longer be used #22920

WojciechMazur opened this issue Apr 5, 2025 · 8 comments
Labels
area:implicits related to implicits itype:bug regression This worked in a previous version but doesn't anymore stat:wontfix

Comments

@WojciechMazur
Copy link
Contributor

WojciechMazur commented Apr 5, 2025

Based on OpenCB failure in rescala-lang/rescala - build logs

Compiler version

Last good release: 3.7.1-RC1-bin-20250328-d519790-NIGHTLY
First bad release: 3.7.1-RC1-bin-20250401-d0e9062-NIGHTLY
Bisect points to 4cdeb81

Minimized code

import scala.util.*

object delay {
  class Async[-Ctx, +A]

  @FunctionalInterface
  trait Callback[-A] {
    def complete(tr: Try[A]): Unit
  }

  implicit object syntax:
    extension [Ctx, A](inline async: Async[Ctx, A])
      inline def run(using inline ctx: Ctx)(inline cb: Callback[A]): Unit = ???
}


@main def Test = {
  import delay.Async
  val initializeOutbound: Async[Any, String] = ???
  initializeOutbound.run:
    case Success(target) => ???
    case Failure(exception) => ???
}

Output

[error] ./test.scala:20:3
[error] Found:    (initializeOutbound : delay.Async[Any, String])
[error] Required: ?{ run: ? }
[error] Note that implicit conversions were not tried because the result of an implicit conversion
[error] must be more specific than Ctx
[error]   initializeOutbound.run:
[error]   ^^^^^^^^^^^^^^^^^^

Expectation

Not sure if this usage is covered by language specification. In such case it should be decided accessing extension in implicit object is an undefined behaviour. Otherwise should continue to compile

@WojciechMazur WojciechMazur added itype:bug regression This worked in a previous version but doesn't anymore stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 5, 2025
@som-snytt
Copy link
Contributor

som-snytt commented Apr 6, 2025

For context, that implicit object was deleted from slips.

commit 0a40d31df44f4118c553968465e2b61c5c7c18ac
Author: ragnar <[email protected]>
Date:   Tue Nov 26 15:27:29 2024 +0100

    remove implicit syntax object for delay?

With Ctx inferred Any, it uses $conforms[Any] for (using Ctx)? I haven't looked at what the code is supposed to do.

@Gedochao
Copy link
Contributor

Gedochao commented Apr 7, 2025

cc @mbovel

@Gedochao Gedochao added area:implicits related to implicits and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 7, 2025
@odersky
Copy link
Contributor

odersky commented Apr 8, 2025

In cases like this, it helps to supply the implicit argument manually and see what happens. First, there is no syntax import in @main, so how could this work? After adding the import I had:

@main def Test = {
  import delay.{Async, syntax}
  val initializeOutbound: Async[Any, String] = ???
  syntax.run(initializeOutbound):
    case Success(target) => ???
    case Failure(exception) => ???
}

And the compiler responded with

-- [E172] Type Error: i22920.scala:20:32 ---------------------------------------
20 |  syntax.run(initializeOutbound):
   |                                ^
   |No implicit search was attempted for parameter ctx of method run in object syntax
   |since the expected type Ctx is not specific enough
   |
   |where:    Ctx is a type variable
1 error found

And that makes also sense. There is no specific Ctx implicit and its instance Any is not eligible as aa target for an implicit search. So this is as expected.

The original error message is not great, but given how botched this example is, I don't think it's a good idea to invest a lot of time to tweak it.

@odersky odersky closed this as completed Apr 8, 2025
@Gedochao Gedochao closed this as not planned Won't fix, can't repro, duplicate, stale Apr 8, 2025
@Gedochao
Copy link
Contributor

Gedochao commented Apr 8, 2025

cc @rmgk

@som-snytt
Copy link
Contributor

The extension is in implicit scope because in a prefix of delay.Async. It doesn't matter whether the extension is in the implicit object or not wrapped.

If the type is Async[String, String], it suggests importing the extension method.

Writing the call explicitly, for debug, it finally says No given instance of type String.

I'm looking at a ticket about improving ImportSuggestions; I'll add this as a test case.

The suggestions are tricky, but also a beloved feature of Dotty (as I gather from social media).

@rmgk
Copy link

rmgk commented Apr 8, 2025

The issue title is quite misleading. I minimized the example further to:

object delay {
  class Contravariant[-Ctx]
  inline def run[Ctx](contravariant: Contravariant[Ctx])(using inline ctx: Ctx): Unit = ???
}


@main def Test = {
  import delay.*
  val anyValue: Contravariant[Any] = ???
  delay.run(anyValue)
}

So it seems to me that this combination of inline and a contravariant type somehow did not trigger the specificity check for summoning the implicit value.

When I wrote the code, I vaguely remember that I was wondering why it would work when not specifying the context, then realizing that it likely would just use some random implicit value.

The old behaviour is kinda useful in this “can just not specify a context” way.
But it is also absurdly confusing as it just takes random values as context, and breaks as soon as any ambiguous implicits exist in scope.

My tests indicate that specifying the context as part of the run method is sufficient (i.e., run(using ())) which was the originally intended use anyway.

I tried this patch for the failed project:
rescala-lang/REScala@74478b6
Which unearths some more uses of the pattern that unfortunately cause a compiler crash with the 3.7.1 nightlies, also adapting those cases to specify the context explicitly makes the project compile (well, mostly, would need scala native nightlies I assume, and one of the submodules currently does not compile):
rescala-lang/REScala@6555dbc

… the compiler crash is more likely due to the macros. I’ll investigate if I find time (likely only if the issue comes up again)

@rmgk
Copy link

rmgk commented Apr 8, 2025

The original error message is not great, but given how botched this example is, I don't think it's a good idea to invest a lot of time to tweak it.

To maybe comment on the error message, the message seems to be due to some combination of type inference, and implict parameters on extension methods, and can be triggered with a minified:

object delay {
  class Box[A]
  extension [A](box: Box[A]) def run(using A): Unit = ???
}

@main def Test = {
  import delay.{Box, run}
  val anyValue: Box[Any] = ???
  anyValue.run
}

I think the only unusual bit about this code is that using Any is not that common.

It really just seems to be an unfortunate interaction with the bit of logic that decides that Any is not specific enough for an implicit conversion (why does this even talk about implicit conversions), which “overrides”(?) the more useful error message in this case.

@som-snytt
Copy link
Contributor

why does this even talk about implicit conversions

It's looking for anything it can convert to the required type of the missing arg.

I'm not sure if it refuses to search for Any or just refuses to consider whether Predef.$conforms[Any] (an implicit value) itself conforms to Any. That is the "random" value it previously supplied.

TIL the explicit using arg (below) is required even if there is a given of the required type. That is, there is no sense in which the "exact" type satisfies the implicit param without asking if it conforms, which is just to ask A <:< B or summon[A => B], but conversions to Any are disallowed because trivial.

The extra import is not needed.

Also, f(using()) is my new favorite puzzler syntax.

object delay {
  class Box[A]
  extension [A](box: Box[A]) def run(using A): Unit = ???
}

@main def Test = {
  import delay.Box
  //import delay.{Box, run}
  given any: Any = ()
  val anyValue: Box[Any] = ???
  anyValue.run(using any)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:implicits related to implicits itype:bug regression This worked in a previous version but doesn't anymore stat:wontfix
Projects
None yet
Development

No branches or pull requests

5 participants