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

macro crushes invariant type variables to Nothing #12343

Closed
tpolecat opened this issue May 5, 2021 · 28 comments · Fixed by #14425
Closed

macro crushes invariant type variables to Nothing #12343

tpolecat opened this issue May 5, 2021 · 28 comments · Fixed by #14425

Comments

@tpolecat
Copy link

tpolecat commented May 5, 2021

Skunk's sql interpolator has a bug in which I am unable to abstract over interpolated Encoder[A]s because the type arguments are crushed to Nothing. I have minimized it in the following dumb but small macros.

Compiler version

3.0.0-RC3

Minimized code

Here is a pair of transparent macros. One takes any expression, and if it's a Set it returns it, annotated with its own type. The other does the same, but with List.

package test

import scala.quoted._

object Test {

  // Invariant (Set)

  def inv(arg: Expr[Any])(using Quotes): Expr[Any] =
    arg match {
      case '{ $h : Set[h] } => '{ $h : Set[h] }
    }

  transparent inline def inv(inline arg: Any): Any =
    ${ inv('arg) }

  // Covariant (List)

  def cov(arg: Expr[Any])(using Quotes): Expr[Any] =
    arg match {
      case '{ $h : List[h] } => '{ $h : List[h] }
    }

  transparent inline def cov(inline arg: Any): Any =
    ${ cov('arg) }

}

Here are some examples. In both cases calling the macros with concrete types works correctly; the passed expression is inlined with the correct type annotation. But in the case where the type argument is a type variable the covariant case works (the type is List[A]) but in the invariant case the type is Set[Nothing] rather than Set[A].

package test

val inv1: Set[Boolean] = Test.inv(Set(true))
def inv2[A](a: Set[A]): Set[A] = Test.inv(a) // doesn't compile; Set[Nothing] is inferred

val cov1: List[Boolean] = Test.cov(List(true))
def cov2[A](a: List[A]): List[A] = Test.cov(a)

Expectation

My expectation is that this mechanism shouldn't be sensitive to variance.

Notes

The annotated type is important in the code I generate. If I simplify the returned expressions above to simply h then the examples work, but I can't do this in the actual macro because it causes inference failures downstream.

@liufengyun
Copy link
Contributor

It seems there can be a simple fix. @nicolasstucki Could you have a look or give me a tip where the logic is located?

@nicolasstucki
Copy link
Contributor

nicolasstucki commented May 10, 2021

It seems like we are not collecting the constraint while matching in compiler/src/scala/quoted/runtime/impl/Matcher.scala. Maybe a missing =:= or <:<. Those constraints are collected in QuotesImple.treeMatch with typeHoleApproximation.

@liufengyun liufengyun self-assigned this May 10, 2021
@liufengyun
Copy link
Contributor

From the logs, it seems the problem is with GADT:

narrow gadt bound of type h:  from above to A TypeRef(NoPrefix,type A) false
adding ordering h(param)1 <: A(param)2 to
Constraint(
 uninstantiated variables: A(param)2, h(param)1
 constrained types: [A(param)2] => Any, [h(param)1] => Any
 bounds:
     A(param)2
     h(param)1
 ordering:
) down1 = List(TypeParamRef(h(param)1)), up2 = List(TypeParamRef(A(param)2))
added ordering h(param)1 <: A(param)2 to
Constraint(
 uninstantiated variables: A(param)2, h(param)1
 constrained types: [A(param)2] => Any, [h(param)1] => Any
 bounds:
     A(param)2
     h(param)1
 ordering:
     h(param)1 <: A(param)2
) = true
adding upper bound type h <: A = true
narrow gadt bound of type A:  from above to h TypeRef(NoPrefix,type h) false
unifying TypeParamRef(h(param)1) TypeParamRef(A(param)2)
added ordering A(param)2 <: h(param)1 to
Constraint(
 uninstantiated variables: h(param)1
 constrained types: [A(param)2] => Any, [h(param)1] => Any
 bounds:
     A(param)2 := h(param)1
     h(param)1
 ordering:
) = true

As can be seen above, the inference unifies A and h while leaving the bounds of h to be Nothing .. Any. If the unification works the other way h := A, it would be correct.

@abgruszecki
Copy link
Contributor

@liufengyun this constraint is correct, how are you using it?

@liufengyun
Copy link
Contributor

@liufengyun this constraint is correct, how are you using it?

Yes, it works as expected. In QuotesImple.treeMatch, it seems to be doing the right thing as well.

However, this example seems to show that we need a mechanism in constraint solving where we can influence the direction of unification. In this case, we'd like to have h := A instead of A := h.

Or, in QuotesImple.treeMatch, before we create a nested GADT environment, we temporarily make GADT constraints in the outer context invisible to avoid interference? That seems to agree with the semantics of quoted pattrn match, could you confirm @nicolasstucki ?

@LPTK
Copy link
Contributor

LPTK commented May 11, 2021

Why is that code not typed similarly to the following?

scala> def test(x: Any) = x match
     |   case h: Set[h] => h: Set[h]
     |
def test(x: Any): Set[?]

scala> @main def d = test(Set(1)): Set[Int]
1 |@main def d = test(Set(1)): Set[Int]
  |              ^^^^^^^^^^^^
  |Found:    Set[?1.CAP]
  |Required: Set[Int]
  |
  |where:    ?1 is an unknown value of type scala.runtime.TypeBox[Nothing, Any]

Regardless of the bounds of h and of whether it's unified with A, it's of course unsound to just instantiate h with Nothing, because h is a rigid type variable.

@abgruszecki
Copy link
Contributor

abgruszecki commented May 11, 2021

@liufengyun again, how are you using the constraint? Are you extracting TypeBounds for h out of it? Does .fullBounds not give you what you expect? If not, it should be possible to write a function which extracts the information you need out of GadtConstraint.

EDIT: OK, I think I can guess what's happening here. Manually extracting the information out of the GADT constraint could solve this particular example, but there could be further problems down the road. Maybe it's possible to keep a set of "frozen" GADT-constrainable types in Context, types for which no further GADT bounds should be recorded? That sounds like it could work.

@liufengyun
Copy link
Contributor

@liufengyun again, how are you using the constraint? Are you extracting TypeBounds for h out of it? Does .fullBounds not give you what you expect? If not, it should be possible to write a function which extracts the information you need out of GadtConstraint.

I don't think reading the bounds help, as they just work as expected. Reading the bounds, we get Any ... Nothing for h.

From what I read in QuotesImple.treeMatch (file compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala), it works by first setting up a new environment:

val ctx1 = ctx.fresh.setFreshGADTBounds.addMode(dotc.core.Mode.GadtConstraintInference)

Later it ask GADT to approximate the type holes:

def typeHoleApproximation(sym: Symbol) =
   ctx1.gadt.approximation(sym, !sym.hasAnnotation(dotc.core.Symbols.defn.QuotedRuntimePatterns_fromAboveAnnot)).asInstanceOf[qctx1.reflect.TypeRepr].asType

@liufengyun
Copy link
Contributor

Maybe it's possible to keep a set of "frozen" GADT-constrainable types in Context, types for which no further GADT bounds should be recorded?

Yes, that could help as well.

@liufengyun
Copy link
Contributor

@LPTK From the user's perspective, they may have the same expectations. The technical difference is that in this example there is an interference of two GADT constraints -- the side effect of such interference is not expected by the programmer.

Technically, it might be related to type avoidance as well --- if we have A := h, the local type/symbol h is leaked out of its defining context.

@LPTK
Copy link
Contributor

LPTK commented May 11, 2021

Yes, this is all about type avoidance. An existential should be introduced (the ? in my example), because it is not sound to just pick an arbitrary bound.

From the user's perspective, they may have the same expectations.

It's not just a matter of expectations – it's a matter of the right thing to do. In case you missed it, this bug constitutes a soundness hole. If Set[Nothing] is indeed inferred, then (Test.inv(a).head: Int) + 1 will crash.

@liufengyun
Copy link
Contributor

In case you missed it, this bug constitutes a soundness hole. If Set[Nothing] is indeed inferred, then (Test.inv(a).head: Int) + 1 will crash.

Good point. This means the quoted pattern match logic may have to be improved. /cc: @nicolasstucki

@nicolasstucki
Copy link
Contributor

Yes, the logic needs to improve. #12402 does to preliminary steps to be able to improve that code.

@tpolecat
Copy link
Author

Is there a process by which I can get this prioritized for 3.0.3? Thanks.

@ahjohannessen
Copy link

Is anyone working on this?

@smarter
Copy link
Member

smarter commented Jan 12, 2022

Some good news: it looks like #14026 at least fixed the soundness hole pointed out by @LPTK above which means I did my job correctly 😅 :

def inv3[A](a: Set[A]): Set[Nothing] = Test.inv(a) // error:
//                                     ^^^^^^^^^^^
//                                     Found:    Set[A(param)1]
//                                     Required: Set[Nothing]

(I'm not familiar with QuoteMatcher so I can't comment on what is needed to actually infer A here instead of an unknown abstract type)

@ahjohannessen
Copy link

@smarter Is there a Scala version with #14026 in it that one can try out? :)

@nicolasstucki
Copy link
Contributor

@ahjohannessen you can use 3.1.2-RC1-bin-20220111-1292246-NIGHTLY. I believe 3.1.1-RC2 also contains that change.

@ahjohannessen
Copy link

@nicolasstucki Thanks 👍

@ahjohannessen
Copy link

@nicolasstucki I tried the following with scala-cli:

Test.scala:

import scala.quoted._

object Test {

  // Invariant (Set)

  def inv(arg: Expr[Any])(using Quotes): Expr[Any] =
    arg match {
      case '{ $h : Set[h] } => '{ $h : Set[h] }
    }

  transparent inline def inv(inline arg: Any): Any =
    ${ inv('arg) }

  // Covariant (List)

  def cov(arg: Expr[Any])(using Quotes): Expr[Any] =
    arg match {
      case '{ $h : List[h] } => '{ $h : List[h] }
    }

  transparent inline def cov(inline arg: Any): Any =
    ${ cov('arg) }

}

Run.scala:

object Run {
  def main(args: Array[String]): Unit =
    println(inv2(Set("fails")))

  def inv2[A](a: Set[A]): Set[A] = Test.inv(a)
}

scala-cli:

~ scala-cli -S 3.1.2-RC1-bin-20220111-1292246-NIGHTLY Test.scala Run.scala
[error] ./Run.scala:5:36: Found:    Set[A(param)2]
[error] Required: Set[A]
[error]   def inv2[A](a: Set[A]): Set[A] = Test.inv(a)
[error]                                    ^^^^^^^^^^^
Error compiling project (Scala 3.1.2-RC1-bin-20220111-1292246-NIGHTLY, JVM)

@smarter
Copy link
Member

smarter commented Jan 13, 2022

Yep, inference isn't fixed as state in my comment, it's just the soundness bug that is fixed.

@SethTisue
Copy link
Member

case '{ $h : Set[h] } => '{ $h : Set[h] }

you rang?

@SethTisue
Copy link
Member

Does anyone understand why it works in the covariant case, only failing in the invariant case?

@nicolasstucki
Copy link
Contributor

I don't. We get a TypeParamRef out of the constraint solver. Not sure if that is a new avoidance issue.

@LPTK
Copy link
Contributor

LPTK commented Jan 14, 2022

The funny thing is that if you remove the precise type annotation, you get a compiler crash:

object Run {
  def inv2[A](a: Set[A]): Any = Test.inv(a)
}
[error] java.lang.AssertionError: assertion failed: orphan parameter reference: TypeParamRef(A(param)1)
[error] scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleNewType(TreePickler.scala:286)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleType(TreePickler.scala:155)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree(TreePickler.scala:601)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree$$anonfun$32$$anonfun$1(TreePickler.scala:617)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.immutable.List.foreach(List.scala:333)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree$$anonfun$32(TreePickler.scala:617)
[error] dotty.tools.dotc.core.tasty.TreePickler.withLength(TreePickler.scala:58)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree(TreePickler.scala:617)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTpt(TreePickler.scala:313)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree$$anonfun$6(TreePickler.scala:455)
[error] dotty.tools.dotc.core.tasty.TreePickler.withLength(TreePickler.scala:58)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree(TreePickler.scala:455)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree$$anonfun$19(TreePickler.scala:510)
[error] dotty.tools.dotc.core.tasty.TreePickler.withLength(TreePickler.scala:58)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree(TreePickler.scala:516)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree$$anonfun$19(TreePickler.scala:510)
[error] dotty.tools.dotc.core.tasty.TreePickler.withLength(TreePickler.scala:58)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree(TreePickler.scala:516)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTreeUnlessEmpty(TreePickler.scala:316)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleDef$$anonfun$1(TreePickler.scala:332)
[error] dotty.tools.dotc.core.tasty.TreePickler.withLength(TreePickler.scala:58)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleDef(TreePickler.scala:334)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree(TreePickler.scala:557)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleStats$$anonfun$2(TreePickler.scala:357)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.immutable.List.foreach(List.scala:333)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleStats(TreePickler.scala:357)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree$$anonfun$26(TreePickler.scala:583)
[error] dotty.tools.dotc.core.tasty.TreePickler.withLength(TreePickler.scala:58)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree(TreePickler.scala:584)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleDef$$anonfun$1(TreePickler.scala:329)
[error] dotty.tools.dotc.core.tasty.TreePickler.withLength(TreePickler.scala:58)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleDef(TreePickler.scala:334)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree(TreePickler.scala:559)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleStats$$anonfun$2(TreePickler.scala:357)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.immutable.List.foreach(List.scala:333)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleStats(TreePickler.scala:357)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree$$anonfun$29(TreePickler.scala:599)
[error] dotty.tools.dotc.core.tasty.TreePickler.withLength(TreePickler.scala:58)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickleTree(TreePickler.scala:599)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickle$$anonfun$1(TreePickler.scala:770)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.immutable.List.foreach(List.scala:333)
[error] dotty.tools.dotc.core.tasty.TreePickler.pickle(TreePickler.scala:770)
[error] dotty.tools.dotc.transform.Pickler.run$$anonfun$1$$anonfun$1(Pickler.scala:69)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.immutable.List.foreach(List.scala:333)
[error] dotty.tools.dotc.transform.Pickler.run$$anonfun$1(Pickler.scala:106)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.immutable.List.foreach(List.scala:333)
[error] dotty.tools.dotc.transform.Pickler.run(Pickler.scala:106)
[error] dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:308)
[error] scala.collection.immutable.List.map(List.scala:246)
[error] dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:309)
[error] dotty.tools.dotc.transform.Pickler.runOn(Pickler.scala:111)
[error] dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:259)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1323)
[error] dotty.tools.dotc.Run.runPhases$1(Run.scala:270)
[error] dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:278)
[error] scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
[error] dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:68)
[error] dotty.tools.dotc.Run.compileUnits(Run.scala:287)
[error] dotty.tools.dotc.Run.compileSources(Run.scala:220)
[error] dotty.tools.dotc.Run.compile(Run.scala:204)
[error] dotty.tools.dotc.Driver.doCompile(Driver.scala:39)
[error] dotty.tools.xsbt.CompilerBridgeDriver.run(CompilerBridgeDriver.java:88)
[error] dotty.tools.xsbt.CompilerBridge.run(CompilerBridge.java:22)
[error] sbt.internal.inc.AnalyzingCompiler.compile(AnalyzingCompiler.scala:91)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$7(MixedAnalyzingCompiler.scala:186)
[error] scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
[error] sbt.internal.inc.MixedAnalyzingCompiler.timed(MixedAnalyzingCompiler.scala:241)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4(MixedAnalyzingCompiler.scala:176)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4$adapted(MixedAnalyzingCompiler.scala:157)
[error] sbt.internal.inc.JarUtils$.withPreviousJar(JarUtils.scala:239)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compileScala$1(MixedAnalyzingCompiler.scala:157)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compile(MixedAnalyzingCompiler.scala:204)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1(IncrementalCompilerImpl.scala:528)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1$adapted(IncrementalCompilerImpl.scala:528)
[error] sbt.internal.inc.Incremental$.$anonfun$apply$5(Incremental.scala:174)
[error] sbt.internal.inc.Incremental$.$anonfun$apply$5$adapted(Incremental.scala:172)
[error] sbt.internal.inc.Incremental$$anon$2.run(Incremental.scala:457)
[error] sbt.internal.inc.IncrementalCommon$CycleState.next(IncrementalCommon.scala:116)
[error] sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:56)
[error] sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:52)
[error] sbt.internal.inc.IncrementalCommon.cycle(IncrementalCommon.scala:261)
[error] sbt.internal.inc.Incremental$.$anonfun$incrementalCompile$8(Incremental.scala:412)
[error] sbt.internal.inc.Incremental$.withClassfileManager(Incremental.scala:499)
[error] sbt.internal.inc.Incremental$.incrementalCompile(Incremental.scala:399)
[error] sbt.internal.inc.Incremental$.apply(Incremental.scala:166)
[error] sbt.internal.inc.IncrementalCompilerImpl.compileInternal(IncrementalCompilerImpl.scala:528)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileIncrementally$1(IncrementalCompilerImpl.scala:482)
[error] sbt.internal.inc.IncrementalCompilerImpl.handleCompilationError(IncrementalCompilerImpl.scala:332)
[error] sbt.internal.inc.IncrementalCompilerImpl.compileIncrementally(IncrementalCompilerImpl.scala:420)
[error] sbt.internal.inc.IncrementalCompilerImpl.compile(IncrementalCompilerImpl.scala:137)
[error] sbt.Defaults$.compileIncrementalTaskImpl(Defaults.scala:2357)
[error] sbt.Defaults$.$anonfun$compileIncrementalTask$2(Defaults.scala:2314)
[error] sbt.internal.io.Retry$.apply(Retry.scala:46)
[error] sbt.internal.io.Retry$.apply(Retry.scala:28)
[error] sbt.internal.io.Retry$.apply(Retry.scala:23)
[error] sbt.internal.server.BspCompileTask$.compute(BspCompileTask.scala:31)
[error] sbt.Defaults$.$anonfun$compileIncrementalTask$1(Defaults.scala:2310)
[error] scala.Function1.$anonfun$compose$1(Function1.scala:49)
[error] sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:62)
[error] sbt.std.Transform$$anon$4.work(Transform.scala:68)
[error] sbt.Execute.$anonfun$submit$2(Execute.scala:282)
[error] sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:23)
[error] sbt.Execute.work(Execute.scala:291)
[error] sbt.Execute.$anonfun$submit$1(Execute.scala:282)
[error] sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:265)
[error] sbt.CompletionService$$anon$2.call(CompletionService.scala:64)
[error] java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error] java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
[error] java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error] java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
[error] java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
[error] java.lang.Thread.run(Thread.java:748)

With -Xprint:typer (keeping the annotation):

[info] package progabs {
[info]   final lazy module val Run: Run = new Run()
[info]   final module class Run() extends Object() { this: Run.type =>
[info]     def inv2[A >: Nothing <: Any](a: Set[A]): Set[A] = a:Set[A(param)1]
[info]   }
[info] }

Actually that may not be so funny for @smarter 😬

@dwijnand dwijnand linked a pull request Feb 7, 2022 that will close this issue
@dwijnand dwijnand assigned SethTisue and dwijnand and unassigned liufengyun Feb 7, 2022
@LPTK
Copy link
Contributor

LPTK commented Feb 8, 2022

@dwijnand That's a step in the right direcrtion, but it's still wrong.

Looking at your change:

object Test:
  // [...]
  def inv2[X](a: Set[X]): Set[X]   = Macro.inv(a) // doesn't compile; Set[Nothing] is inferred

Referring to my comment from #12343 (comment):

it is not sound to just pick an arbitrary bound
[...]
If Set[Nothing] is indeed inferred, then (Test.inv(a).head: Int) + 1 will crash.

I think this issue shouldn't be closed.

@dwijnand
Copy link
Member

dwijnand commented Feb 8, 2022

Sorry for misleading there. That comment is from the original case. Now it infers Set[X] as desired, and sound.

I should've updated the comment but I only realised after I had complete green CI runs..

@LPTK
Copy link
Contributor

LPTK commented Feb 8, 2022

Ah ok sure, good to know! I missed this was in the pos tests. Would be nice to fix the comment still 😬

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants