From d7fcea36cfcb0bf3943fb9a4db9c975962fc3bfd Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Tue, 18 Nov 2025 14:09:01 +0000 Subject: [PATCH 1/3] Refine the type of inline vals with literal rhs --- .../dotty/tools/dotc/inlines/Inlines.scala | 28 +++++++++++++++++++ .../src/dotty/tools/dotc/typer/Namer.scala | 5 +++- .../src/dotty/tools/dotc/typer/Typer.scala | 9 +++++- tests/neg/i8841.check | 4 --- tests/neg/i8841.scala | 2 +- tests/neg/inline-val.scala | 4 +-- tests/pos/i24321.scala | 4 +++ tests/printing/i24321.check | 15 ++++++++++ tests/printing/i24321.scala | 7 +++++ 9 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 tests/pos/i24321.scala create mode 100644 tests/printing/i24321.check create mode 100644 tests/printing/i24321.scala diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 2dd86132fb97..405a55c9a7ab 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -619,4 +619,32 @@ object Inlines: // the opaque type itself. An example is in pos/opaque-inline1.scala. end expand end InlineCall + + /** An inline val is refinable if it has an explicit type that is not a + * singleton type. + */ + def mightRefineInlineVal(mdef: untpd.ValOrDefDef, sym: Symbol)(using Context): Boolean = + sym.isInlineVal && !sym.is(Final) + && !mdef.tpt.isEmpty && !mdef.tpt.isInstanceOf[untpd.SingletonTypeTree] + + /** Refine the type of an inline val to a constant type if its right hand + * side is a literal. See tests/pos/i24321.scala and tests/printing/i24321.scala. + */ + def refineInlineVal(mdef: untpd.ValOrDefDef, sym: Symbol, tp: Type)(using Context): Type = + if !Inlines.mightRefineInlineVal(mdef, sym) then + return tp + + // Only refine if the explicit type is a primitive value types and String. + // We don't include opaque types. See tests/neg/i13851b.scala. + val tpSym = tp.dealiasKeepOpaques.typeSymbol + if !tpSym.isPrimitiveValueClass && tpSym != defn.StringClass then + return tp + + untpd.stripBlock(mdef.rhs) match + case rhs: (untpd.Literal | untpd.Number) => + ctx.typer.typedAheadExpr(rhs, tp) match + case tpd.ConstantValue(c) => ConstantType(Constant(c)) + case _ => tp + case _ => tp + end Inlines diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 1207914fbcd1..6bceb57e1d65 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1927,7 +1927,10 @@ class Namer { typer: Typer => sym.setFlag(Deferred | HasDefault) case _ => - val mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe) + val mbrTpe0 = checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe + val mbrTpe1 = Inlines.refineInlineVal(mdef, sym, mbrTpe0) + val mbrTpe = paramFn(mbrTpe1) + // Add an erased to the using clause generated from a `: Singleton` context bound mdef.tpt match case tpt: untpd.ContextBoundTypeTree if mbrTpe.typeSymbol == defn.SingletonClass => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 164ff411e73b..9f5576aba317 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3038,7 +3038,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer sym.resetFlag(Lazy) checkErasedOK(sym) sym.setFlag(Erased) - val tpt1 = checkSimpleKinded(typedType(tpt)) + + val isRefinedInlineVal = + Inlines.mightRefineInlineVal(vdef, sym) + && sym.info.isInstanceOf[ConstantType] + + val tpt0 = checkSimpleKinded(typedType(tpt)) + val tpt1 = if isRefinedInlineVal then TypeTree(sym.info) else tpt0 + val rhs1 = vdef.rhs match case rhs @ Ident(nme.WILDCARD) => rhs.withType(tpt1.tpe) diff --git a/tests/neg/i8841.check b/tests/neg/i8841.check index d5b79c6bc88b..316a3efd4517 100644 --- a/tests/neg/i8841.check +++ b/tests/neg/i8841.check @@ -1,7 +1,3 @@ --- Error: tests/neg/i8841.scala:2:20 ----------------------------------------------------------------------------------- -2 | inline val log1 : Boolean = false // error - | ^^^^^^^ - | inline value must have a literal constant type -- Error: tests/neg/i8841.scala:3:20 ----------------------------------------------------------------------------------- 3 | inline val log2 = true: Boolean // error | ^^^^^^^^^^^^^ diff --git a/tests/neg/i8841.scala b/tests/neg/i8841.scala index ebe39bb7991e..28de743c85c8 100644 --- a/tests/neg/i8841.scala +++ b/tests/neg/i8841.scala @@ -1,5 +1,5 @@ object Foo { - inline val log1 : Boolean = false // error + inline val log1 : Boolean = false // ok inline val log2 = true: Boolean // error inline val log3: false = { println(); false } // error } diff --git a/tests/neg/inline-val.scala b/tests/neg/inline-val.scala index e6eeee9c6299..1da15eda3e22 100644 --- a/tests/neg/inline-val.scala +++ b/tests/neg/inline-val.scala @@ -1,4 +1,4 @@ inline val a = 1 : Int // error -inline val b: Int = 1 // error -inline val c = b // error +inline val b: Int = 1 // ok +inline val c = b // ok diff --git a/tests/pos/i24321.scala b/tests/pos/i24321.scala new file mode 100644 index 000000000000..f54e60755adc --- /dev/null +++ b/tests/pos/i24321.scala @@ -0,0 +1,4 @@ +object Bar: + inline val MAX: Byte = 10 + +val y = Bar.MAX to Bar.MAX diff --git a/tests/printing/i24321.check b/tests/printing/i24321.check new file mode 100644 index 000000000000..b5259581599f --- /dev/null +++ b/tests/printing/i24321.check @@ -0,0 +1,15 @@ +[[syntax trees at end of typer]] // tests/printing/i24321.scala +package { + final lazy module val i24321$package: i24321$package = new i24321$package() + final module class i24321$package() extends Object() { + this: i24321$package.type => + inline val BOOL_CONST: (true : Boolean) = true + inline val CHAR_CONST: ('A' : Char) = 'A' + inline val INT_CONST: (100000 : Int) = 100000 + inline val LONG_CONST: (100000L : Long) = 100000L + inline val FLOAT_CONST: (3.14f : Float) = 3.14f + inline val DOUBLE_CONST: (3.14159d : Double) = 3.14159d + inline val STRING_CONST: ("Hello, Scala 3!" : String) = "Hello, Scala 3!" + } +} + diff --git a/tests/printing/i24321.scala b/tests/printing/i24321.scala new file mode 100644 index 000000000000..5280535aa42f --- /dev/null +++ b/tests/printing/i24321.scala @@ -0,0 +1,7 @@ +inline val BOOL_CONST: Boolean = true +inline val CHAR_CONST: Char = 'A' +inline val INT_CONST: Int = 100000 +inline val LONG_CONST: Long = 100000L +inline val FLOAT_CONST: Float = 3.14f +inline val DOUBLE_CONST: Double = 3.14159 +inline val STRING_CONST: String = "Hello, Scala 3!" From f87f72c31f93ccf7b46ab7230191a237a3c67bf9 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Wed, 19 Nov 2025 11:14:17 +0100 Subject: [PATCH 2/3] Fix typo --- compiler/src/dotty/tools/dotc/inlines/Inlines.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 405a55c9a7ab..38aebaae5da9 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -634,7 +634,7 @@ object Inlines: if !Inlines.mightRefineInlineVal(mdef, sym) then return tp - // Only refine if the explicit type is a primitive value types and String. + // Only refine if the explicit type is a primitive value type or String. // We don't include opaque types. See tests/neg/i13851b.scala. val tpSym = tp.dealiasKeepOpaques.typeSymbol if !tpSym.isPrimitiveValueClass && tpSym != defn.StringClass then From 345fcba592e8e9e276fc0cbcd4396682cb8739e7 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Wed, 19 Nov 2025 10:14:19 +0000 Subject: [PATCH 3/3] Add inline-tracked-val test --- tests/neg/inline-tracked-val.scala | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/neg/inline-tracked-val.scala diff --git a/tests/neg/inline-tracked-val.scala b/tests/neg/inline-tracked-val.scala new file mode 100644 index 000000000000..a3b9b22f9240 --- /dev/null +++ b/tests/neg/inline-tracked-val.scala @@ -0,0 +1,50 @@ +import language.experimental.modularity + +inline val v1 = 3 +inline val v2: Short = 3 +inline val v3: Int = 3 +inline val v4 = 3: Short +inline val v5 = 3: Int +inline val v6: Short = 3: Short +inline val v7: Short = 3: Int // error +inline val v8: Int = 3: Short +inline val v9: Int = 3: Int + +// The same tests with `tracked` should behave the same way + +tracked inline val v1t = 3 +tracked inline val v2t: Short = 3 +tracked inline val v3t: Int = 3 +tracked inline val v4t = 3: Short +tracked inline val v5t = 3: Int +tracked inline val v6t: Short = 3: Short +tracked inline val v7t: Short = 3: Int // error +tracked inline val v8t: Int = 3: Short +tracked inline val v9t: Int = 3: Int + +@main def Test() = + summon[v1.type =:= 3] + summon[v2.type <:< Short] + summon[v3.type =:= 3] + summon[v4.type <:< Short] + summon[v5.type <:< Int] + summon[v5.type <:< 3] // error + summon[v6.type <:< Short] + summon[v7.type <:< Short] + summon[v8.type <:< Int] + summon[v8.type <:< 3] // error + summon[v9.type <:< Int] + summon[v9.type <:< 3] // error + + summon[v1t.type =:= 3] + summon[v2t.type <:< Short] + summon[v3t.type =:= 3] + summon[v4t.type <:< Short] + summon[v5t.type <:< Int] + summon[v5t.type <:< 3] // error + summon[v6t.type <:< Short] + summon[v7t.type <:< Short] + summon[v8t.type <:< Int] + summon[v8t.type <:< 3] // error + summon[v9t.type <:< Int] + summon[v9t.type <:< 3] // error