From e896db28f8d5a19353fa7914d6008edd9bf637aa Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Wed, 3 Jul 2024 15:21:07 +0200 Subject: [PATCH 1/7] feat: infer expected type --- .../dotty/tools/pc/InferExpectedType.scala | 128 ++++++++ .../tools/pc/ScalaPresentationCompiler.scala | 10 + .../pc/utils/InteractiveEnrichments.scala | 3 + .../pc/tests/InferExpectedTypeSuite.scala | 292 ++++++++++++++++++ 4 files changed, 433 insertions(+) create mode 100644 presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala create mode 100644 presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala new file mode 100644 index 000000000000..85beb31d5d29 --- /dev/null +++ b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala @@ -0,0 +1,128 @@ +package dotty.tools.pc + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.core.StdNames +import dotty.tools.dotc.core.Symbols +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.core.Types.Type +import dotty.tools.dotc.interactive.Interactive +import dotty.tools.dotc.interactive.InteractiveDriver +import dotty.tools.dotc.util.SourceFile +import dotty.tools.dotc.util.Spans.Span +import dotty.tools.pc.IndexedContext +import dotty.tools.pc.printer.ShortenedTypePrinter +import dotty.tools.pc.printer.ShortenedTypePrinter.IncludeDefaultParam +import dotty.tools.pc.utils.InteractiveEnrichments.* + +import scala.meta.internal.metals.ReportContext +import scala.meta.pc.OffsetParams +import scala.meta.pc.SymbolSearch + +class InferExpectedType( + search: SymbolSearch, + driver: InteractiveDriver, + params: OffsetParams +)(implicit rc: ReportContext): + val uri = params.uri().nn + val code = params.text().nn + + val sourceFile = SourceFile.virtual(uri, code) + driver.run(uri, sourceFile) + + val ctx = driver.currentCtx + val pos = driver.sourcePosition(params) + + def infer() = + driver.compilationUnits.get(uri) match + case Some(unit) => + val path = + Interactive.pathTo(driver.openedTrees(uri), pos)(using ctx) + val newctx = ctx.fresh.setCompilationUnit(unit) + val tpdPath = + Interactive.pathTo(newctx.compilationUnit.tpdTree, pos.span)(using + newctx + ) + val locatedCtx = + Interactive.contextOfPath(tpdPath)(using newctx) + val indexedCtx = IndexedContext(locatedCtx) + val printer = + ShortenedTypePrinter(search, IncludeDefaultParam.ResolveLater)(using indexedCtx) + InterCompletionType.inferType(path)(using newctx).map{ + tpe => printer.tpe(tpe) + } + case None => None + +object InterCompletionType: + def inferType(path: List[Tree])(using Context): Option[Type] = + path match + case (lit: Literal) :: Select(Literal(_), _) :: Apply(Select(Literal(_), _), List(Literal(Constant(null)))) :: rest => inferType(rest, lit.span) + case ident :: rest => inferType(rest, ident.span) + case _ => None + + def inferType(path: List[Tree], span: Span)(using Context): Option[Type] = + path match + case Typed(expr, tpt) :: _ if expr.span.contains(span) && !tpt.tpe.isErroneous => Some(tpt.tpe) + case Block(_, expr) :: rest if expr.span.contains(span) => + inferType(rest, span) + case Bind(_, body) :: rest if body.span.contains(span) => inferType(rest, span) + case Alternative(_) :: rest => inferType(rest, span) + case Try(block, _, _) :: rest if block.span.contains(span) => inferType(rest, span) + case CaseDef(_, _, body) :: Try(_, cases, _) :: rest if body.span.contains(span) && cases.exists(_.span.contains(span)) => inferType(rest, span) + case If(cond, _, _) :: rest if !cond.span.contains(span) => inferType(rest, span) + case If(cond, _, _) :: rest if cond.span.contains(span) => Some(Symbols.defn.BooleanType) + case CaseDef(_, _, body) :: Match(_, cases) :: rest if body.span.contains(span) && cases.exists(_.span.contains(span)) => + inferType(rest, span) + case NamedArg(_, arg) :: rest if arg.span.contains(span) => inferType(rest, span) + // x match + // case @@ + case CaseDef(pat, _, _) :: Match(sel, cases) :: rest if pat.span.contains(span) && cases.exists(_.span.contains(span)) && !sel.tpe.isErroneous => + sel.tpe match + case tpe: TermRef => Some(tpe.symbol.info).filterNot(_.isErroneous) + case tpe => Some(tpe) + // List(@@) + case SeqLiteral(_, tpe) :: _ if !tpe.tpe.isErroneous => + Some(tpe.tpe) + // val _: T = @@ + // def _: T = @@ + case (defn: ValOrDefDef) :: rest if !defn.tpt.tpe.isErroneous => Some(defn.tpt.tpe) + // f(@@) + case (app: Apply) :: rest => + val param = + for { + ind <- app.args.zipWithIndex.collectFirst { + case (arg, id) if arg.span.contains(span) => id + } + params <- app.symbol.paramSymss.find(!_.exists(_.isTypeParam)) + param <- params.get(ind) + } yield param.info + param match + // def f[T](a: T): T = ??? + // f[Int](@@) + // val _: Int = f(@@) + case Some(t : TypeRef) if t.symbol.is(Flags.TypeParam) => + for { + (typeParams, args) <- + app match + case Apply(TypeApply(fun, args), _) => + val typeParams = fun.symbol.paramSymss.headOption.filter(_.forall(_.isTypeParam)) + typeParams.map((_, args.map(_.tpe))) + // val f: (j: "a") => Int + // f(@@) + case Apply(Select(v, StdNames.nme.apply), _) => + v.symbol.info match + case AppliedType(des, args) => + Some((des.typeSymbol.typeParams, args)) + case _ => None + case _ => None + ind = typeParams.indexOf(t.symbol) + tpe <- args.get(ind) + if !tpe.isErroneous + } yield tpe + case Some(tpe) => Some(tpe) + case _ => None + case _ => None + diff --git a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala index a8ab7af0d147..85de8e7d8439 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala @@ -30,6 +30,7 @@ import scala.meta.pc.{PcSymbolInformation as IPcSymbolInformation} import dotty.tools.dotc.reporting.StoreReporter import dotty.tools.pc.completions.CompletionProvider +import dotty.tools.pc.InferExpectedType import dotty.tools.pc.completions.OverrideCompletions import dotty.tools.pc.buildinfo.BuildInfo @@ -198,6 +199,15 @@ case class ScalaPresentationCompiler( .asJava } + def inferExpectedType(params: OffsetParams): CompletableFuture[ju.Optional[String]] = + compilerAccess.withInterruptableCompiler(Some(params))( + Optional.empty(), + params.token, + ) { access => + val driver = access.compiler() + new InferExpectedType(search, driver, params).infer().asJava + } + def shutdown(): Unit = compilerAccess.shutdown() diff --git a/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala b/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala index dd2fb3107c49..8ff11694ff1c 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala @@ -412,4 +412,7 @@ object InteractiveEnrichments extends CommonMtagsEnrichments: RefinedType(parent.dealias, name, refinedInfo.deepDealias) case dealised => dealised + extension[T] (list: List[T]) + def get(n: Int): Option[T] = if 0 <= n && n < list.size then Some(list(n)) else None + end InteractiveEnrichments diff --git a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala new file mode 100644 index 000000000000..3c40ee075a4c --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala @@ -0,0 +1,292 @@ +package dotty.tools.pc.tests + +import scala.language.unsafeNulls +import dotty.tools.pc.base.BasePCSuite +import scala.meta.internal.metals.CompilerOffsetParams +import java.nio.file.Paths +import scala.meta.internal.metals.EmptyCancelToken +import dotty.tools.pc.ScalaPresentationCompiler +import scala.meta.internal.mtags.CommonMtagsEnrichments.* + +import org.junit.Test +import org.junit.Ignore + +class InferExpectedTypeSuite extends BasePCSuite: + def check( + original: String, + expectedType: String, + fileName: String = "A.scala" + ): Unit = + presentationCompiler.restart() + val (code, offset) = params(original.replace("@@", "CURSOR@@"), fileName) + val offsetParams = CompilerOffsetParams( + Paths.get(fileName).toUri(), + code, + offset, + EmptyCancelToken + ) + presentationCompiler.asInstanceOf[ScalaPresentationCompiler].inferExpectedType(offsetParams).get().asScala match { + case Some(value) => assertNoDiff(value, expectedType) + case None => fail("Empty result.") + } + + @Test def basic = + check( + """|def doo: Double = @@ + |""".stripMargin, + """|Double + |""".stripMargin + ) + + @Test def `basic-param` = + check( + """|def paint(c: Int) = ??? + |val _ = paint(@@) + |""".stripMargin, + """|Int + |""".stripMargin + ) + + @Test def `type-ascription` = + check( + """|def doo = (@@ : Double) + |""".stripMargin, + """|Double + |""".stripMargin + ) + + @Ignore("Not handled correctly.") + @Test def list = + check( + """|val i: List[Int] = List(@@) + |""".stripMargin, + """|Int + |""".stripMargin + ) + + @Test def `list-singleton` = + check( + """|val i: List["foo"] = List("@@") + |""".stripMargin, + """|"foo" + |""".stripMargin + ) + + @Test def option = + check( + """|val i: Option[Int] = Option(@@) + |""".stripMargin, + """|Int + |""".stripMargin + ) + +// some structures + @Test def `with-block` = + check( + """|def c: Double = + | @@ + |""".stripMargin, + """|Double + |""".stripMargin + ) + + @Test def `if-statement` = + check( + """|def c(shouldBeBlue: Boolean): Int = + | if(shouldBeBlue) @@ + | else 2 + |""".stripMargin, + """|Int + |""".stripMargin + ) + + @Test def `if-statement-2` = + check( + """|def c(shouldBeBlue: Boolean): Int = + | if(shouldBeBlue) 1 + | else @@ + |""".stripMargin, + """|Int + |""".stripMargin + ) + + @Test def `if-statement-3` = + check( + """|def c(shouldBeBlue: Boolean): Int = + | if(@@) 3 + | else 2 + |""".stripMargin, + """|Boolean + |""".stripMargin + ) + + @Test def `try` = + check( + """|val _: Int = + | try { + | @@ + | } catch { + | case _ => + | } + |""".stripMargin, + """|Int + |""".stripMargin + ) + + @Test def `try-catch` = + check( + """|val _: Int = + | try { + | } catch { + | case _ => @@ + | } + |""".stripMargin, + """|Int + |""".stripMargin + ) + + @Test def `if-condition` = + check( + """|val _ = if @@ then 1 else 2 + |""".stripMargin, + """|Boolean + |""".stripMargin + ) + + @Test def `inline-if` = + check( + """|inline def o: Int = inline if ??? then @@ else ??? + |""".stripMargin, + """|Int + |""".stripMargin + ) + +// pattern matching + + @Test def `pattern-match` = + check( + """|val _ = + | List(1) match + | case @@ + |""".stripMargin, + """|List[Int] + |""".stripMargin + ) + + @Test def bind = + check( + """|val _ = + | List(1) match + | case name @ @@ + |""".stripMargin, + """|List[Int] + |""".stripMargin + ) + + @Test def alternative = + check( + """|val _ = + | List(1) match + | case Nil | @@ + |""".stripMargin, + """|List[Int] + |""".stripMargin + ) + + @Ignore("Unapply is not handled correctly.") + @Test def unapply = + check( + """|val _ = + | List(1) match + | case @@ :: _ => + |""".stripMargin, + """|Int + |""".stripMargin + ) + +// generic functions + + @Test def `any-generic` = + check( + """|val _ : List[Int] = identity(@@) + |""".stripMargin, + """|List[Int] + |""".stripMargin + ) + + @Test def `eq-generic` = + check( + """|def eq[T](a: T, b: T): Boolean = ??? + |val _ = eq(1, @@) + |""".stripMargin, + """|Int + |""".stripMargin + ) + + @Ignore("Generic functions are not handled correctly.") + @Test def flatmap = + check( + """|val _ : List[Int] = List().flatMap(_ => @@) + |""".stripMargin, + """|IterableOnce[Int] + |""".stripMargin + ) + + @Ignore("Generic functions are not handled correctly.") + @Test def `for-comprehension` = + check( + """|val _ : List[Int] = + | for { + | _ <- List("a", "b") + | } yield @@ + |""".stripMargin, + """|Int + |""".stripMargin + ) + +// bounds + @Ignore("Bounds are not handled correctly.") + @Test def any = + check( + """|trait Foo + |def foo[T](a: T): Boolean = ??? + |val _ = foo(@@) + |""".stripMargin, + """|<: Any + |""".stripMargin + ) + + @Ignore("Bounds are not handled correctly.") + @Test def `bounds-1` = + check( + """|trait Foo + |def foo[T <: Foo](a: Foo): Boolean = ??? + |val _ = foo(@@) + |""".stripMargin, + """|<: Foo + |""".stripMargin + ) + + @Ignore("Bounds are not handled correctly.") + @Test def `bounds-2` = + check( + """|trait Foo + |def foo[T :> Foo](a: Foo): Boolean = ??? + |val _ = foo(@@) + |""".stripMargin, + """|:> Foo + |""".stripMargin + ) + + @Ignore("Bounds are not handled correctly.") + @Test def `bounds-3` = + check( + """|trait A + |class B extends A + |class C extends B + |def roo[F >: C <: A](f: F) = ??? + |val kjk = roo(@@) + |""".stripMargin, + """|>: C <: A + |""".stripMargin + ) From da42229a8053bb86ccee3c0723f6105ea0748195 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 17 Aug 2024 14:53:52 +0100 Subject: [PATCH 2/7] Add InferExpectedTypeSuite.map and fixup others --- .../tools/pc/tests/InferExpectedTypeSuite.scala | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala index 3c40ee075a4c..e296f9a70839 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala @@ -26,7 +26,7 @@ class InferExpectedTypeSuite extends BasePCSuite: EmptyCancelToken ) presentationCompiler.asInstanceOf[ScalaPresentationCompiler].inferExpectedType(offsetParams).get().asScala match { - case Some(value) => assertNoDiff(value, expectedType) + case Some(value) => assertNoDiff(expectedType, value) case None => fail("Empty result.") } @@ -232,6 +232,15 @@ class InferExpectedTypeSuite extends BasePCSuite: |""".stripMargin ) + @Ignore("Generic functions are not handled correctly.") + @Test def map = + check( + """|val _ : List[Int] = List().map(_ => @@) + |""".stripMargin, + """|Int + |""".stripMargin + ) + @Ignore("Generic functions are not handled correctly.") @Test def `for-comprehension` = check( @@ -260,7 +269,7 @@ class InferExpectedTypeSuite extends BasePCSuite: @Test def `bounds-1` = check( """|trait Foo - |def foo[T <: Foo](a: Foo): Boolean = ??? + |def foo[T <: Foo](a: T): Boolean = ??? |val _ = foo(@@) |""".stripMargin, """|<: Foo @@ -271,7 +280,7 @@ class InferExpectedTypeSuite extends BasePCSuite: @Test def `bounds-2` = check( """|trait Foo - |def foo[T :> Foo](a: Foo): Boolean = ??? + |def foo[T >: Foo](a: T): Boolean = ??? |val _ = foo(@@) |""".stripMargin, """|:> Foo From 0b4e6771bad9f91f93180fd0bcd30ca05764928e Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 17 Aug 2024 15:19:39 +0100 Subject: [PATCH 3/7] Fix InferExpectedTypeSuite.unapply --- .../src/main/dotty/tools/pc/InferExpectedType.scala | 6 ++++++ .../test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala index 85beb31d5d29..260a28392093 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala @@ -11,6 +11,8 @@ import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.core.Types.Type import dotty.tools.dotc.interactive.Interactive import dotty.tools.dotc.interactive.InteractiveDriver +import dotty.tools.dotc.typer.Applications.UnapplyArgs +import dotty.tools.dotc.util.NoSourcePosition import dotty.tools.dotc.util.SourceFile import dotty.tools.dotc.util.Spans.Span import dotty.tools.pc.IndexedContext @@ -89,6 +91,10 @@ object InterCompletionType: // val _: T = @@ // def _: T = @@ case (defn: ValOrDefDef) :: rest if !defn.tpt.tpe.isErroneous => Some(defn.tpt.tpe) + case UnApply(fun, _, pats) :: _ => + val ind = pats.indexWhere(_.span.contains(span)) + if ind < 0 then None + else Some(UnapplyArgs(fun.tpe.finalResultType, fun, pats, NoSourcePosition).argTypes(ind)) // f(@@) case (app: Apply) :: rest => val param = diff --git a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala index e296f9a70839..3bc1964735e4 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala @@ -193,7 +193,6 @@ class InferExpectedTypeSuite extends BasePCSuite: |""".stripMargin ) - @Ignore("Unapply is not handled correctly.") @Test def unapply = check( """|val _ = From b34df4d8c45bb01df46754b900df276d9beb886b Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 17 Aug 2024 15:57:24 +0100 Subject: [PATCH 4/7] Fix InferExpectedTypeSuite.list with Apply fixes --- .../dotty/tools/dotc/typer/Applications.scala | 10 ++-- .../dotty/tools/dotc/util/Signatures.scala | 2 +- .../pc/tests/InferExpectedTypeSuite.scala | 1 - .../signaturehelp/SignatureHelpSuite.scala | 54 ++++++++++++++++++- 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 33ab9f210634..c6d8fd80fd60 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -571,7 +571,7 @@ trait Applications extends Compatibility { fail(TypeMismatch(methType.resultType, resultType, None)) // match all arguments with corresponding formal parameters - matchArgs(orderedArgs, methType.paramInfos, 0) + if success then matchArgs(orderedArgs, methType.paramInfos, 0) case _ => if (methType.isError) ok = false else fail(em"$methString does not take parameters") @@ -666,7 +666,7 @@ trait Applications extends Compatibility { * @param n The position of the first parameter in formals in `methType`. */ def matchArgs(args: List[Arg], formals: List[Type], n: Int): Unit = - if (success) formals match { + formals match { case formal :: formals1 => def checkNoVarArg(arg: Arg) = @@ -878,7 +878,9 @@ trait Applications extends Compatibility { init() def addArg(arg: Tree, formal: Type): Unit = - typedArgBuf += adapt(arg, formal.widenExpr) + val typedArg = adapt(arg, formal.widenExpr) + typedArgBuf += typedArg + ok = ok & !typedArg.tpe.isError def makeVarArg(n: Int, elemFormal: Type): Unit = { val args = typedArgBuf.takeRight(n).toList @@ -943,7 +945,7 @@ trait Applications extends Compatibility { var typedArgs = typedArgBuf.toList def app0 = cpy.Apply(app)(normalizedFun, typedArgs) // needs to be a `def` because typedArgs can change later val app1 = - if (!success || typedArgs.exists(_.tpe.isError)) app0.withType(UnspecifiedErrorType) + if !success then app0.withType(UnspecifiedErrorType) else { if isJavaAnnotConstr(methRef.symbol) then // #19951 Make sure all arguments are NamedArgs for Java annotations diff --git a/compiler/src/dotty/tools/dotc/util/Signatures.scala b/compiler/src/dotty/tools/dotc/util/Signatures.scala index ae6bc583bae8..3b45d8f2fa51 100644 --- a/compiler/src/dotty/tools/dotc/util/Signatures.scala +++ b/compiler/src/dotty/tools/dotc/util/Signatures.scala @@ -651,7 +651,7 @@ object Signatures { * * @param err The error message to inspect. * @param params The parameters that were given at the call site. - * @param alreadyCurried Index of paramss we are currently in. + * @param paramssIndex Index of paramss we are currently in. * * @return A pair composed of the index of the best alternative (0 if no alternatives * were found), and the list of alternatives. diff --git a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala index 3bc1964735e4..94b0f92d0c7f 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala @@ -55,7 +55,6 @@ class InferExpectedTypeSuite extends BasePCSuite: |""".stripMargin ) - @Ignore("Not handled correctly.") @Test def list = check( """|val i: List[Int] = List(@@) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/signaturehelp/SignatureHelpSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/signaturehelp/SignatureHelpSuite.scala index 2b458ced9683..bd9f8edeef49 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/signaturehelp/SignatureHelpSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/signaturehelp/SignatureHelpSuite.scala @@ -2,7 +2,7 @@ package dotty.tools.pc.tests.signaturehelp import dotty.tools.pc.base.BaseSignatureHelpSuite -import org.junit.Test +import org.junit.{ Ignore, Test } class SignatureHelpSuite extends BaseSignatureHelpSuite: @@ -253,6 +253,20 @@ class SignatureHelpSuite extends BaseSignatureHelpSuite: ) @Test def `tparam5` = + check( + """ + |object a { + | List[Int](1).lengthCompare(@@) + |} + """.stripMargin, + """|lengthCompare(len: Int): Int + | ^^^^^^^^ + |lengthCompare(that: Iterable[?]): Int + |""".stripMargin + ) + + @Ignore("See if applyCallInfo can still inform on lengthCompare's sig, even if recv is in error") + @Test def `tparam5_TypeMismatch` = check( """ |object a { @@ -265,6 +279,31 @@ class SignatureHelpSuite extends BaseSignatureHelpSuite: |""".stripMargin ) + @Test def `tparam5_nonvarargs` = + check( + """ + |object a { + | Option[Int](1).getOrElse(@@) + |} + """.stripMargin, + """|getOrElse[B >: Int](default: => B): B + | ^^^^^^^^^^^^^ + |""".stripMargin + ) + + @Ignore("Similar to `tparam5_TypeMismatch`") + @Test def `tparam5_nonvarargs_TypeMismatch` = + check( + """ + |object a { + | Option[String](1).getOrElse(@@) + |} + """.stripMargin, + """|getOrElse[B >: String](default: => B): B + | ^^^^^^^^^^^^^ + |""".stripMargin + ) + @Test def `error1` = check( """ @@ -547,6 +586,19 @@ class SignatureHelpSuite extends BaseSignatureHelpSuite: ) @Test def `last-arg1` = + check( + """ + |object A { + | List[Int](1).map(a => @@) + |} + """.stripMargin, + """|map[B](f: Int => B): List[B] + | ^^^^^^^^^^^ + |""".stripMargin + ) + + @Ignore("Similar to `tparam5_TypeMismatch`") + @Test def `last-arg1_TypeMismatch` = check( """ |object A { From 24051098d61474b68a7b8ef97b3b5caa0ece9678 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 17 Aug 2024 17:07:47 +0100 Subject: [PATCH 5/7] Fix InferExpectedTypeSuite.bounds expectations.. --- .../tools/pc/tests/InferExpectedTypeSuite.scala | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala index 94b0f92d0c7f..1a04c5d8c864 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala @@ -252,40 +252,36 @@ class InferExpectedTypeSuite extends BasePCSuite: ) // bounds - @Ignore("Bounds are not handled correctly.") @Test def any = check( """|trait Foo |def foo[T](a: T): Boolean = ??? |val _ = foo(@@) |""".stripMargin, - """|<: Any + """|Any |""".stripMargin ) - @Ignore("Bounds are not handled correctly.") @Test def `bounds-1` = check( """|trait Foo |def foo[T <: Foo](a: T): Boolean = ??? |val _ = foo(@@) |""".stripMargin, - """|<: Foo + """|Foo |""".stripMargin ) - @Ignore("Bounds are not handled correctly.") @Test def `bounds-2` = check( """|trait Foo |def foo[T >: Foo](a: T): Boolean = ??? |val _ = foo(@@) |""".stripMargin, - """|:> Foo - |""".stripMargin + """|Foo + |""".stripMargin // ideally Any (maybe?) ) - @Ignore("Bounds are not handled correctly.") @Test def `bounds-3` = check( """|trait A @@ -294,6 +290,6 @@ class InferExpectedTypeSuite extends BasePCSuite: |def roo[F >: C <: A](f: F) = ??? |val kjk = roo(@@) |""".stripMargin, - """|>: C <: A - |""".stripMargin + """|C + |""".stripMargin // ideally A ) From 5aaea2f6e847be0881f2116cc206e2057d64b524 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 17 Aug 2024 12:01:47 +0100 Subject: [PATCH 6/7] Print failures in boundsViolations, via TypeComparer.explaining TypeComparer.explaining is like TypeComparer.explained, but instead of just returning the trace, returns the result, still allowing the trace to be accessed via .lastTrace, as exemplified by implementing TypeComparer.explained in terms of TypeComparer.explaining. Also add, but leave commented out the call of, a trace.dumpStack, which is like Thread.dumpStack(), but outputing to System.out, like all our tracing does - so the two don't interact when unbuffering onto the terminal. Also, we can do customisations like filtering out stack elements, limiting the stack. --- .../dotty/tools/dotc/core/TypeComparer.scala | 12 ++++++++---- .../src/dotty/tools/dotc/core/TypeOps.scala | 19 +++++++++++++++---- .../dotty/tools/dotc/reporting/trace.scala | 12 ++++++++++++ 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 0f74ca40843b..5aab2e277693 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3268,9 +3268,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling /** The trace of comparison operations when performing `op` */ def explained[T](op: ExplainingTypeComparer => T, header: String = "Subtype trace:", short: Boolean)(using Context): String = - val cmp = explainingTypeComparer(short) - inSubComparer(cmp)(op) - cmp.lastTrace(header) + explaining(cmp => { op(cmp); cmp.lastTrace(header) }, short) + + def explaining[T](op: ExplainingTypeComparer => T, short: Boolean)(using Context): T = + inSubComparer(explainingTypeComparer(short))(op) def reduceMatchWith[T](op: MatchReducer => T)(using Context): T = inSubComparer(matchReducer)(op) @@ -3440,6 +3441,9 @@ object TypeComparer { def explained[T](op: ExplainingTypeComparer => T, header: String = "Subtype trace:", short: Boolean = false)(using Context): String = comparing(_.explained(op, header, short)) + def explaining[T](op: ExplainingTypeComparer => T, short: Boolean = false)(using Context): T = + comparing(_.explaining(op, short)) + def reduceMatchWith[T](op: MatchReducer => T)(using Context): T = comparing(_.reduceMatchWith(op)) @@ -3873,7 +3877,7 @@ class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeCompa override def recur(tp1: Type, tp2: Type): Boolean = def moreInfo = if Config.verboseExplainSubtype || ctx.settings.verbose.value - then s" ${tp1.getClass} ${tp2.getClass}" + then s" ${tp1.className} ${tp2.className}" else "" val approx = approxState def approxStr = if short then "" else approx.show diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 8089735bdb0f..0d8801b646ee 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -691,11 +691,22 @@ object TypeOps: val hiBound = instantiate(bounds.hi, skolemizedArgTypes) val loBound = instantiate(bounds.lo, skolemizedArgTypes) - def check(using Context) = { - if (!(lo <:< hiBound)) violations += ((arg, "upper", hiBound)) - if (!(loBound <:< hi)) violations += ((arg, "lower", loBound)) + def check(tp1: Type, tp2: Type, which: String, bound: Type)(using Context) = { + val isSub = TypeComparer.explaining { cmp => + val isSub = cmp.isSubType(tp1, tp2) + if !isSub then + if !ctx.typerState.constraint.domainLambdas.isEmpty then + typr.println(i"${ctx.typerState.constraint}") + if !ctx.gadt.symbols.isEmpty then + typr.println(i"${ctx.gadt}") + typr.println(cmp.lastTrace(i"checkOverlapsBounds($lo, $hi, $arg, $bounds)($which)")) + //trace.dumpStack() + isSub + }//(using ctx.fresh.setSetting(ctx.settings.verbose, true)) // uncomment to enable moreInfo in ExplainingTypeComparer recur + if !isSub then violations += ((arg, which, bound)) } - check(using checkCtx) + check(lo, hiBound, "upper", hiBound)(using checkCtx) + check(loBound, hi, "lower", loBound)(using checkCtx) } def loop(args: List[Tree], boundss: List[TypeBounds]): Unit = args match diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index fbbc3d990969..732e779e9bf7 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -27,6 +27,18 @@ object trace extends TraceSyntax: object log extends TraceSyntax: inline def isEnabled: true = true protected val isForced = false + + def dumpStack(limit: Int = -1): Unit = { + val out = Console.out + val exc = new Exception("Dump Stack") + var stack = exc.getStackTrace + .filter(e => !e.getClassName.startsWith("dotty.tools.dotc.reporting.TraceSyntax")) + .filter(e => !e.getClassName.startsWith("dotty.tools.dotc.reporting.trace")) + if limit >= 0 then + stack = stack.take(limit) + exc.setStackTrace(stack) + exc.printStackTrace(out) + } end trace /** This module is carefully optimized to give zero overhead if Config.tracingEnabled From 43fc10c2b382eb09e3dcac9a4652ddd6a9f4261e Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 21 Aug 2024 13:59:18 +0100 Subject: [PATCH 7/7] Give up on InferExpectedTypeSuite.map/flatMap But keep the extraction of the instDecision logic, and keep the tests cases I used in studying this change. Also simplify and update InstantiateModel. Martin had tweaked my i14218 fix to make it more conservative: in the (NN, UU) case (i.e. no inferred bounds, only a UU upper bound declared) for covariant type parameters, revert back to minimising to Nothing rather than maximising to the declared bound. --- .../dotty/tools/dotc/typer/Inferencing.scala | 55 ++++++++++------- .../dotty/tools/dotc/typer/ProtoTypes.scala | 4 +- .../tools/dotc/typer/InstantiateModel.scala | 49 ++++++++------- .../pc/tests/InferExpectedTypeSuite.scala | 15 ++--- tests/pos/i21390.TrieMap.scala | 12 ++++ tests/pos/i21390.zio.scala | 59 +++++++++++++++++++ 6 files changed, 138 insertions(+), 56 deletions(-) create mode 100644 tests/pos/i21390.TrieMap.scala create mode 100644 tests/pos/i21390.zio.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index c41fb2e60ae5..2ebcd96d5bde 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -240,25 +240,12 @@ object Inferencing { && { var fail = false var skip = false - val direction = instDirection(tvar.origin) - if minimizeSelected then - if direction <= 0 && tvar.hasLowerBound then - skip = instantiate(tvar, fromBelow = true) - else if direction >= 0 && tvar.hasUpperBound then - skip = instantiate(tvar, fromBelow = false) - // else hold off instantiating unbounded unconstrained variable - else if direction != 0 then - skip = instantiate(tvar, fromBelow = direction < 0) - else if variance >= 0 && tvar.hasLowerBound then - skip = instantiate(tvar, fromBelow = true) - else if (variance > 0 || variance == 0 && !tvar.hasUpperBound) - && force.ifBottom == IfBottom.ok - then // if variance == 0, prefer upper bound if one is given - skip = instantiate(tvar, fromBelow = true) - else if variance >= 0 && force.ifBottom == IfBottom.fail then - fail = true - else - toMaximize = tvar :: toMaximize + instDecision(tvar, variance, minimizeSelected, force.ifBottom) match + case Decision.Min => skip = instantiate(tvar, fromBelow = true) + case Decision.Max => skip = instantiate(tvar, fromBelow = false) + case Decision.Skip => // hold off instantiating unbounded unconstrained variable + case Decision.Fail => fail = true + case Decision.ToMax => toMaximize ::= tvar !fail && (skip || foldOver(x, tvar)) } case tp => foldOver(x, tp) @@ -452,9 +439,32 @@ object Inferencing { if (!cmp.isSubTypeWhenFrozen(constrained.lo, original.lo)) 1 else 0 val approxAbove = if (!cmp.isSubTypeWhenFrozen(original.hi, constrained.hi)) 1 else 0 + //println(i"instDirection($param) = $approxAbove - $approxBelow original=[$original] constrained=[$constrained]") approxAbove - approxBelow } + /** The instantiation decision for given poly param computed from the constraint. */ + enum Decision { case Min; case Max; case ToMax; case Skip; case Fail } + private def instDecision(tvar: TypeVar, v: Int, minimizeSelected: Boolean, ifBottom: IfBottom)(using Context): Decision = + import Decision.* + val direction = instDirection(tvar.origin) + val dec = if minimizeSelected then + if direction <= 0 && tvar.hasLowerBound then Min + else if direction >= 0 && tvar.hasUpperBound then Max + else Skip + else if direction != 0 then if direction < 0 then Min else Max + else if tvar.hasLowerBound then if v >= 0 then Min else ToMax + else ifBottom match + // What's left are unconstrained tvars with at most a non-Any param upperbound: + // * IfBottom.flip will always maximise to the param upperbound, for all variances + // * IfBottom.fail will fail the IFD check, for covariant or invariant tvars, maximise contravariant tvars + // * IfBottom.ok will minimise to Nothing covariant and unbounded invariant tvars, and max to Any the others + case IfBottom.ok => if v > 0 || v == 0 && !tvar.hasUpperBound then Min else ToMax // prefer upper bound if one is given + case IfBottom.fail => if v >= 0 then Fail else ToMax + case ifBottom_flip => ToMax + //println(i"instDecision($tvar, v=v, minimizedSelected=$minimizeSelected, $ifBottom) dir=$direction = $dec") + dec + /** Following type aliases and stripping refinements and annotations, if one arrives at a * class type reference where the class has a companion module, a reference to * that companion module. Otherwise NoType @@ -651,7 +661,7 @@ trait Inferencing { this: Typer => val ownedVars = state.ownedVars if (ownedVars ne locked) && !ownedVars.isEmpty then - val qualifying = ownedVars -- locked + val qualifying = (ownedVars -- locked).toList if (!qualifying.isEmpty) { typr.println(i"interpolate $tree: ${tree.tpe.widen} in $state, pt = $pt, owned vars = ${state.ownedVars.toList}%, %, qualifying = ${qualifying.toList}%, %, previous = ${locked.toList}%, % / ${state.constraint}") val resultAlreadyConstrained = @@ -687,6 +697,10 @@ trait Inferencing { this: Typer => def constraint = state.constraint + trace(i"interpolateTypeVars($tree: ${tree.tpe}, $pt, $qualifying)", typr, (_: Any) => i"$qualifying\n$constraint\n${ctx.gadt}") { + //println(i"$constraint") + //println(i"${ctx.gadt}") + /** Values of this type report type variables to instantiate with variance indication: * +1 variable appears covariantly, can be instantiated from lower bound * -1 variable appears contravariantly, can be instantiated from upper bound @@ -804,6 +818,7 @@ trait Inferencing { this: Typer => end doInstantiate doInstantiate(filterByDeps(toInstantiate)) + } } end if tree diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 5909cda8c428..a69a63d1ceef 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -18,10 +18,11 @@ import config.Printers.typr import Inferencing.* import ErrorReporting.* import util.SourceFile +import util.Spans.{NoSpan, Span} import TypeComparer.necessarySubType +import reporting.* import scala.annotation.internal.sharable -import dotty.tools.dotc.util.Spans.{NoSpan, Span} object ProtoTypes { @@ -83,6 +84,7 @@ object ProtoTypes { * fits the given expected result type. */ def constrainResult(mt: Type, pt: Type)(using Context): Boolean = + trace(i"constrainResult($mt, $pt)", typr): val savedConstraint = ctx.typerState.constraint val res = pt.widenExpr match { case pt: FunProto => diff --git a/compiler/test/dotty/tools/dotc/typer/InstantiateModel.scala b/compiler/test/dotty/tools/dotc/typer/InstantiateModel.scala index b08062913dac..9841fcbafb5b 100644 --- a/compiler/test/dotty/tools/dotc/typer/InstantiateModel.scala +++ b/compiler/test/dotty/tools/dotc/typer/InstantiateModel.scala @@ -4,22 +4,16 @@ package typer // Modelling the decision in IsFullyDefined object InstantiateModel: - enum LB { case NN; case LL; case L1 }; import LB.* - enum UB { case AA; case UU; case U1 }; import UB.* - enum Var { case V; case NotV }; import Var.* - enum MSe { case M; case NotM }; import MSe.* - enum Bot { case Fail; case Ok; case Flip }; import Bot.* - enum Act { case Min; case Max; case ToMax; case Skip; case False }; import Act.* + enum LB { case NN; case LL; case L1 }; import LB.* + enum UB { case AA; case UU; case U1 }; import UB.* + enum Decision { case Min; case Max; case ToMax; case Skip; case Fail }; import Decision.* // NN/AA = Nothing/Any // LL/UU = the original bounds, on the type parameter // L1/U1 = the constrained bounds, on the type variable - // V = variance >= 0 ("non-contravariant") - // MSe = minimisedSelected - // Bot = IfBottom // ToMax = delayed maximisation, via addition to toMaximize // Skip = minimisedSelected "hold off instantiating" - // False = return false + // Fail = IfBottom.fail's bail option // there are 9 combinations: // # | LB | UB | d | // d = direction @@ -34,24 +28,27 @@ object InstantiateModel: // 8 | NN | UU | 0 | T <: UU // 9 | NN | AA | 0 | T - def decide(lb: LB, ub: UB, v: Var, bot: Bot, m: MSe): Act = (lb, ub) match + def instDecision(lb: LB, ub: UB, v: Int, ifBottom: IfBottom, min: Boolean) = (lb, ub) match case (L1, AA) => Min case (L1, UU) => Min case (LL, U1) => Max case (NN, U1) => Max - case (L1, U1) => if m==M || v==V then Min else ToMax - case (LL, UU) => if m==M || v==V then Min else ToMax - case (LL, AA) => if m==M || v==V then Min else ToMax - - case (NN, UU) => bot match - case _ if m==M => Max - //case Ok if v==V => Min // removed, i14218 fix - case Fail if v==V => False - case _ => ToMax - - case (NN, AA) => bot match - case _ if m==M => Skip - case Ok if v==V => Min - case Fail if v==V => False - case _ => ToMax + case (L1, U1) => if min then Min else pickVar(v, Min, Min, ToMax) + case (LL, UU) => if min then Min else pickVar(v, Min, Min, ToMax) + case (LL, AA) => if min then Min else pickVar(v, Min, Min, ToMax) + + case (NN, UU) => ifBottom match + case _ if min => Max + case IfBottom.ok => pickVar(v, Min, ToMax, ToMax) + case IfBottom.fail => pickVar(v, Fail, Fail, ToMax) + case IfBottom.flip => ToMax + + case (NN, AA) => ifBottom match + case _ if min => Skip + case IfBottom.ok => pickVar(v, Min, Min, ToMax) + case IfBottom.fail => pickVar(v, Fail, Fail, ToMax) + case IfBottom.flip => ToMax + + def pickVar[A](v: Int, cov: A, inv: A, con: A) = + if v > 0 then cov else if v == 0 then inv else con diff --git a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala index 1a04c5d8c864..ccdc68ef1cad 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala @@ -221,25 +221,22 @@ class InferExpectedTypeSuite extends BasePCSuite: |""".stripMargin ) - @Ignore("Generic functions are not handled correctly.") @Test def flatmap = check( """|val _ : List[Int] = List().flatMap(_ => @@) |""".stripMargin, - """|IterableOnce[Int] - |""".stripMargin + """|IterableOnce[Nothing] + |""".stripMargin // ideally IterableOnce[Int], but can't change interpolateTypeVars ) - @Ignore("Generic functions are not handled correctly.") @Test def map = check( """|val _ : List[Int] = List().map(_ => @@) |""".stripMargin, - """|Int - |""".stripMargin + """|Nothing + |""".stripMargin // ideally Int, but can't change interpolateTypeVars ) - @Ignore("Generic functions are not handled correctly.") @Test def `for-comprehension` = check( """|val _ : List[Int] = @@ -247,8 +244,8 @@ class InferExpectedTypeSuite extends BasePCSuite: | _ <- List("a", "b") | } yield @@ |""".stripMargin, - """|Int - |""".stripMargin + """|Nothing + |""".stripMargin // ideally Int, but can't change interpolateTypeVars ) // bounds diff --git a/tests/pos/i21390.TrieMap.scala b/tests/pos/i21390.TrieMap.scala new file mode 100644 index 000000000000..e49cca353485 --- /dev/null +++ b/tests/pos/i21390.TrieMap.scala @@ -0,0 +1,12 @@ +// Minimised from scala.collection.concurrent.LNode +// Useful as a minimisation of how, +// If we were to change the type interpolation +// to minimise to the inferred "X" type, +// then this is a minimisation of how the (ab)use of +// GADT constraints to handle class type params +// can fail PostTyper, -Ytest-pickler, and probably others. + +import scala.language.experimental.captureChecking + +class Foo[X](xs: List[X]): + def this(a: X, b: X) = this(if (a == b) then a :: Nil else a :: b :: Nil) diff --git a/tests/pos/i21390.zio.scala b/tests/pos/i21390.zio.scala new file mode 100644 index 000000000000..3aece69632b3 --- /dev/null +++ b/tests/pos/i21390.zio.scala @@ -0,0 +1,59 @@ +// A minimisation of a community build failure in PR 21390 +// To see why changing the instantiation direction in interpolateTypeVars +// using the same logic as IsFullyDefined. +class Has[A] +object Has: + class Union[B, C] + object Union: + given HasHasUnion[B0 <: Has[?], C0 <: Has[?]]: Union[B0, C0] = ??? + +class Lay[+D]: + def and1[B1 >: D, C1](that: Lay[C1])(using Has.Union[B1, C1]): Lay[B1 & C1] = ??? + def and2[B2 >: D, C2](that: Lay[C2])(using Has.Union[B2, C2]): Lay[B2 & C2] = ??? + +class J; type X = Has[J] +class K; type Y = Has[K] +class L; type Z = Has[L] + +def t1(x: Lay[X], y: Lay[Y], z: Lay[Z]): Lay[X & Y & Z] = x.and1(y).and2(z) + +/* + +Here's what goes wrong in the tvar instantiation, in method t1: + +1) <== constrainResult(method and1, (using x$2: Union[B1, C1]): Lay[B1 & C1], ?{ and2: ? }) = true +2) ==> Has.Union[B0, C0] <: Has.Union[B1, C1 := Y]? +3) <== Has.Union[B0, C0] <: Has.Union[B1, C1 := Y] = OK + +1) B1 >: X B2 >: B1 & C1 +2) B1 >: X C1 := Y B2 >: B1 & Y B0 <: Has[?] C0 <: Has[?] +3) B1 >: X <: Has[?] C1 := Y B2 >: B1 & Y B0 := B1 C0 := Y + +1) Check that the result of and1 fits the expected .and2 call, inferring any necessary constraints +2) Initiate the check that calling HasHasUnion matches the needed Has.Union[B1, C1] parameter +3) In inferring that the need B0 := B1 and C0 := Y, we end up inferring B0's `<: Has[?]` on B1. + +4a) <== B1.instantiate(fromBelow = true ) = X +4b) <== B1.instantiate(fromBelow = false) = Has[?] +5a) <== B2.instantiate(fromBelow = true) = X & Y +5b) <== B2.instantiate(fromBelow = true) = Y +6) <== constrainResult(method and2, (using x$2: Has.Union[B2, C2]): Lay[B2 & C2], Lay[X & Y & Z]) = true + +4a) B2 >: X & Y +4b) B2 >: Y & Has[?] +5a) B2 := X & Y +5b) B2 := Y +6a) B2 >: X & Y C2 <: Z +6b) B2 >: Y C2 <: X & Z + +4) With the extra upper bound constraint, we end up maximising to Has[?] (4b) instead of minimising to X (4a) +5) Which leads to instantiating B2 to just Y (5b) instead of X & Y (5a) +6) Which leads the constraints from the result of and2 to infer X & Z (6b) instead of just Z (6a) + +-- [E007] Type Mismatch Error: tests/pos/i21390.zio.scala:14:73 ------------------------------------ +14 |def t1(x: Lay[X], y: Lay[Y], z: Lay[Z]): Lay[X & Y & Z] = x.and1(y).and2(z) + | ^ + | Found: (z : Lay[Z]) + | Required: Lay[X & Z] + +*/