From 202b1963faaf5960a64346f7f500989bfa1e9b99 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Sat, 15 Nov 2025 09:15:19 +0000 Subject: [PATCH 1/6] Ignore ascriptions from `ensureNoLocalRefs` in `ReTyper` --- compiler/src/dotty/tools/dotc/typer/ReTyper.scala | 3 +++ compiler/src/dotty/tools/dotc/typer/Typer.scala | 5 ++++- tests/run/i24420-transparent-inline-local-ref.scala | 12 ++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/run/i24420-transparent-inline-local-ref.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 8400639706d6..20b0fd93737b 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -58,6 +58,9 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = { assertTyped(tree) + if tree.hasAttachment(Typer.NoLocalRefsAscription) then + return typedExpr(tree.expr, pt) + val tpt1 = checkSimpleKinded(typedType(tree.tpt)) val expr1 = tree.expr match { case id: untpd.Ident if (ctx.mode is Mode.Pattern) && untpd.isVarPattern(id) && (id.name == nme.WILDCARD || id.name == nme.WILDCARD_STAR) => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 164ff411e73b..29ad072b8e8f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -86,6 +86,9 @@ object Typer { /** Indicates that an expression is explicitly ascribed to [[Unit]] type. */ val AscribedToUnit = new Property.StickyKey[Unit] + /** Indicates that this ascription was inserted by `ensureNoLocalRefs`. */ + val NoLocalRefsAscription = new Property.StickyKey[Unit] + /** Tree adaptation lost fidelity; this attachment preserves the original tree. */ val AdaptedTree = new Property.StickyKey[tpd.Tree] @@ -1580,7 +1583,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => val target = pt.simplified val targetTpt = TypeTree(target, inferred = true) - if tree.tpe <:< target then Typed(tree, targetTpt) + if tree.tpe <:< target then Typed(tree, targetTpt).withAttachment(NoLocalRefsAscription, ()) else // This case should not normally arise. It currently does arise in test cases // pos/t4080b.scala and pos/i7067.scala. In that case, a type ascription is wrong diff --git a/tests/run/i24420-transparent-inline-local-ref.scala b/tests/run/i24420-transparent-inline-local-ref.scala new file mode 100644 index 000000000000..7d8180680130 --- /dev/null +++ b/tests/run/i24420-transparent-inline-local-ref.scala @@ -0,0 +1,12 @@ +transparent inline def f(): Long = + 1L + +transparent inline def g(): Long = + inline val x = f() + x + +transparent inline def h(): Long = + inline if g() > 0L then 1L else 0L + +@main def Test: Unit = + assert(h() == 1L) From 504cb789e23a8b24209f120810f802c93496a189 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Sat, 15 Nov 2025 14:17:43 +0000 Subject: [PATCH 2/6] Ignore ascriptions from `PrepareInlinable.wrapRHS` in `InlineTyper` --- .../dotty/tools/dotc/inlines/Inliner.scala | 16 ++++++++++-- .../dotc/inlines/PrepareInlineable.scala | 10 +++++++- tests/pos/i24412.scala | 13 ++++++++++ tests/run/i24420-inline-local-ref.scala | 12 +++++++++ tests/run/i24420-inline-val.scala | 25 +++++++++++++++++++ 5 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 tests/pos/i24412.scala create mode 100644 tests/run/i24420-inline-local-ref.scala create mode 100644 tests/run/i24420-inline-val.scala diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 356e5ad40fdd..0d69af2cfd00 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -1056,8 +1056,20 @@ class Inliner(val call: tpd.Tree)(using Context): val meth = tree.symbol if meth.isAllOf(DeferredInline) then errorTree(tree, em"Deferred inline ${meth.showLocated} cannot be invoked") - else if Inlines.needsInlining(tree) then Inlines.inlineCall(simplify(tree, pt, locked)) - else tree + else if Inlines.needsInlining(tree) then + StripInlineResultAscriptionMap().transform(Inlines.inlineCall(simplify(tree, pt, locked))) + else + tree + + private class StripInlineResultAscriptionMap extends tpd.TreeMap: + override def transform(tree: Tree)(using Context): Tree = + tree match + case Typed(expr, _) if tree.hasAttachment(PrepareInlineable.InlineResultAscription) => + expr + case tree: Inlined => + super.transform(tree) + case _ => + tree override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = super.typedUnadapted(tree, pt, locked) match diff --git a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala index 47a47f10f905..bd4b6a3530c0 100644 --- a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala @@ -30,6 +30,11 @@ object PrepareInlineable { private val InlineAccessorsKey = new Property.Key[InlineAccessors] + /** Indicates that an ascription was inserted by [[PrepareInlinable.wrapRHS]]. + * It is used to remove it [[Inliner.stripInlineResultAscription]]. + */ + val InlineResultAscription = new Property.StickyKey[Unit] + def initContext(ctx: Context): Context = ctx.fresh.setProperty(InlineAccessorsKey, new InlineAccessors) @@ -246,7 +251,10 @@ object PrepareInlineable { /** The type ascription `rhs: tpt`, unless `original` is `transparent`. */ def wrapRHS(original: untpd.DefDef, tpt: Tree, rhs: Tree)(using Context): Tree = - if original.mods.is(Transparent) then rhs else Typed(rhs, tpt) + if original.mods.is(Transparent) then + rhs + else + Typed(rhs, tpt).withAttachment(InlineResultAscription, ()) /** Return result of evaluating `op`, but drop `Inline` flag and `Body` annotation * of `sym` in case that leads to errors. diff --git a/tests/pos/i24412.scala b/tests/pos/i24412.scala new file mode 100644 index 000000000000..3b9ec8d5a579 --- /dev/null +++ b/tests/pos/i24412.scala @@ -0,0 +1,13 @@ +object test { + import scala.compiletime.erasedValue + + inline def contains[T <: Tuple, E]: Boolean = inline erasedValue[T] match { + case _: EmptyTuple => false + case _: (_ *: tail) => contains[tail, E] + } + inline def check[T <: Tuple]: Unit = { + inline if contains[T, Long] && false then ??? + } + + check[(String, Double)] +} diff --git a/tests/run/i24420-inline-local-ref.scala b/tests/run/i24420-inline-local-ref.scala new file mode 100644 index 000000000000..84828de9f443 --- /dev/null +++ b/tests/run/i24420-inline-local-ref.scala @@ -0,0 +1,12 @@ +inline def f(): Long = + 1L + +inline def g(): Long = + inline val x = f() + x + +inline def h(): Long = + inline if g() > 0L then 1L else 0L + +@main def Test: Unit = + assert(h() == 1L) diff --git a/tests/run/i24420-inline-val.scala b/tests/run/i24420-inline-val.scala new file mode 100644 index 000000000000..ec201d948a96 --- /dev/null +++ b/tests/run/i24420-inline-val.scala @@ -0,0 +1,25 @@ +inline def f1(): Long = + 1L + +inline def f2(): Long = + inline val x = f1() + 1L + x + +inline def f3(): Long = + inline val x = f1() + x + +inline def g1(): Boolean = + true + +inline def g2(): Long = + inline if g1() then 1L else 2L + +inline def g3(): Long = + inline if f1() > 0L then 1L else 2L + +@main def Test: Unit = + assert(f2() == 2L) + assert(f3() == 1L) + assert(g2() == 1L) + assert(g3() == 1L) From ae33ea6c5675de8a298baa188c458d9a518ba753 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Sat, 15 Nov 2025 14:20:07 +0000 Subject: [PATCH 3/6] Move stripping of ascriptions from `PrepareInlineable.wrapRHS` to `InlineTyper.typedTyped` --- .../dotty/tools/dotc/inlines/Inliner.scala | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 0d69af2cfd00..b93ef514fe4c 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -1047,6 +1047,19 @@ class Inliner(val call: tpd.Tree)(using Context): reduceInlineMatchExpr(sel) } + private def shouldStripAscription(tree: Typed)(using Context): Boolean = + val exprTp = tree.expr.tpe + tree.hasAttachment(PrepareInlineable.InlineResultAscription) + && exprTp.exists + && !exprTp.widen.isRef(defn.NothingClass) + && !exprTp.widen.isRef(defn.NullClass) + && (exprTp frozen_<:< tree.tpe) + + override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = + super.typedTyped(tree, pt) match + case typedTree: Typed if shouldStripAscription(typedTree) => typedTree.expr + case typedTree => typedTree + override def newLikeThis(nestingLevel: Int): Typer = new InlineTyper(initialErrorCount, nestingLevel) /** True if this inline typer has already issued errors */ @@ -1056,20 +1069,8 @@ class Inliner(val call: tpd.Tree)(using Context): val meth = tree.symbol if meth.isAllOf(DeferredInline) then errorTree(tree, em"Deferred inline ${meth.showLocated} cannot be invoked") - else if Inlines.needsInlining(tree) then - StripInlineResultAscriptionMap().transform(Inlines.inlineCall(simplify(tree, pt, locked))) - else - tree - - private class StripInlineResultAscriptionMap extends tpd.TreeMap: - override def transform(tree: Tree)(using Context): Tree = - tree match - case Typed(expr, _) if tree.hasAttachment(PrepareInlineable.InlineResultAscription) => - expr - case tree: Inlined => - super.transform(tree) - case _ => - tree + else if Inlines.needsInlining(tree) then Inlines.inlineCall(simplify(tree, pt, locked)) + else tree override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = super.typedUnadapted(tree, pt, locked) match From 6ae39efefe5d92610ae5999919a55dd045d97606 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Sat, 15 Nov 2025 14:53:43 +0000 Subject: [PATCH 4/6] Do not lift bindings for blocks --- compiler/src/dotty/tools/dotc/inlines/Inlines.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 2dd86132fb97..a21912439051 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -134,9 +134,9 @@ object Inlines: * inline call expansions smaller. */ def liftBindings(tree: Tree, liftPos: Tree => Tree): Tree = tree match { - case Block(stats, expr) => - bindings ++= stats.map(liftPos) - liftBindings(expr, liftPos) + //case Block(stats, expr) => + // bindings ++= stats.map(liftPos) + // liftBindings(expr, liftPos) case tree @ Inlined(call, stats, expr) => bindings ++= stats.map(liftPos) val lifter = liftFromInlined(call) From 3d68bbaed36f00265f5c9eb8368b6276b133b8b5 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Sat, 15 Nov 2025 15:52:28 +0000 Subject: [PATCH 5/6] Only strip ascriptions in nested inline calls --- compiler/src/dotty/tools/dotc/inlines/Inliner.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index b93ef514fe4c..d4781f760015 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -1050,6 +1050,7 @@ class Inliner(val call: tpd.Tree)(using Context): private def shouldStripAscription(tree: Typed)(using Context): Boolean = val exprTp = tree.expr.tpe tree.hasAttachment(PrepareInlineable.InlineResultAscription) + && enclosingInlineds.size > 1 && exprTp.exists && !exprTp.widen.isRef(defn.NothingClass) && !exprTp.widen.isRef(defn.NullClass) From 8b803e193c895db65057af5bffffe999700bed7c Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Sun, 16 Nov 2025 11:34:06 +0000 Subject: [PATCH 6/6] Recurse through blocks in `inlineCall` --- compiler/src/dotty/tools/dotc/inlines/Inlines.scala | 6 ++++++ tests/neg/i18123b.check | 9 +++++++++ tests/neg/i18123b.scala | 8 ++++++++ 3 files changed, 23 insertions(+) create mode 100644 tests/neg/i18123b.check create mode 100644 tests/neg/i18123b.scala diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index a21912439051..a84fd1a67797 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -113,6 +113,12 @@ object Inlines: if tree.symbol.isConstructor then return tree // error already reported for the inline constructor definition + tree match + case Block(bindings, expr) => + return cpy.Block(tree)(bindings, inlineCall(expr)) + case _ => + () + /** Set the position of all trees logically contained in the expansion of * inlined call `call` to the position of `call`. This transform is necessary * when lifting bindings from the expansion to the outside of the call. diff --git a/tests/neg/i18123b.check b/tests/neg/i18123b.check new file mode 100644 index 000000000000..eb0fc99ea362 --- /dev/null +++ b/tests/neg/i18123b.check @@ -0,0 +1,9 @@ +-- [E007] Type Mismatch Error: tests/neg/i18123b.scala:8:8 ------------------------------------------------------------- +8 |def z = y.rep().toUpperCase // error + | ^^^^^^^ + | Found: (??? : => Nothing) + | Required: ?{ toUpperCase: ? } + | Note that implicit conversions were not tried because the result of an implicit conversion + | must be more specific than ?{ toUpperCase: } + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i18123b.scala b/tests/neg/i18123b.scala new file mode 100644 index 000000000000..03c31b8166f2 --- /dev/null +++ b/tests/neg/i18123b.scala @@ -0,0 +1,8 @@ +// Minimized version of `tests/pos/i18123.scala` to test #24425. + +extension (x: String) + transparent inline def rep(min: Int = 0): String = ??? + +def y: String = ??? + +def z = y.rep().toUpperCase // error