From 36cb5271cbaacfd661c909779d5a838706ea3558 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 27 Sep 2025 12:14:44 +0200 Subject: [PATCH 1/3] Make `into` a preview feature --- .../src/dotty/tools/dotc/core/Flags.scala | 2 +- .../tools/dotc/core/tasty/TreePickler.scala | 90 +++++++------ .../tools/dotc/core/tasty/TreeUnpickler.scala | 8 +- .../dotty/tools/dotc/parsing/Parsers.scala | 4 +- library/src/scala/Conversion.scala | 3 +- .../src/scala/annotation/internal/$into.scala | 5 - library/src/scala/language.scala | 1 + .../runtime/stdLibPatches/language.scala | 1 + tasty/src/dotty/tools/tasty/TastyFormat.scala | 1 + tests/neg/i23400.scala | 4 +- tests/neg/i5525.check | 118 +++++++++--------- tests/neg/i5525.scala | 7 +- tests/neg/into-mods.check | 16 +-- tests/neg/into-mods.scala | 3 +- tests/pos/into-separate/classes_1.scala | 2 + .../stdlibExperimentalDefinitions.scala | 5 - tests/warn/into-as-mod.scala | 2 +- 17 files changed, 132 insertions(+), 140 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 8d69917c8189..af7455fa2994 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -250,7 +250,7 @@ object Flags { val (Local @ _, _, _) = newFlags(13, "") /** A field generated for a primary constructor parameter (no matter if it's a 'val' or not), - * or an accessor of such a field. + * or an accessor of such a field / An `into` modifier on a class */ val (ParamAccessorOrInto @ _, ParamAccessor @ _, Into @ _) = newFlags(14, "", "into") diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 9e8567f57ddc..1703a8a87d6e 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -858,64 +858,60 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { if (flags.is(ParamAccessor) && sym.isTerm && !sym.isSetter) flags = flags &~ ParamAccessor // we only generate a tag for parameter setters pickleFlags(flags, sym.isTerm) - if flags.is(Into) then - // Temporary measure until we can change TastyFormat to include an INTO tag - pickleAnnotation(sym, mdef, Annotation(defn.SilentIntoAnnot, util.Spans.NoSpan)) val annots = sym.annotations.foreach(pickleAnnotation(sym, mdef, _)) } - def pickleFlags(flags: FlagSet, isTerm: Boolean)(using Context): Unit = { + def pickleFlags(flags: FlagSet, isTerm: Boolean)(using Context): Unit = import Flags.* - def writeModTag(tag: Int) = { + def writeModTag(tag: Int) = assert(isModifierTag(tag)) writeByte(tag) - } + if flags.is(Scala2x) then assert(attributes.scala2StandardLibrary) - if (flags.is(Private)) writeModTag(PRIVATE) - if (flags.is(Protected)) writeModTag(PROTECTED) - if (flags.is(Final, butNot = Module)) writeModTag(FINAL) - if (flags.is(Case)) writeModTag(CASE) - if (flags.is(Override)) writeModTag(OVERRIDE) - if (flags.is(Inline)) writeModTag(INLINE) - if (flags.is(InlineProxy)) writeModTag(INLINEPROXY) - if (flags.is(Macro)) writeModTag(MACRO) - if (flags.is(JavaStatic)) writeModTag(STATIC) - if (flags.is(Module)) writeModTag(OBJECT) - if (flags.is(Enum)) writeModTag(ENUM) - if (flags.is(Local)) writeModTag(LOCAL) - if (flags.is(Synthetic)) writeModTag(SYNTHETIC) - if (flags.is(Artifact)) writeModTag(ARTIFACT) + if flags.is(Private) then writeModTag(PRIVATE) + if flags.is(Protected) then writeModTag(PROTECTED) + if flags.is(Final, butNot = Module) then writeModTag(FINAL) + if flags.is(Case) then writeModTag(CASE) + if flags.is(Override) then writeModTag(OVERRIDE) + if flags.is(Inline) then writeModTag(INLINE) + if flags.is(InlineProxy) then writeModTag(INLINEPROXY) + if flags.is(Macro) then writeModTag(MACRO) + if flags.is(JavaStatic) then writeModTag(STATIC) + if flags.is(Module) then writeModTag(OBJECT) + if flags.is(Enum) then writeModTag(ENUM) + if flags.is(Local) then writeModTag(LOCAL) + if flags.is(Synthetic) then writeModTag(SYNTHETIC) + if flags.is(Artifact) then writeModTag(ARTIFACT) if flags.is(Transparent) then writeModTag(TRANSPARENT) if flags.is(Infix) then writeModTag(INFIX) if flags.is(Invisible) then writeModTag(INVISIBLE) - if (flags.is(Erased)) writeModTag(ERASED) - if (flags.is(Exported)) writeModTag(EXPORTED) - if (flags.is(Given)) writeModTag(GIVEN) - if (flags.is(Implicit)) writeModTag(IMPLICIT) - if (flags.is(Tracked)) writeModTag(TRACKED) - if (isTerm) { - if (flags.is(Lazy, butNot = Module)) writeModTag(LAZY) - if (flags.is(AbsOverride)) { writeModTag(ABSTRACT); writeModTag(OVERRIDE) } - if (flags.is(Mutable)) writeModTag(MUTABLE) - if (flags.is(Accessor)) writeModTag(FIELDaccessor) - if (flags.is(CaseAccessor)) writeModTag(CASEaccessor) - if (flags.is(HasDefault)) writeModTag(HASDEFAULT) + if flags.is(Erased) then writeModTag(ERASED) + if flags.is(Exported) then writeModTag(EXPORTED) + if flags.is(Given) then writeModTag(GIVEN) + if flags.is(Implicit) then writeModTag(IMPLICIT) + if flags.is(Tracked) then writeModTag(TRACKED) + if isTerm then + if flags.is(Lazy, butNot = Module) then writeModTag(LAZY) + if flags.is(AbsOverride) then { writeModTag(ABSTRACT); writeModTag(OVERRIDE) } + if flags.is(Mutable) then writeModTag(MUTABLE) + if flags.is(Accessor) then writeModTag(FIELDaccessor) + if flags.is(CaseAccessor) then writeModTag(CASEaccessor) + if flags.is(HasDefault) then writeModTag(HASDEFAULT) if flags.isAllOf(StableMethod) then writeModTag(STABLE) // other StableRealizable flag occurrences are either implied or can be recomputed - if (flags.is(Extension)) writeModTag(EXTENSION) - if (flags.is(ParamAccessor)) writeModTag(PARAMsetter) - if (flags.is(SuperParamAlias)) writeModTag(PARAMalias) - assert(!(flags.is(Label))) - } - else { - if (flags.is(Sealed)) writeModTag(SEALED) - if (flags.is(Abstract)) writeModTag(ABSTRACT) - if (flags.is(Trait)) writeModTag(TRAIT) - if (flags.is(Covariant)) writeModTag(COVARIANT) - if (flags.is(Contravariant)) writeModTag(CONTRAVARIANT) - if (flags.is(Opaque)) writeModTag(OPAQUE) - if (flags.is(Open)) writeModTag(OPEN) - } - } + if flags.is(Extension) then writeModTag(EXTENSION) + if flags.is(ParamAccessor) then writeModTag(PARAMsetter) + if flags.is(SuperParamAlias) then writeModTag(PARAMalias) + assert(!flags.is(Label)) + else + if flags.is(Sealed) then writeModTag(SEALED) + if flags.is(Abstract) then writeModTag(ABSTRACT) + if flags.is(Trait) then writeModTag(TRAIT) + if flags.is(Covariant) then writeModTag(COVARIANT) + if flags.is(Contravariant) then writeModTag(CONTRAVARIANT) + if flags.is(Opaque) then writeModTag(OPAQUE) + if flags.is(Open) then writeModTag(OPEN) + if flags.is(Into) then writeModTag(INTO) + end pickleFlags private def isUnpicklable(owner: Symbol, ann: Annotation)(using Context) = ann match { case Annotation.Child(sym) => sym.isInaccessibleChildOf(owner) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 3265d98fd859..aa1fbf371fec 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -670,12 +670,7 @@ class TreeUnpickler(reader: TastyReader, } val annotOwner = if sym.owner.isClass then newLocalDummy(sym.owner) else sym.owner - var annots = annotFns.map(_(annotOwner)) - if annots.exists(_.hasSymbol(defn.SilentIntoAnnot)) then - // Temporary measure until we can change TastyFormat to include an INTO tag - sym.setFlag(Into) - annots = annots.filterNot(_.symbol == defn.SilentIntoAnnot) - sym.annotations = annots + sym.annotations = annotFns.map(_(annotOwner)) if sym.isOpaqueAlias then sym.setFlag(Deferred) val isScala2MacroDefinedInScala3 = flags.is(Macro, butNot = Inline) && flags.is(Erased) ctx.owner match { @@ -765,6 +760,7 @@ class TreeUnpickler(reader: TastyReader, case TRANSPARENT => addFlag(Transparent) case INFIX => addFlag(Infix) case TRACKED => addFlag(Tracked) + case INTO => addFlag(Into) case PRIVATEqualified => readByte() privateWithin = readWithin diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index a31152ddcc6f..8d93821e2b3a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3367,12 +3367,14 @@ object Parsers { case IDENTIFIER => name match { case nme.inline => Mod.Inline() - case nme.into => Mod.Into() case nme.opaque => Mod.Opaque() case nme.open => Mod.Open() case nme.transparent => Mod.Transparent() case nme.infix => Mod.Infix() case nme.tracked => Mod.Tracked() + case nme.into => + Feature.checkPreviewFeature("`into`", in.sourcePos()) + Mod.Into() case nme.erased if in.erasedEnabled => Mod.Erased() case nme.update if Feature.ccEnabled => Mod.Update() } diff --git a/library/src/scala/Conversion.scala b/library/src/scala/Conversion.scala index 282566063b6e..a64f429cf5db 100644 --- a/library/src/scala/Conversion.scala +++ b/library/src/scala/Conversion.scala @@ -41,10 +41,9 @@ object Conversion: * conversions are tried from the type of `t` to `T`. `into[T]` types are erased to `T` * in all covariant positions of the types of parameter symbols. */ - @experimental opaque type into[+T] >: T = T /** Unwrap an `into` */ extension [T](x: into[T]) - @experimental def underlying: T = x + def underlying: T = x end Conversion diff --git a/library/src/scala/annotation/internal/$into.scala b/library/src/scala/annotation/internal/$into.scala index 565aad3f2aa7..446703f6cb25 100644 --- a/library/src/scala/annotation/internal/$into.scala +++ b/library/src/scala/annotation/internal/$into.scala @@ -1,9 +1,5 @@ package scala.annotation.internal -import language.experimental.captureChecking - -import annotation.experimental - /** An internal annotation on (part of) a parameter type that serves as a marker where * the original type was of the form `into[T]`. These annotated types are mapped back * to `into[T]` types when forming a method types from the parameter types. The idea is @@ -11,5 +7,4 @@ import annotation.experimental * `T`. Hence, we don't need to use `.underlying` to go from an into type to its * underlying type in the types of local parameters. */ -@experimental class $into extends annotation.StaticAnnotation \ No newline at end of file diff --git a/library/src/scala/language.scala b/library/src/scala/language.scala index bacbb09ad615..36531eea993c 100644 --- a/library/src/scala/language.scala +++ b/library/src/scala/language.scala @@ -292,6 +292,7 @@ object language { * @see [[https://dotty.epfl.ch/docs/reference/experimental/into-modifier]] */ @compileTimeOnly("`into` can only be used at compile time in import statements") + @deprecated("The into language import is no longer needed since the feature is now standard", since = "3.9") object into /** Experimental support for named tuples. diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 9d38ea4371ff..43b4dc64e099 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -99,6 +99,7 @@ object language: * @see [[https://dotty.epfl.ch/docs/reference/experimental/into-modifier]] */ @compileTimeOnly("`into` can only be used at compile time in import statements") + @deprecated("The into language import is no longer needed since the feature is now standard", since = "3.9") object into /** Experimental support for named tuples. diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index e3c410dc32b3..d4108c37ddda 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -512,6 +512,7 @@ object TastyFormat { final val SPLITCLAUSE = 46 final val TRACKED = 47 final val SUBMATCH = 48 // experimental.subCases + final val INTO = 49 // Tree Cat. 2: tag Nat final val firstNatTreeTag = SHAREDterm diff --git a/tests/neg/i23400.scala b/tests/neg/i23400.scala index 99a41d7a072b..1956385a1adc 100644 --- a/tests/neg/i23400.scala +++ b/tests/neg/i23400.scala @@ -1,4 +1,6 @@ -import scala.language.experimental.into +//> using options -preview +// preview needed for into in 3.8 + import Conversion.into import scala.deriving.Mirror diff --git a/tests/neg/i5525.check b/tests/neg/i5525.check index 81e352cd368f..cb8df7cbf3c9 100644 --- a/tests/neg/i5525.check +++ b/tests/neg/i5525.check @@ -1,114 +1,114 @@ --- Error: tests/neg/i5525.scala:1:0 ------------------------------------------------------------------------------------ -1 |abstract enum Foo1 { case C } // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:4:0 ------------------------------------------------------------------------------------ +4 |abstract enum Foo1 { case C } // error: only access modifiers allowed |^^^^^^^^ |This modifier is not allowed on an enum --- Error: tests/neg/i5525.scala:2:0 ------------------------------------------------------------------------------------ -2 |final enum Foo2 { case C } // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:5:0 ------------------------------------------------------------------------------------ +5 |final enum Foo2 { case C } // error: only access modifiers allowed |^^^^^ |This modifier is not allowed on an enum --- Error: tests/neg/i5525.scala:3:0 ------------------------------------------------------------------------------------ -3 |sealed enum Foo3 { case C } // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:6:0 ------------------------------------------------------------------------------------ +6 |sealed enum Foo3 { case C } // error: only access modifiers allowed |^^^^^^ |This modifier is not allowed on an enum --- Error: tests/neg/i5525.scala:4:0 ------------------------------------------------------------------------------------ -4 |implicit enum Foo4 { case C } // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:7:0 ------------------------------------------------------------------------------------ +7 |implicit enum Foo4 { case C } // error: only access modifiers allowed |^^^^^^^^ |This modifier is not allowed on an enum --- Error: tests/neg/i5525.scala:5:0 ------------------------------------------------------------------------------------ -5 |lazy enum Foo5 { case C } // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:8:0 ------------------------------------------------------------------------------------ +8 |lazy enum Foo5 { case C } // error: only access modifiers allowed |^^^^ |This modifier is not allowed on an enum --- Error: tests/neg/i5525.scala:6:0 ------------------------------------------------------------------------------------ -6 |override enum Foo7 { case C } // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:9:0 ------------------------------------------------------------------------------------ +9 |override enum Foo7 { case C } // error: only access modifiers allowed |^^^^^^^^ |This modifier is not allowed on an enum --- Error: tests/neg/i5525.scala:7:0 ------------------------------------------------------------------------------------ -7 |inline enum Foo8 { case C } // error: only access modifiers allowed - |^^^^^^ - |This modifier is not allowed on an enum --- Error: tests/neg/i5525.scala:8:0 ------------------------------------------------------------------------------------ -8 |opaque enum Foo9 { case C } // error: only access modifiers allowed - |^^^^^^ - |This modifier is not allowed on an enum --- Error: tests/neg/i5525.scala:11:2 ----------------------------------------------------------------------------------- -11 | abstract case C1() // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:10:0 ----------------------------------------------------------------------------------- +10 |inline enum Foo8 { case C } // error: only access modifiers allowed + |^^^^^^ + |This modifier is not allowed on an enum +-- Error: tests/neg/i5525.scala:11:0 ----------------------------------------------------------------------------------- +11 |opaque enum Foo9 { case C } // error: only access modifiers allowed + |^^^^^^ + |This modifier is not allowed on an enum +-- Error: tests/neg/i5525.scala:14:2 ----------------------------------------------------------------------------------- +14 | abstract case C1() // error: only access modifiers allowed | ^^^^^^^^ | This modifier is not allowed on an enum case --- Error: tests/neg/i5525.scala:12:2 ----------------------------------------------------------------------------------- -12 | final case C2() // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:15:2 ----------------------------------------------------------------------------------- +15 | final case C2() // error: only access modifiers allowed | ^^^^^ | This modifier is not allowed on an enum case --- Error: tests/neg/i5525.scala:13:2 ----------------------------------------------------------------------------------- -13 | sealed case C3() // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:16:2 ----------------------------------------------------------------------------------- +16 | sealed case C3() // error: only access modifiers allowed | ^^^^^^ | This modifier is not allowed on an enum case --- Error: tests/neg/i5525.scala:14:2 ----------------------------------------------------------------------------------- -14 | implicit case C4() // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:17:2 ----------------------------------------------------------------------------------- +17 | implicit case C4() // error: only access modifiers allowed | ^^^^^^^^ | This modifier is not allowed on an enum case --- Error: tests/neg/i5525.scala:15:2 ----------------------------------------------------------------------------------- -15 | lazy case C5() // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:18:2 ----------------------------------------------------------------------------------- +18 | lazy case C5() // error: only access modifiers allowed | ^^^^ | This modifier is not allowed on an enum case --- Error: tests/neg/i5525.scala:16:2 ----------------------------------------------------------------------------------- -16 | override case C7() // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:19:2 ----------------------------------------------------------------------------------- +19 | override case C7() // error: only access modifiers allowed | ^^^^^^^^ | This modifier is not allowed on an enum case --- Error: tests/neg/i5525.scala:22:2 ----------------------------------------------------------------------------------- -22 | abstract case C1 // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:25:2 ----------------------------------------------------------------------------------- +25 | abstract case C1 // error: only access modifiers allowed | ^^^^^^^^ | This modifier is not allowed on an enum case --- Error: tests/neg/i5525.scala:23:2 ----------------------------------------------------------------------------------- -23 | final case C2 // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:26:2 ----------------------------------------------------------------------------------- +26 | final case C2 // error: only access modifiers allowed | ^^^^^ | This modifier is not allowed on an enum case --- Error: tests/neg/i5525.scala:24:2 ----------------------------------------------------------------------------------- -24 | sealed case C3 // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:27:2 ----------------------------------------------------------------------------------- +27 | sealed case C3 // error: only access modifiers allowed | ^^^^^^ | This modifier is not allowed on an enum case --- Error: tests/neg/i5525.scala:25:2 ----------------------------------------------------------------------------------- -25 | implicit case C4 // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:28:2 ----------------------------------------------------------------------------------- +28 | implicit case C4 // error: only access modifiers allowed | ^^^^^^^^ | This modifier is not allowed on an enum case --- Error: tests/neg/i5525.scala:26:2 ----------------------------------------------------------------------------------- -26 | lazy case C5 // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:29:2 ----------------------------------------------------------------------------------- +29 | lazy case C5 // error: only access modifiers allowed | ^^^^ | This modifier is not allowed on an enum case --- Error: tests/neg/i5525.scala:27:2 ----------------------------------------------------------------------------------- -27 | override case C7 // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:30:2 ----------------------------------------------------------------------------------- +30 | override case C7 // error: only access modifiers allowed | ^^^^^^^^ | This modifier is not allowed on an enum case --- Error: tests/neg/i5525.scala:33:12 ---------------------------------------------------------------------------------- -33 | inline case C10() // error: only access modifiers allowed +-- Error: tests/neg/i5525.scala:36:12 ---------------------------------------------------------------------------------- +36 | inline case C10() // error: only access modifiers allowed | ^^^^ | end of statement expected but 'case' found --- Error: tests/neg/i5525.scala:36:0 ----------------------------------------------------------------------------------- -36 |final enum Foo13 { // error: only access modifiers and `into` allowed +-- Error: tests/neg/i5525.scala:39:0 ----------------------------------------------------------------------------------- +39 |final enum Foo13 { // error: only access modifiers and `into` allowed |^^^^^ |This modifier is not allowed on an enum --- Error: tests/neg/i5525.scala:42:8 ----------------------------------------------------------------------------------- -42 | infix case C2 extends Foo14[Int, Int] // error // error +-- Error: tests/neg/i5525.scala:45:8 ----------------------------------------------------------------------------------- +45 | infix case C2 extends Foo14[Int, Int] // error // error | ^^^^ | end of statement expected but 'case' found --- Error: tests/neg/i5525.scala:49:7 ----------------------------------------------------------------------------------- -49 | into case C1 // error +-- Error: tests/neg/i5525.scala:50:7 ----------------------------------------------------------------------------------- +50 | into case C1 // error // error | ^^^^ | end of statement expected but 'case' found --- [E145] Syntax Error: tests/neg/i5525.scala:32:5 --------------------------------------------------------------------- -32 |enum Foo12 { // error: Enumerations must contain at least one case +-- [E145] Syntax Error: tests/neg/i5525.scala:35:5 --------------------------------------------------------------------- +35 |enum Foo12 { // error: Enumerations must contain at least one case | ^^^^^ | Enumerations must contain at least one case | | longer explanation available when compiling with `-explain` --- [E006] Not Found Error: tests/neg/i5525.scala:42:2 ------------------------------------------------------------------ -42 | infix case C2 extends Foo14[Int, Int] // error // error +-- [E006] Not Found Error: tests/neg/i5525.scala:45:2 ------------------------------------------------------------------ +45 | infix case C2 extends Foo14[Int, Int] // error // error | ^^^^^ | Not found: infix | | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/i5525.scala:49:2 ---------------------------------------------------------- -49 | into case C1 // error +-- [E006] Not Found Error: tests/neg/i5525.scala:50:2 ------------------------------------------------------------------ +50 | into case C1 // error // error | ^^^^ - | A pure expression does nothing in statement position + | Not found: into | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i5525.scala b/tests/neg/i5525.scala index eff8d07fdddd..d72e1716663b 100644 --- a/tests/neg/i5525.scala +++ b/tests/neg/i5525.scala @@ -1,3 +1,6 @@ +//> using options -preview +// preview needed for into in 3.8 + abstract enum Foo1 { case C } // error: only access modifiers allowed final enum Foo2 { case C } // error: only access modifiers allowed sealed enum Foo3 { case C } // error: only access modifiers allowed @@ -42,9 +45,7 @@ infix enum Foo14[A, B]{ // OK infix case C2 extends Foo14[Int, Int] // error // error } -import language.experimental.into - into enum Foo15 { // OK case C0 - into case C1 // error + into case C1 // error // error } \ No newline at end of file diff --git a/tests/neg/into-mods.check b/tests/neg/into-mods.check index 30ff99372b99..b53196491361 100644 --- a/tests/neg/into-mods.check +++ b/tests/neg/into-mods.check @@ -1,16 +1,16 @@ --- Error: tests/neg/into-mods.scala:10:11 ------------------------------------------------------------------------------ -10 | into def foo = 22 // error +-- Error: tests/neg/into-mods.scala:11:11 ------------------------------------------------------------------------------ +11 | into def foo = 22 // error | ^ | values cannot be into --- [E156] Syntax Error: tests/neg/into-mods.scala:7:12 ----------------------------------------------------------------- -7 |into object M // error +-- [E156] Syntax Error: tests/neg/into-mods.scala:8:12 ----------------------------------------------------------------- +8 |into object M // error |^^^^^^^^^^^^^ |Modifier into is not allowed for this definition --- [E156] Syntax Error: tests/neg/into-mods.scala:12:2 ----------------------------------------------------------------- -12 | into type T = Int // error +-- [E156] Syntax Error: tests/neg/into-mods.scala:13:2 ----------------------------------------------------------------- +13 | into type T = Int // error | ^^^^ | Modifier into is not allowed for this definition --- Error: tests/neg/into-mods.scala:11:11 ------------------------------------------------------------------------------ -11 | into val x = 33 // error +-- Error: tests/neg/into-mods.scala:12:11 ------------------------------------------------------------------------------ +12 | into val x = 33 // error | ^^^^^^^^^^^^^^^ | modifier(s) `into` incompatible with value definition diff --git a/tests/neg/into-mods.scala b/tests/neg/into-mods.scala index 7b426471c0e2..ed6200d2f65c 100644 --- a/tests/neg/into-mods.scala +++ b/tests/neg/into-mods.scala @@ -1,4 +1,5 @@ -import language.experimental.into +//> using options -preview +// preview needed for into in 3.8 into class Test diff --git a/tests/pos/into-separate/classes_1.scala b/tests/pos/into-separate/classes_1.scala index b39bfe840e3e..c12223522c5d 100644 --- a/tests/pos/into-separate/classes_1.scala +++ b/tests/pos/into-separate/classes_1.scala @@ -1,3 +1,5 @@ +//> using options -preview + package test import language.experimental.into diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index a55d2132e7b8..09d76a6bd8e5 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -49,11 +49,6 @@ val experimentalDefinitionInLibrary = Set( "scala.caps.package$package$.Exclusive", "scala.caps.package$package$.Shared", - //// New feature: into - "scala.Conversion$.into", - "scala.Conversion$.underlying", - "scala.annotation.internal.$into", - //// New feature: Macro annotations "scala.annotation.MacroAnnotation", diff --git a/tests/warn/into-as-mod.scala b/tests/warn/into-as-mod.scala index 587bb375e4d1..a531d2509581 100644 --- a/tests/warn/into-as-mod.scala +++ b/tests/warn/into-as-mod.scala @@ -1,4 +1,4 @@ -//> using options -feature +//> using options -feature -preview import language.experimental.into import Conversion.into From 0e80aae5f9d312ed28ba4e5013374cbcd2966feb Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 27 Sep 2025 16:22:49 +0200 Subject: [PATCH 2/3] Update TastyFormat to include INTO --- tasty/src/dotty/tools/tasty/TastyFormat.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index d4108c37ddda..530623ebb2ce 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -228,7 +228,8 @@ Standard-Section: "ASTs" TopLevelStat* EXPORTED -- An export forwarder OPEN -- an open class INVISIBLE -- invisible during typechecking, except when resolving from TASTy - TRACKED -- a tracked class parameter / a dependent class + TRACKED + INTO -- a tracked class parameter / a dependent class Annotation Variance = STABLE -- invariant @@ -705,7 +706,8 @@ object TastyFormat { | ANNOTATION | PRIVATEqualified | PROTECTEDqualified - | TRACKED => true + | TRACKED + | INTO => true case _ => false } From dd0c26d0f0481c91f1650350a5d64a1e70dd2162 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 29 Sep 2025 19:48:43 +0200 Subject: [PATCH 3/3] Address review comments - Make all aspects of into a -preview feature - Drop experimental import in tests --- library/src/scala/Conversion.scala | 3 +++ .../src/scala/annotation/internal/$into.scala | 1 + library/src/scala/language.scala | 2 +- .../runtime/stdLibPatches/language.scala | 2 +- tasty/src/dotty/tools/tasty/TastyFormat.scala | 4 ++-- tests/neg/into-inferred.check | 20 ++++++++-------- tests/neg/into-inferred.scala | 3 +-- tests/neg/into-override.check | 24 +++++++++---------- tests/neg/into-override.scala | 3 +-- tests/pos/i23398.scala | 4 ++-- tests/pos/into-bigint.scala | 2 +- tests/pos/into-class.scala | 2 +- tests/pos/into-expr.scala | 3 +-- tests/pos/into-sam.scala | 3 +-- tests/pos/into-separate/Test_2.scala | 3 +-- tests/pos/into-separate/classes_1.scala | 2 -- tests/run/Parser.scala | 2 +- tests/run/convertible.scala | 3 +-- tests/warn/convertible.scala | 3 +-- tests/warn/into-as-mod.scala | 1 - 20 files changed, 42 insertions(+), 48 deletions(-) diff --git a/library/src/scala/Conversion.scala b/library/src/scala/Conversion.scala index a64f429cf5db..3329e6a49b2b 100644 --- a/library/src/scala/Conversion.scala +++ b/library/src/scala/Conversion.scala @@ -1,6 +1,7 @@ package scala import language.experimental.captureChecking +import annotation.internal.preview /** A class for implicit values that can serve as implicit conversions. * The implicit resolution algorithm will act as if there existed @@ -41,9 +42,11 @@ object Conversion: * conversions are tried from the type of `t` to `T`. `into[T]` types are erased to `T` * in all covariant positions of the types of parameter symbols. */ + @preview opaque type into[+T] >: T = T /** Unwrap an `into` */ extension [T](x: into[T]) + @preview def underlying: T = x end Conversion diff --git a/library/src/scala/annotation/internal/$into.scala b/library/src/scala/annotation/internal/$into.scala index 446703f6cb25..3ca9d193bd0f 100644 --- a/library/src/scala/annotation/internal/$into.scala +++ b/library/src/scala/annotation/internal/$into.scala @@ -7,4 +7,5 @@ package scala.annotation.internal * `T`. Hence, we don't need to use `.underlying` to go from an into type to its * underlying type in the types of local parameters. */ +@preview class $into extends annotation.StaticAnnotation \ No newline at end of file diff --git a/library/src/scala/language.scala b/library/src/scala/language.scala index 36531eea993c..a2000fd6e8ab 100644 --- a/library/src/scala/language.scala +++ b/library/src/scala/language.scala @@ -292,7 +292,7 @@ object language { * @see [[https://dotty.epfl.ch/docs/reference/experimental/into-modifier]] */ @compileTimeOnly("`into` can only be used at compile time in import statements") - @deprecated("The into language import is no longer needed since the feature is now standard", since = "3.9") + @deprecated("The into language import is no longer needed since the feature is now in preview", since = "3.8") object into /** Experimental support for named tuples. diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 43b4dc64e099..b6c7c5327815 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -99,7 +99,7 @@ object language: * @see [[https://dotty.epfl.ch/docs/reference/experimental/into-modifier]] */ @compileTimeOnly("`into` can only be used at compile time in import statements") - @deprecated("The into language import is no longer needed since the feature is now standard", since = "3.9") + @deprecated("The into language import is no longer needed since the feature is now in preview", since = "3.8") object into /** Experimental support for named tuples. diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 530623ebb2ce..b366411322b9 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -228,8 +228,8 @@ Standard-Section: "ASTs" TopLevelStat* EXPORTED -- An export forwarder OPEN -- an open class INVISIBLE -- invisible during typechecking, except when resolving from TASTy - TRACKED - INTO -- a tracked class parameter / a dependent class + TRACKED -- a tracked class parameter / a dependent class + INTO -- a legal conversion target Annotation Variance = STABLE -- invariant diff --git a/tests/neg/into-inferred.check b/tests/neg/into-inferred.check index f96fe4bbafaf..6f97e883aa29 100644 --- a/tests/neg/into-inferred.check +++ b/tests/neg/into-inferred.check @@ -1,36 +1,36 @@ --- [E007] Type Mismatch Error: tests/neg/into-inferred.scala:34:32 ----------------------------------------------------- -34 | val l1: List[into[Keyword]] = l :+ "then" :+ "else" // error +-- [E007] Type Mismatch Error: tests/neg/into-inferred.scala:33:32 ----------------------------------------------------- +33 | val l1: List[into[Keyword]] = l :+ "then" :+ "else" // error | ^^^^^^^^^^^^^^^^^^^^^ | Found: List[Conversion.into[Keyword] | String] | Required: List[Conversion.into[Keyword]] | | longer explanation available when compiling with `-explain` --- Feature Warning: tests/neg/into-inferred.scala:22:43 ---------------------------------------------------------------- -22 | val ys: List[into[Keyword]] = List(ifKW, "then", "else") // warn // warn +-- Feature Warning: tests/neg/into-inferred.scala:21:43 ---------------------------------------------------------------- +21 | val ys: List[into[Keyword]] = List(ifKW, "then", "else") // warn // warn | ^^^^^^ | Use of implicit conversion given instance given_Conversion_String_Keyword in object Test should be enabled | by adding the import clause 'import scala.language.implicitConversions' | or by setting the compiler option -language:implicitConversions. | See the Scala docs for value scala.language.implicitConversions for a discussion | why the feature should be explicitly enabled. --- Feature Warning: tests/neg/into-inferred.scala:22:51 ---------------------------------------------------------------- -22 | val ys: List[into[Keyword]] = List(ifKW, "then", "else") // warn // warn +-- Feature Warning: tests/neg/into-inferred.scala:21:51 ---------------------------------------------------------------- +21 | val ys: List[into[Keyword]] = List(ifKW, "then", "else") // warn // warn | ^^^^^^ | Use of implicit conversion given instance given_Conversion_String_Keyword in object Test should be enabled | by adding the import clause 'import scala.language.implicitConversions' | or by setting the compiler option -language:implicitConversions. | See the Scala docs for value scala.language.implicitConversions for a discussion | why the feature should be explicitly enabled. --- Feature Warning: tests/neg/into-inferred.scala:35:42 ---------------------------------------------------------------- -35 | val l2: List[into[Keyword]] = l ++ List("then", "else") // warn // warn +-- Feature Warning: tests/neg/into-inferred.scala:34:42 ---------------------------------------------------------------- +34 | val l2: List[into[Keyword]] = l ++ List("then", "else") // warn // warn | ^^^^^^ | Use of implicit conversion given instance given_Conversion_String_Keyword in object Test should be enabled | by adding the import clause 'import scala.language.implicitConversions' | or by setting the compiler option -language:implicitConversions. | See the Scala docs for value scala.language.implicitConversions for a discussion | why the feature should be explicitly enabled. --- Feature Warning: tests/neg/into-inferred.scala:35:50 ---------------------------------------------------------------- -35 | val l2: List[into[Keyword]] = l ++ List("then", "else") // warn // warn +-- Feature Warning: tests/neg/into-inferred.scala:34:50 ---------------------------------------------------------------- +34 | val l2: List[into[Keyword]] = l ++ List("then", "else") // warn // warn | ^^^^^^ | Use of implicit conversion given instance given_Conversion_String_Keyword in object Test should be enabled | by adding the import clause 'import scala.language.implicitConversions' diff --git a/tests/neg/into-inferred.scala b/tests/neg/into-inferred.scala index e2d459394ab8..1ced0bd3ef3a 100644 --- a/tests/neg/into-inferred.scala +++ b/tests/neg/into-inferred.scala @@ -1,5 +1,4 @@ -//> using options -feature -import language.experimental.into +//> using options -feature -preview import Conversion.{into, underlying} trait Token diff --git a/tests/neg/into-override.check b/tests/neg/into-override.check index c497986a9800..81162ef57c0b 100644 --- a/tests/neg/into-override.check +++ b/tests/neg/into-override.check @@ -1,33 +1,33 @@ --- [E120] Naming Error: tests/neg/into-override.scala:16:6 ------------------------------------------------------------- -16 |trait C[X] extends A[X]: // error +-- [E120] Naming Error: tests/neg/into-override.scala:15:6 ------------------------------------------------------------- +15 |trait C[X] extends A[X]: // error | ^ | Name clash between defined and inherited member: - | def f(x: X): Unit in trait A at line 11 and - | override def f(x: Conversion.into[X]): Unit in trait C at line 17 + | def f(x: X): Unit in trait A at line 10 and + | override def f(x: Conversion.into[X]): Unit in trait C at line 16 | have the same type (x: Object): Unit after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. | | longer explanation available when compiling with `-explain` --- [E120] Naming Error: tests/neg/into-override.scala:19:6 ------------------------------------------------------------- -19 |class D[X] extends B[X], C[X] // error +-- [E120] Naming Error: tests/neg/into-override.scala:18:6 ------------------------------------------------------------- +18 |class D[X] extends B[X], C[X] // error | ^ | Name clash between inherited members: - | override def f(x: X): Unit in trait B at line 14 and - | override def f(x: Conversion.into[X]): Unit in trait C at line 17 + | override def f(x: X): Unit in trait B at line 13 and + | override def f(x: Conversion.into[X]): Unit in trait C at line 16 | have the same type (x: Object): Unit after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. | | longer explanation available when compiling with `-explain` --- [E120] Naming Error: tests/neg/into-override.scala:21:6 ------------------------------------------------------------- -21 |trait E[X] extends C[X]: // error +-- [E120] Naming Error: tests/neg/into-override.scala:20:6 ------------------------------------------------------------- +20 |trait E[X] extends C[X]: // error | ^ | Name clash between defined and inherited member: - | override def f(x: Conversion.into[X]): Unit in trait C at line 17 and - | override def f(x: X): Unit in trait E at line 22 + | override def f(x: Conversion.into[X]): Unit in trait C at line 16 and + | override def f(x: X): Unit in trait E at line 21 | have the same type (x: Object): Unit after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions diff --git a/tests/neg/into-override.scala b/tests/neg/into-override.scala index 6a25ddbeb7f0..6a1e2ca10fda 100644 --- a/tests/neg/into-override.scala +++ b/tests/neg/into-override.scala @@ -1,6 +1,5 @@ -//> using options -Xfatal-warnings +//> using options -Xfatal-warnings -preview -import language.experimental.into import Conversion.into class Text(val str: String) diff --git a/tests/pos/i23398.scala b/tests/pos/i23398.scala index b38b139cc485..4d6be7248c5d 100644 --- a/tests/pos/i23398.scala +++ b/tests/pos/i23398.scala @@ -1,5 +1,5 @@ -//> using options -feature -Werror -import scala.language.experimental.into +//> using options -feature -Werror -preview + import Conversion.into case class Foo(x: Int) diff --git a/tests/pos/into-bigint.scala b/tests/pos/into-bigint.scala index 9ecac8c68dcd..928cfb2261ac 100644 --- a/tests/pos/into-bigint.scala +++ b/tests/pos/into-bigint.scala @@ -1,4 +1,4 @@ -import language.experimental.into +//> using options -preview import Conversion.into class BigInt(x: Int): diff --git a/tests/pos/into-class.scala b/tests/pos/into-class.scala index 989c932475cb..652b167000ae 100644 --- a/tests/pos/into-class.scala +++ b/tests/pos/into-class.scala @@ -1,4 +1,4 @@ -import language.experimental.into +//> using options -preview import Conversion.into class Text(str: String) diff --git a/tests/pos/into-expr.scala b/tests/pos/into-expr.scala index 85b287668ee8..8fd1a13fbfee 100644 --- a/tests/pos/into-expr.scala +++ b/tests/pos/into-expr.scala @@ -1,7 +1,6 @@ -//> using options -feature -Xfatal-warnings +//> using options -feature -Xfatal-warnings -preview -import language.experimental.into import Conversion.into enum Expr: diff --git a/tests/pos/into-sam.scala b/tests/pos/into-sam.scala index c4b528fce215..bb12696b9fc6 100644 --- a/tests/pos/into-sam.scala +++ b/tests/pos/into-sam.scala @@ -1,7 +1,6 @@ -//> using options -feature -Xfatal-warnings +//> using options -feature -Xfatal-warnings -preview -import language.experimental.into import Conversion.into class Text(val str: String) diff --git a/tests/pos/into-separate/Test_2.scala b/tests/pos/into-separate/Test_2.scala index afcd46522229..3b25fe907feb 100644 --- a/tests/pos/into-separate/Test_2.scala +++ b/tests/pos/into-separate/Test_2.scala @@ -1,6 +1,5 @@ -//> using options -feature +//> using options -feature -preview package test -import language.experimental.into object Test: given Conversion[Int, C] = C(_) diff --git a/tests/pos/into-separate/classes_1.scala b/tests/pos/into-separate/classes_1.scala index c12223522c5d..8d005234aa8e 100644 --- a/tests/pos/into-separate/classes_1.scala +++ b/tests/pos/into-separate/classes_1.scala @@ -2,8 +2,6 @@ package test -import language.experimental.into - into trait T class C(x: Int) extends T diff --git a/tests/run/Parser.scala b/tests/run/Parser.scala index 33b3379d2c76..e6e5c7556569 100644 --- a/tests/run/Parser.scala +++ b/tests/run/Parser.scala @@ -1,4 +1,4 @@ -import language.experimental.into +//> using options -preview import Conversion.into type Input = List[String] diff --git a/tests/run/convertible.scala b/tests/run/convertible.scala index affb0698ed70..bf1a41234b17 100644 --- a/tests/run/convertible.scala +++ b/tests/run/convertible.scala @@ -1,6 +1,5 @@ -//> using options -feature -Xfatal-warnings +//> using options -feature -Xfatal-warnings -preview -import language.experimental.into import Conversion.into class Text(val str: String) diff --git a/tests/warn/convertible.scala b/tests/warn/convertible.scala index 655d935adb59..86a82445a58c 100644 --- a/tests/warn/convertible.scala +++ b/tests/warn/convertible.scala @@ -1,6 +1,5 @@ -//> using options -feature +//> using options -feature -preview -import language.experimental.into import Conversion.into class Text(val str: String) diff --git a/tests/warn/into-as-mod.scala b/tests/warn/into-as-mod.scala index a531d2509581..02edb7a737e4 100644 --- a/tests/warn/into-as-mod.scala +++ b/tests/warn/into-as-mod.scala @@ -1,6 +1,5 @@ //> using options -feature -preview -import language.experimental.into import Conversion.into into trait T