Skip to content

Commit 819d127

Browse files
tanishikingmbovel
andauthored
Inlines: decide sealing against original target, while using widened type as cast destination (scala#25448)
Fixes scala#25417 fixes scala#25427 Regression introduced by tanishiking@816fc6c (scala#25111), which fixed scala#25091. Background: - scala#25091 showed that inline sealing could cast target contains method-reference that results in `notype` at erasure. - scala#25111 fixed this by using `target.widenIfUnstable` to remove method-reference from cast destination, and use it's return type instead. - However, deciding adaptation against the widened type is too weak and could skip a required cast, and leak projected opaque-proxy types (scala#25417). For example: ```scala import scala.compiletime.summonInline trait MyEvidence[T] object Scope: opaque type T = String inline given MyEvidence[T] with {} inline def summonInlineNoOpProxy: MyEvidence[Scope.T] = summonInline[MyEvidence[Scope.T]] val smoke = ( summonInline[MyEvidence[Scope.T]], summonInlineNoOpProxy, ) ``` For `summonInline[MyEvidence[Scope.T]]`: - `inlined.tpe`: `Scope.type{type T = String}#given_MyEvidence_T` - `target` (before widening): `(Scope.given_MyEvidence_T : => Scope.given_MyEvidence_T)` - `resultType = target.widenIfUnstable`: `Scope.given_MyEvidence_T` Previously - `inlined.tpe <:< resultType` hold, so adaptation is skipped. - Therefore, no cast is inserted, inlined tree type remains`Scope.type{type T = String}#given_MyEvidence_T` - This is seen as `MyEvidence[String]` - Later typing fails for required `MyEvidence[Scope.T]` vs `MyEvidence[String]` (path dependence infor is dropped). --- This commit fixes the problem by: decide whether adaptation is needed against original `target` (the real call-site contract), while using `target.widenIfUnstable` as a cast destination, so that scala#25091 fix) `target.widenIfUnstable` is an upper approximation of `target`. Conformance to widened type is weaker than conformance to original `target`. Therefore adaptation-need must be checked against `target`, while widened type is used only as cast destination. <!-- Fixes #XYZ (where XYZ is the issue number from the issue tracker) --> <!-- TODO description of the change --> <!-- Ideally should have a title like "Fix #XYZ: Short fix description" --> <!-- TODO first sign the CLA https://contribute.akka.io/cla/scala --> <!-- if the PR is still a WIP, create it as a draft PR (or convert it into one) --> ## How much have your relied on LLM-based tools in this contribution? <!-- State clearly in the pull request description, whether LLM-based tools were used and to what extent (extensively/moderately/minimally/not at all) --> <!-- Refer to our [LLM usage policy](https://github.com/scala/scala3/blob/main/LLM_POLICY.md) for rules and guidelines regarding usage of LLM-based tools in contributions. --> Worked with GPT5.3-Codex ## How was the solution tested? `testCompilation (i25091|i25417|i25427)` <!-- If automated tests are included, mention it. If they are not, explain why and how the solution was tested. --> ## Additional notes <!-- Placeholder for any extra context regarding this contribution. --> <!-- When in doubt, and for support regarding contributions to a particular component of the compiler, refer to [our contribution guide](https://github.com/scala/scala3/blob/main/CONTRIBUTING.md), and feel free to tag the maintainers listed there for the area(s) you are modifying. --> Co-authored-by: Matt Bovel <matthieu@bovel.net>
1 parent 5b0f56d commit 819d127

File tree

3 files changed

+53
-2
lines changed

3 files changed

+53
-2
lines changed

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -623,17 +623,29 @@ object Inlines:
623623
val withAdjustedThisTypes = if call.symbol.is(Macro) then fixThisTypeModuleClassReferences(unpacked) else unpacked
624624
(call.tpe & withAdjustedThisTypes, withAdjustedThisTypes != unpacked)
625625
else (call.tpe, false)
626+
// `target` might contain a method reference, which is an invalid cast target. Use its return type instead.
627+
// see https://github.com/scala/scala3/issues/25091
626628
val resultType = target.widenIfUnstable
627629
if forceCast then
628630
// we need to force the cast for issues with ThisTypes, as ensureConforms will just
629631
// check subtyping and then choose not to cast, leaving the previous, incorrect type
630632
inlined.cast(resultType)
631-
else
632-
inlined.ensureConforms(resultType)
633+
else if !(inlined.tpe <:< target) then
633634
// Make sure that the sealing with the declared type
634635
// is type correct. Without it we might get problems since the
635636
// expression's type is the opaque alias but the call's type is
636637
// the opaque type itself. An example is in pos/opaque-inline1.scala.
638+
//
639+
// Here we can't just use `inlined.ensureConforms(resultType)`:
640+
// `target.widenIfUnstable` is an upper approximation of `target`,
641+
// so a tree may conform to it while still not conforming to `target`.
642+
// This can happen when widening drops path-/prefix-sensitive information
643+
// (e.g. projected opaque-proxy types).
644+
// We check conformance against the original `target`, but cast to the
645+
// widened type to avoid NoType issues at erasure (see #25091, #25417).
646+
inlined.cast(resultType)
647+
else
648+
inlined
637649
end expand
638650
end InlineCall
639651
end Inlines

tests/pos/i25417.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import scala.compiletime.summonFrom
2+
3+
trait Decoder[A]
4+
5+
given Decoder[String] with {}
6+
7+
trait Mirror[T]:
8+
type IronType
9+
10+
given [T](using mirror: Mirror[T], ev: Decoder[mirror.IronType]): Decoder[T] =
11+
ev.asInstanceOf[Decoder[T]]
12+
13+
object Foo:
14+
opaque type T = String
15+
16+
inline given Mirror[T] with
17+
type IronType = String
18+
19+
inline def summonFooDecoder: Decoder[Foo.T] =
20+
summonFrom {
21+
case decodeA: Decoder[Foo.T] => decodeA
22+
}
23+
24+
val fooDecoder: Decoder[Foo.T] = summonFooDecoder

tests/pos/i25427.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import scala.compiletime.summonInline
2+
3+
trait MyEvidence[T]
4+
5+
object Scope:
6+
opaque type T = String
7+
8+
inline given MyEvidence[T] with {}
9+
10+
inline def summonInlineNoOpProxy: MyEvidence[Scope.T] = summonInline[MyEvidence[Scope.T]]
11+
12+
val smoke = (
13+
summonInline[MyEvidence[Scope.T]],
14+
summonInlineNoOpProxy,
15+
)

0 commit comments

Comments
 (0)