diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 646874a7e5c1..8fc6307c426c 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)) @@ -3871,7 +3875,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 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/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/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/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/src/main/dotty/tools/pc/InferExpectedType.scala b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala new file mode 100644 index 000000000000..260a28392093 --- /dev/null +++ b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala @@ -0,0 +1,134 @@ +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.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 +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) + 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 = + 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..ccdc68ef1cad --- /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(expectedType, value) + 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 + ) + + @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 + ) + + @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 + ) + + @Test def flatmap = + check( + """|val _ : List[Int] = List().flatMap(_ => @@) + |""".stripMargin, + """|IterableOnce[Nothing] + |""".stripMargin // ideally IterableOnce[Int], but can't change interpolateTypeVars + ) + + @Test def map = + check( + """|val _ : List[Int] = List().map(_ => @@) + |""".stripMargin, + """|Nothing + |""".stripMargin // ideally Int, but can't change interpolateTypeVars + ) + + @Test def `for-comprehension` = + check( + """|val _ : List[Int] = + | for { + | _ <- List("a", "b") + | } yield @@ + |""".stripMargin, + """|Nothing + |""".stripMargin // ideally Int, but can't change interpolateTypeVars + ) + +// bounds + @Test def any = + check( + """|trait Foo + |def foo[T](a: T): Boolean = ??? + |val _ = foo(@@) + |""".stripMargin, + """|Any + |""".stripMargin + ) + + @Test def `bounds-1` = + check( + """|trait Foo + |def foo[T <: Foo](a: T): Boolean = ??? + |val _ = foo(@@) + |""".stripMargin, + """|Foo + |""".stripMargin + ) + + @Test def `bounds-2` = + check( + """|trait Foo + |def foo[T >: Foo](a: T): Boolean = ??? + |val _ = foo(@@) + |""".stripMargin, + """|Foo + |""".stripMargin // ideally Any (maybe?) + ) + + @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 + |""".stripMargin // ideally A + ) 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 { 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] + +*/