Skip to content

Commit c487723

Browse files
committed
Add "into as a modifier" scheme
1 parent 1611a0d commit c487723

File tree

15 files changed

+92
-11
lines changed

15 files changed

+92
-11
lines changed

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
236236

237237
case class Tracked()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Tracked)
238238

239+
case class Into()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Into)
240+
239241
/** Used under pureFunctions to mark impure function types `A => B` in `FunctionWithMods` */
240242
case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure)
241243
}

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ object Flags {
252252
/** A field generated for a primary constructor parameter (no matter if it's a 'val' or not),
253253
* or an accessor of such a field.
254254
*/
255-
val (_, ParamAccessor @ _, _) = newFlags(14, "<paramaccessor>")
255+
val (ParamAccessorOrInto @ _, ParamAccessor @ _, Into @ _) = newFlags(14, "<paramaccessor>", "into")
256256

257257
/** A value or class implementing a module */
258258
val (Module @ _, ModuleVal @ _, ModuleClass @ _) = newFlags(15, "module")
@@ -452,7 +452,7 @@ object Flags {
452452
commonFlags(Private, Protected, Final, Case, Implicit, Given, Override, JavaStatic, Transparent, Erased)
453453

454454
val TypeSourceModifierFlags: FlagSet =
455-
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open
455+
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open | Into
456456

457457
val TermSourceModifierFlags: FlagSet =
458458
CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Tracked
@@ -467,7 +467,7 @@ object Flags {
467467
* TODO: Should check that FromStartFlags do not change in completion
468468
*/
469469
val FromStartFlags: FlagSet = commonFlags(
470-
Module, Package, Deferred, Method, Case, Enum, Param, ParamAccessor,
470+
Module, Package, Deferred, Method, Case, Enum, Param, ParamAccessorOrInto,
471471
Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic,
472472
OuterOrCovariant, LabelOrContravariant, CaseAccessor, Tracked,
473473
Extension, NonMember, Implicit, Given, Permanent, Synthetic, Exported,
@@ -547,7 +547,7 @@ object Flags {
547547
val RetainedExportTypeFlags = Infix
548548

549549
/** Flags that apply only to classes */
550-
val ClassOnlyFlags = Sealed | Open | Abstract.toTypeFlags
550+
val ClassOnlyFlags = Sealed | Open | Into | Abstract.toTypeFlags
551551

552552
// ------- Other flag sets -------------------------------------
553553

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,10 @@ object Types extends TypeUtils {
457457
*/
458458
def isConversionTargetType(using Context): Boolean =
459459
dealias(KeepTypeVars | KeepOpaques).match
460-
case _: AppliedType =>
461-
isInto
460+
case tp: TypeRef =>
461+
tp.symbol.isClass && tp.symbol.is(Into)
462+
case tp @ AppliedType(tycon: TypeRef, _) =>
463+
isInto || tycon.isConversionTargetType
462464
case tp: AndOrType =>
463465
tp.tp1.isConversionTargetType && tp.tp2.isConversionTargetType
464466
case tp: TypeVar =>

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) {
905905
if (flags.is(Contravariant)) writeModTag(CONTRAVARIANT)
906906
if (flags.is(Opaque)) writeModTag(OPAQUE)
907907
if (flags.is(Open)) writeModTag(OPEN)
908+
if (flags.is(Into)) writeModTag(INTO)
908909
}
909910
}
910911

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,7 @@ class TreeUnpickler(reader: TastyReader,
758758
case TRANSPARENT => addFlag(Transparent)
759759
case INFIX => addFlag(Infix)
760760
case TRACKED => addFlag(Tracked)
761+
case INTO => addFlag(Into)
761762
case PRIVATEqualified =>
762763
readByte()
763764
privateWithin = readWithin

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3271,6 +3271,7 @@ object Parsers {
32713271
case IDENTIFIER =>
32723272
name match {
32733273
case nme.inline => Mod.Inline()
3274+
case nme.into => Mod.Into()
32743275
case nme.opaque => Mod.Opaque()
32753276
case nme.open => Mod.Open()
32763277
case nme.transparent => Mod.Transparent()

compiler/src/dotty/tools/dotc/parsing/Tokens.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ object Tokens extends TokensCommon {
299299

300300
final val closingParens = BitSet(RPAREN, RBRACKET, RBRACE)
301301

302-
final val softModifierNames = Set(nme.inline, nme.opaque, nme.open, nme.transparent, nme.infix)
302+
final val softModifierNames = Set(nme.inline, nme.into, nme.opaque, nme.open, nme.transparent, nme.infix)
303303

304304
def showTokenDetailed(token: Int): String = debugString(token)
305305

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,8 @@ object Checking {
635635
mods.mods.find(_.flags == flag).getOrElse(mdef).srcPos
636636
if mods.is(Open) then
637637
report.error(ModifierNotAllowedForDefinition(Open), flagSourcePos(Open))
638+
if mods.is(Into) then
639+
report.error(ModifierNotAllowedForDefinition(Into), flagSourcePos(Open))
638640
if mods.is(Abstract) then
639641
report.error(ModifierNotAllowedForDefinition(Abstract), flagSourcePos(Abstract))
640642
if mods.is(Sealed) then

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3178,6 +3178,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
31783178
def Implicit: Flags = dotc.core.Flags.Implicit
31793179
def Infix: Flags = dotc.core.Flags.Infix
31803180
def Inline: Flags = dotc.core.Flags.Inline
3181+
def Into: Flags = dotc.core.Flags.Into
31813182
def Invisible: Flags = dotc.core.Flags.Invisible
31823183
def JavaDefined: Flags = dotc.core.Flags.JavaDefined
31833184
def JavaStatic: Flags = dotc.core.Flags.JavaStatic
@@ -3203,6 +3204,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
32033204
def StableRealizable: Flags = dotc.core.Flags.StableRealizable
32043205
@deprecated("Use JavaStatic instead", "3.3.0") def Static: Flags = dotc.core.Flags.JavaStatic
32053206
def Synthetic: Flags = dotc.core.Flags.Synthetic
3207+
def Tracked: Flags = dotc.core.Flags.Tracked
32063208
def Trait: Flags = dotc.core.Flags.Trait
32073209
def Transparent: Flags = dotc.core.Flags.Transparent
32083210

docs/_docs/reference/experimental/into.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,28 @@ type Modifier = into[ModifierClass]
132132
```
133133
The into-erasure for function parameters also works in aliased types. So a function defining parameters of `Modifier` type can use them internally as if they were from the underlying `ModifierClass`.
134134

135+
## Alternative: `into` as a Modifier
136+
137+
The `into` scheme discussed so far strikes a nice balance between explicitness and convenience. But migrating to it from Scala 2 implicits does require major changes since possibly a large number of function signatures has to be changed to allow conversions on the arguments. This might ultimately hold back migration to Scala 3 implicits.
138+
139+
To facilitate migration, we also introduce an alternative way to specify target types of implicit conversions. We allow `into` as a soft modifier on
140+
classes and traits. If a class or trait is declared with `into`, then implicit conversions into that class or trait don't need a language import.
141+
142+
Example:
143+
```scala
144+
into class Keyword
145+
given stringToKeyword: Conversion[String, Keyword] = Keyword(_)
146+
147+
val dclKeywords = List("def", "val")
148+
val xs: List[Keyword] = dclkeywords ++ List("if", "then", "else")
149+
```
150+
Here, the strings `"if"`, `"then"`, and `"else"` are converted to `Keyword` using the given conversion `stringToKeyword`. No feature warning or error is issued since `Keyword` is declared as `into`.
151+
152+
The `into`-as-a-modifier scheme is handy in codebases that have a small set of specific types that are intended to be the targets of implicit conversions defined in the same codebase. But it can be easily abused.
153+
One should restrict the number of `into`-declared types to the absolute minimum. In particular, never make a type `into` to just cater for the
154+
possibility that someone might want to add an implicit conversion to it.
155+
156+
135157
## Details: Conversion target types
136158

137159
The description so far said that conversions are allowed if the target type
@@ -176,8 +198,8 @@ given Conversion[Int, C] = C(_)
176198

177199
def f(x: T) = ()
178200
def g(x: C) = ()
179-
f("abc") // ok
180-
g("abc") // error
201+
f(1) // ok
202+
g(1) // error
181203
```
182204
The call `f("abc")` type-checks since `f`'s parameter type `T` is `into`.
183205
But the call `g("abc")` does not type-check since `g`'s parameter type `C` is not `into`. It does not matter that `C` extends a trait `T` that is `into`.

tasty/src/dotty/tools/tasty/TastyFormat.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ Standard-Section: "ASTs" TopLevelStat*
229229
OPEN -- an open class
230230
INVISIBLE -- invisible during typechecking, except when resolving from TASTy
231231
TRACKED -- a tracked class parameter / a dependent class
232+
INTO -- a trait or class declared with `into`
232233
Annotation
233234
234235
Variance = STABLE -- invariant
@@ -511,6 +512,7 @@ object TastyFormat {
511512
final val EMPTYCLAUSE = 45
512513
final val SPLITCLAUSE = 46
513514
final val TRACKED = 47
515+
final val INTO = 48
514516

515517
// Tree Cat. 2: tag Nat
516518
final val firstNatTreeTag = SHAREDterm
@@ -703,7 +705,8 @@ object TastyFormat {
703705
| ANNOTATION
704706
| PRIVATEqualified
705707
| PROTECTEDqualified
706-
| TRACKED => true
708+
| TRACKED
709+
| INTO => true
707710
case _ => false
708711
}
709712

@@ -763,6 +766,8 @@ object TastyFormat {
763766
case PARAMsetter => "PARAMsetter"
764767
case EXPORTED => "EXPORTED"
765768
case OPEN => "OPEN"
769+
case INTO => "INTO"
770+
case TRACKED => "TRACKED"
766771
case INVISIBLE => "INVISIBLE"
767772
case PARAMalias => "PARAMalias"
768773
case EMPTYCLAUSE => "EMPTYCLAUSE"

tests/neg/i21786.scala

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/neg/into-mods.check

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-- Error: tests/neg/into-mods.scala:10:11 ------------------------------------------------------------------------------
2+
10 | into def foo = 22 // error
3+
| ^
4+
| values cannot be into
5+
-- [E156] Syntax Error: tests/neg/into-mods.scala:7:12 -----------------------------------------------------------------
6+
7 |into object M // error
7+
|^^^^^^^^^^^^^
8+
|Modifier into is not allowed for this definition
9+
-- Error: tests/neg/into-mods.scala:11:11 ------------------------------------------------------------------------------
10+
11 | into val x = 33 // error
11+
| ^^^^^^^^^^^^^^^
12+
| modifier(s) `into` incompatible with value definition
13+
-- Error: tests/neg/into-mods.scala:12:12 ------------------------------------------------------------------------------
14+
12 | into type T = Int // error
15+
| ^
16+
| only classes can be into

tests/neg/into-mods.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import language.experimental.into
2+
3+
into class Test
4+
5+
into trait T
6+
7+
into object M // error
8+
9+
object Test:
10+
into def foo = 22 // error
11+
into val x = 33 // error
12+
into type T = Int // error
13+

tests/warn/into-as-mod.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//> using options -feature
2+
3+
import language.experimental.into
4+
import Conversion.into
5+
6+
into trait T
7+
class C(x: Int) extends T
8+
9+
object Test:
10+
given Conversion[Int, C] = C(_)
11+
12+
def f(x: T) = ()
13+
def g(x: C) = ()
14+
f(1) // ok
15+
g(1) // warn

0 commit comments

Comments
 (0)