Skip to content

Commit c148144

Browse files
committed
fix(scala#18681): un-reduced summonFrom fallback
This commit fixes the reporting issue where un-reduced summonFrom does not fallback to implicitNotFound. Before this commit, `reduceInlineMatch` discarded the information from implicit search in InlineReducer. This commit adds some changes so that the error message from implicit search failure can propagate to Inliner#typedMatchFinish to emit better error message.
1 parent eb6db96 commit c148144

File tree

4 files changed

+142
-92
lines changed

4 files changed

+142
-92
lines changed

compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala

Lines changed: 106 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Names.TermName
1010
import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName}
1111
import config.Printers.inlining
1212
import util.SimpleIdentityMap
13-
13+
import dotty.tools.dotc.reporting.Message
1414
import collection.mutable
1515

1616
/** A utility class offering methods for rewriting inlined code */
@@ -146,17 +146,16 @@ class InlineReducer(inliner: Inliner)(using Context):
146146
}
147147
binding1.withSpan(call.span)
148148
}
149-
// (I guess) these `Option`s should be Either[ErrorMessage, T] to show msg from `@implicitNotFound`.
150149
/** The result type of reducing a match. It consists optionally of a list of bindings
151150
* for the pattern-bound variables and the RHS of the selected case.
152-
* Returns `None` if no case was selected.
151+
* Returns `Left` with an optional message for implicitNotFound if no case was selected.
153152
*/
154-
type MatchRedux = Option[(List[MemberDef], Tree)]
153+
type MatchRedux = Either[Option[Message],(List[MemberDef], Tree)]
155154

156155
/** Same as MatchRedux, but also includes a boolean
157156
* that is true if the guard can be checked at compile time.
158157
*/
159-
type MatchReduxWithGuard = Option[(List[MemberDef], Tree, Boolean)]
158+
type MatchReduxWithGuard = Either[Option[Message],(List[MemberDef], Tree, Boolean)]
160159

161160
/** Reduce an inline match
162161
* @param mtch the match tree
@@ -173,13 +172,14 @@ class InlineReducer(inliner: Inliner)(using Context):
173172
val isImplicit = scrutinee.isEmpty
174173

175174
/** Try to match pattern `pat` against scrutinee reference `scrut`. If successful add
176-
* bindings for variables bound in this pattern to `caseBindingMap`.
175+
* bindings for variables bound in this pattern to `caseBindingMap` and return None.
176+
* Otherwise, it returns optional error message.
177177
*/
178178
def reducePattern(
179179
caseBindingMap: mutable.ListBuffer[(Symbol, MemberDef)],
180180
scrut: TermRef,
181181
pat: Tree
182-
)(using Context): Boolean = {
182+
)(using Context): Option[Option[Message]] = {
183183

184184
/** Create a binding of a pattern bound variable with matching part of
185185
* scrutinee as RHS and type that corresponds to RHS.
@@ -193,26 +193,28 @@ class InlineReducer(inliner: Inliner)(using Context):
193193
val copied = sym.copy(info = TypeAlias(alias), coord = sym.coord).asType
194194
caseBindingMap += ((sym, TypeDef(copied)))
195195
}
196-
197-
def searchImplicit(sym: TermSymbol, tpt: Tree) = {
196+
197+
/**
198+
* @return
199+
* None if implicit search finds an instance or fails with a hard error.
200+
* Otherwise, return the error message for missing implicit instance.
201+
*/
202+
def searchImplicit(sym: TermSymbol, tpt: Tree): Option[Message] = {
198203
val evTyper = new Typer(ctx.nestingLevel + 1)
199204
val evCtx = ctx.fresh.setTyper(evTyper)
200205
inContext(evCtx) {
201206
val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span)
202207
evidence.tpe match {
203208
case fail: Implicits.AmbiguousImplicits =>
204209
report.error(evTyper.missingArgMsg(evidence, tpt.tpe, ""), tpt.srcPos)
205-
true // hard error: return true to stop implicit search here
210+
None // hard error: return None to stop implicit search here
206211
case fail: Implicits.SearchFailureType =>
207-
// Here, we get a message from `@implicitNotFound(...)` like "there is no Inst!"
208-
// let's give this value back to caller
209-
val msgFromAnnotation = evTyper.missingArgMsg(evidence, tpt.tpe, "")
210-
println(msgFromAnnotation)
211-
false
212+
val noImplicitMsg = evTyper.missingArgMsg(evidence, tpt.tpe, "")
213+
Some(noImplicitMsg)
212214
case _ =>
213215
//inlining.println(i"inferred implicit $sym: ${sym.info} with $evidence: ${evidence.tpe.widen}, ${evCtx.gadt.constraint}, ${evCtx.typerState.constraint}")
214216
newTermBinding(sym, evidence)
215-
true
217+
None
216218
}
217219
}
218220
}
@@ -284,48 +286,62 @@ class InlineReducer(inliner: Inliner)(using Context):
284286
case Typed(pat1, tpt) =>
285287
val typeBinds = getTypeBindsMap(pat1, tpt)
286288
registerAsGadtSyms(typeBinds)
287-
scrut <:< tpt.tpe && {
289+
if (scrut <:< tpt.tpe) then
288290
addTypeBindings(typeBinds)
289291
reducePattern(caseBindingMap, scrut, pat1)
290-
}
292+
else
293+
Some(None)
291294
case pat @ Bind(name: TermName, Typed(_, tpt)) if isImplicit =>
292295
val typeBinds = getTypeBindsMap(tpt, tpt)
293296
registerAsGadtSyms(typeBinds)
294-
searchImplicit(pat.symbol.asTerm, tpt) && {
295-
addTypeBindings(typeBinds)
296-
true
297-
}
297+
searchImplicit(pat.symbol.asTerm, tpt) match
298+
// found or hard error
299+
case None =>
300+
addTypeBindings(typeBinds)
301+
None
302+
case Some(msg) =>
303+
Some(Some(msg))
304+
298305
case pat @ Bind(name: TermName, body) =>
299-
reducePattern(caseBindingMap, scrut, body) && {
300-
if (name != nme.WILDCARD) newTermBinding(pat.symbol.asTerm, ref(scrut))
301-
true
302-
}
306+
reducePattern(caseBindingMap,scrut,body) match
307+
case None if name != nme.WILDCARD =>
308+
newTermBinding(pat.symbol.asTerm, ref(scrut))
309+
None
310+
case None =>
311+
None
312+
case Some(msg) =>
313+
Some(msg)
314+
303315
case Ident(nme.WILDCARD) =>
304-
true
316+
None
305317
case pat: Literal =>
306-
scrut.widenTermRefExpr =:= pat.tpe
318+
if scrut.widenTermRefExpr =:= pat.tpe then
319+
None
320+
else Some(None)
307321
case pat: RefTree =>
308-
scrut =:= pat.tpe ||
309-
scrut.classSymbol.is(Module) && scrut.widen =:= pat.tpe.widen && {
310-
scrut.prefix match {
311-
case _: SingletonType | NoPrefix => true
312-
case _ => false
313-
}
314-
}
322+
if(scrut =:= pat.tpe) then
323+
None
324+
else if (scrut.classSymbol.is(Module) && scrut.widen =:= pat.tpe.widen) then
325+
scrut.prefix match
326+
case _: SingletonType | NoPrefix => None
327+
case _ => Some(None)
328+
else Some(None)
315329
case UnApply(unapp, _, pats) =>
316330
unapp.tpe.widen match {
317331
case mt: MethodType if mt.paramInfos.length == 1 =>
318332

319-
def reduceSubPatterns(pats: List[Tree], selectors: List[Tree]): Boolean = (pats, selectors) match {
320-
case (Nil, Nil) => true
333+
def reduceSubPatterns(pats: List[Tree], selectors: List[Tree]): Option[Option[Message]] = (pats, selectors) match {
334+
case (Nil, Nil) => None
321335
case (pat :: pats1, selector :: selectors1) =>
322336
val elem = newSym(InlineBinderName.fresh(), Synthetic, selector.tpe.widenInlineScrutinee).asTerm
323337
val rhs = constToLiteral(selector)
324338
elem.defTree = rhs
325339
caseBindingMap += ((NoSymbol, ValDef(elem, rhs).withSpan(elem.span)))
326-
reducePattern(caseBindingMap, elem.termRef, pat) &&
327-
reduceSubPatterns(pats1, selectors1)
328-
case _ => false
340+
reducePattern(caseBindingMap, elem.termRef, pat) match
341+
case None => reduceSubPatterns(pats1, selectors1)
342+
case Some(msg) => Some(msg)
343+
344+
case _ => Some(None)
329345
}
330346

331347
val paramType = mt.paramInfos.head
@@ -337,17 +353,42 @@ class InlineReducer(inliner: Inliner)(using Context):
337353
val selectors =
338354
for (accessor <- caseAccessors)
339355
yield constToLiteral(reduceProjection(ref(scrut).select(accessor).ensureApplied))
340-
caseAccessors.length == pats.length && reduceSubPatterns(pats, selectors)
356+
if caseAccessors.length == pats.length then reduceSubPatterns(pats, selectors)
357+
else Some(None)
341358
}
342-
else false
359+
else Some(None)
343360
case _ =>
344-
false
361+
Some(None)
345362
}
346363
case Alternative(pats) =>
347-
pats.exists(reducePattern(caseBindingMap, scrut, _))
364+
/**
365+
* Apply `reducePattern` on `caseBindingMap` until it finds a match.
366+
*
367+
* @param ps a list of patterns to match
368+
* @param theLastMsg the last message for missing implicit instance
369+
*
370+
* @return `None` if it finds a match. Otherwise, it returns optional error message.
371+
*/
372+
def applyReduceUntilFind(
373+
ps: List[Tree],
374+
theLastMsg: Option[Message] = None
375+
): Option[Option[Message]] =
376+
ps match
377+
case Nil => Some(theLastMsg)
378+
case p :: ps =>
379+
reducePattern(caseBindingMap, scrut, p) match
380+
// it finds a match or fails with hard error
381+
case None => None
382+
// implicit search fails with message
383+
case Some(msg @ Some(_)) =>
384+
applyReduceUntilFind(ps, msg)
385+
case Some(None) =>
386+
applyReduceUntilFind(ps, theLastMsg)
387+
end applyReduceUntilFind
388+
applyReduceUntilFind(pats)
348389
case tree: Inlined if tree.inlinedFromOuterScope =>
349390
reducePattern(caseBindingMap, scrut, tree.expansion)
350-
case _ => false
391+
case _ => Some(None)
351392
}
352393
}
353394

@@ -372,34 +413,30 @@ class InlineReducer(inliner: Inliner)(using Context):
372413

373414
if (!isImplicit) caseBindingMap += ((NoSymbol, scrutineeBinding))
374415
val gadtCtx = ctx.fresh.setFreshGADTBounds.addMode(Mode.GadtConstraintInference)
375-
// `true` from reducePattern implies there be some instance(s) for `reduceFrom`(?)
376-
if (reducePattern(caseBindingMap, scrutineeSym.termRef, cdef.pat)(using gadtCtx)) {
377-
val (caseBindings, from, to) = substBindings(caseBindingMap.toList, mutable.ListBuffer(), Nil, Nil)
378-
val (guardOK, canReduceGuard) =
379-
if cdef.guard.isEmpty then (true, true)
380-
else typer.typed(cdef.guard.subst(from, to), defn.BooleanType) match {
381-
case ConstantValue(v: Boolean) => (v, true)
382-
case _ => (false, false)
383-
}
384-
if guardOK then Some((caseBindings.map(_.subst(from, to)), cdef.body.subst(from, to), canReduceGuard))
385-
else if canReduceGuard then None
386-
else Some((caseBindings.map(_.subst(from, to)), cdef.body.subst(from, to), canReduceGuard))
387-
}
388-
else None
416+
reducePattern(caseBindingMap,scrutineeSym.termRef,cdef.pat)(using gadtCtx) match
417+
case Some(msg) => Left(msg)
418+
case None => {
419+
val (caseBindings, from, to) = substBindings(caseBindingMap.toList, mutable.ListBuffer(), Nil, Nil)
420+
val (guardOK, canReduceGuard) =
421+
if cdef.guard.isEmpty then (true, true)
422+
else typer.typed(cdef.guard.subst(from, to), defn.BooleanType) match {
423+
case ConstantValue(v: Boolean) => (v, true)
424+
case _ => (false, false)
425+
}
426+
if guardOK then Right((caseBindings.map(_.subst(from, to)), cdef.body.subst(from, to), canReduceGuard))
427+
else if canReduceGuard then Left(None)
428+
else Right((caseBindings.map(_.subst(from, to)), cdef.body.subst(from, to), canReduceGuard))
429+
}
389430
}
390431

391-
def recur(cases: List[CaseDef]): MatchRedux = cases match {
392-
case Nil => None
432+
def recur(cases: List[CaseDef], theLastMsg: Option[Message] = None): MatchRedux = cases match
433+
case Nil => Left(theLastMsg)
393434
case cdef :: cases1 =>
394435
reduceCase(cdef) match
395-
// may fail with 1. ambiguous implicits, 2. missing implicits
396-
// Either[Either[MissingErrorMessage,AmbiguousErrorMessage],T]?
397-
// a preceding case may fail, but one of the remainders cases may succeed.
398-
// So return Left[ErrorMessage] when the last reduce fails?
399-
case None => recur(cases1)
400-
case r @ Some((caseBindings, rhs, canReduceGuard)) if canReduceGuard => Some((caseBindings, rhs))
401-
case _ => None
402-
}
436+
case Left(None) => recur(cases1, theLastMsg)
437+
case Left(msg @Some(_)) => recur(cases1, msg)
438+
case r @ Right((caseBindings, rhs, canReduceGuard)) if canReduceGuard => Right((caseBindings, rhs))
439+
case _ => Left(theLastMsg)
403440

404441
recur(cases)
405442
}

compiler/src/dotty/tools/dotc/inlines/Inliner.scala

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dotty.tools
22
package dotc
33
package inlines
44

5+
import dotty.tools.dotc.reporting.NoExplanation
56
import ast.*, core.*
67
import Flags.*, Symbols.*, Types.*, Decorators.*, Constants.*, Contexts.*
78
import StdNames.nme
@@ -861,7 +862,7 @@ class Inliner(val call: tpd.Tree)(using Context):
861862
}
862863
val selType = if (sel.isEmpty) wideSelType else selTyped(sel)
863864
reduceInlineMatch(sel, selType, cases.asInstanceOf[List[CaseDef]], this) match {
864-
case Some((caseBindings, rhs0)) =>
865+
case Right((caseBindings, rhs0)) =>
865866
// drop type ascriptions/casts hiding pattern-bound types (which are now aliases after reducing the match)
866867
// note that any actually necessary casts will be reinserted by the typing pass below
867868
val rhs1 = rhs0 match {
@@ -887,20 +888,20 @@ class Inliner(val call: tpd.Tree)(using Context):
887888
|--- to:
888889
|$rhs""")
889890
typedExpr(rhs, pt)
890-
case None =>
891-
// what if reduceInlineMatch returns Either[ErrorMessage,(caseBindings, rhs0)]?
891+
case Left(maybeMeg) =>
892892

893893
def guardStr(guard: untpd.Tree) = if (guard.isEmpty) "" else i" if $guard"
894894
def patStr(cdef: untpd.CaseDef) = i"case ${cdef.pat}${guardStr(cdef.guard)}"
895-
println(s"\n\n\nDEBUG!! ${tree.cases}\n\n\n")
896-
val msg =
897-
if (tree.selector.isEmpty)
898-
em"""cannot reduce summonFrom with
899-
| patterns : ${tree.cases.map(patStr).mkString("\n ")}"""
900-
else
901-
em"""cannot reduce inline match with
902-
| scrutinee: $sel : ${selType}
895+
val msg = maybeMeg match
896+
case Some(noImplicitMsg) => em"${noImplicitMsg.message}"
897+
case None =>
898+
if (tree.selector.isEmpty)
899+
em"""cannot reduce summonFrom with
903900
| patterns : ${tree.cases.map(patStr).mkString("\n ")}"""
901+
else
902+
em"""cannot reduce inline match with
903+
| scrutinee: $sel : ${selType}
904+
| patterns : ${tree.cases.map(patStr).mkString("\n ")}"""
904905
errorTree(tree, msg)
905906
}
906907
}
Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
-- Error: tests/neg/summonFrom-implicit-not-found.scala:8:8 ------------------------------------------------------------
2-
8 |val x = lookup // error
3-
| ^^^^^^
4-
| there is no Inst!
2+
8 |val x = summonMissing // error
3+
| ^^^^^^^^^^^^^
4+
| there is no Missing!
55
|---------------------------------------------------------------------------------------------------------------------
66
|Inline stack trace
77
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
88
|This location contains code that was inlined from summonFrom-implicit-not-found.scala:4
9-
4 |inline def lookup = compiletime.summonFrom {
10-
| ^
11-
5 | case m: Inst => m
9+
4 |inline def summonMissing = compiletime.summonFrom {
10+
| ^
11+
5 | case m: Missing => m
1212
6 |}
1313
---------------------------------------------------------------------------------------------------------------------
14+
-- [E172] Type Error: tests/neg/summonFrom-implicit-not-found.scala:9:8 ------------------------------------------------
15+
9 |val y = summonMissing2 // error
16+
| ^^^^^^^^^^^^^^
17+
| there is no Missing!
18+
|---------------------------------------------------------------------------------------------------------------------
19+
|Inline stack trace
20+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
21+
|This location contains code that was inlined from summonFrom-implicit-not-found.scala:7
22+
7 |inline def summonMissing2 = compiletime.summonInline[Missing]
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
24+
---------------------------------------------------------------------------------------------------------------------
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
@annotation.implicitNotFound("there is no Inst!")
2-
trait Inst
1+
@annotation.implicitNotFound("there is no Missing!")
2+
trait Missing
33

4-
transparent inline def lookup[T] = compiletime.summonFrom[T] {
5-
case m: T => m
4+
inline def summonMissing = compiletime.summonFrom {
5+
case m: Missing => m
66
}
7-
// given Inst = {}
8-
val x = lookup[Inst] // error
7+
inline def summonMissing2 = compiletime.summonInline[Missing]
8+
val x = summonMissing // error
9+
val y = summonMissing2 // error

0 commit comments

Comments
 (0)