Skip to content

Commit 692bf77

Browse files
committed
Typechecking with into modifiers
1 parent 3cabdea commit 692bf77

File tree

17 files changed

+306
-27
lines changed

17 files changed

+306
-27
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,7 @@ class Definitions {
10161016
@tu lazy val ImplicitNotFoundAnnot: ClassSymbol = requiredClass("scala.annotation.implicitNotFound")
10171017
@tu lazy val InlineParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InlineParam")
10181018
@tu lazy val IntoAnnot: ClassSymbol = requiredClass("scala.annotation.internal.into")
1019+
@tu lazy val IntoParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.$into")
10191020
@tu lazy val ErasedParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ErasedParam")
10201021
@tu lazy val MainAnnot: ClassSymbol = requiredClass("scala.main")
10211022
@tu lazy val MappedAlternativeAnnot: ClassSymbol = requiredClass("scala.annotation.internal.MappedAlternative")

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

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -419,8 +419,9 @@ object Types extends TypeUtils {
419419
typeSymbol eq defn.RepeatedParamClass
420420

421421
/** Is this a parameter type that allows implicit argument converson? */
422-
def isInto(using Context): Boolean =
423-
typeSymbol eq ???
422+
def isInto(using Context): Boolean = this match
423+
case AnnotatedType(_, annot) => annot.symbol == defn.IntoParamAnnot
424+
case _ => false
424425

425426
/** Is this the type of a method that has a repeated parameter type as
426427
* last parameter type?
@@ -1927,7 +1928,9 @@ object Types extends TypeUtils {
19271928
case res => res
19281929
}
19291930
defn.FunctionNOf(
1930-
mt.paramInfos.mapConserve(_.translateFromRepeated(toArray = isJava)),
1931+
mt.paramInfos.mapConserve:
1932+
_.translateFromRepeated(toArray = isJava)
1933+
.mapIntoAnnot(defn.IntoParamAnnot, null),
19311934
result1, isContextual)
19321935
if mt.hasErasedParams then
19331936
defn.PolyFunctionOf(mt)
@@ -1975,6 +1978,38 @@ object Types extends TypeUtils {
19751978
case _ => this
19761979
}
19771980

1981+
/** A mapping between mapping one kind of into annotation to another or
1982+
* dropping into annotations.
1983+
* @param from the into annotation to map
1984+
* @param to either the replacement annotation symbol, or `null`
1985+
* in which case the `from` annotations are dropped.
1986+
*/
1987+
def mapIntoAnnot(from: ClassSymbol, to: ClassSymbol | Null)(using Context): Type = this match
1988+
case self @ AnnotatedType(tp, annot) =>
1989+
val tp1 = tp.mapIntoAnnot(from, to)
1990+
if annot.symbol == from then
1991+
if to == null then tp1
1992+
else AnnotatedType(tp1, Annotation(to, from.span))
1993+
else self.derivedAnnotatedType(tp1, annot)
1994+
case AppliedType(tycon, arg :: Nil) if tycon.typeSymbol == defn.RepeatedParamClass =>
1995+
val arg1 = arg.mapIntoAnnot(from, to)
1996+
if arg1 eq arg then this
1997+
else AppliedType(tycon, arg1 :: Nil)
1998+
case defn.FunctionOf(argTypes, resType, isContextual) =>
1999+
val resType1 = resType.mapIntoAnnot(from, to)
2000+
if resType1 eq resType then this
2001+
else defn.FunctionOf(argTypes, resType1, isContextual)
2002+
case RefinedType(parent, rname, mt: MethodOrPoly) =>
2003+
val mt1 = mt.mapIntoAnnot(from, to)
2004+
if mt1 eq mt then this
2005+
else RefinedType(parent.mapIntoAnnot(from, to), rname, mt1)
2006+
case mt: MethodOrPoly =>
2007+
mt.derivedLambdaType(resType = mt.resType.mapIntoAnnot(from, to))
2008+
case tp: ExprType =>
2009+
tp.derivedExprType(tp.resType.mapIntoAnnot(from, to))
2010+
case _ =>
2011+
this
2012+
19782013
/** A type capturing `ref` */
19792014
def capturing(ref: CaptureRef)(using Context): Type =
19802015
if captureSet.accountsFor(ref) then this
@@ -4122,6 +4157,7 @@ object Types extends TypeUtils {
41224157
/** Produce method type from parameter symbols, with special mappings for repeated
41234158
* and inline parameters:
41244159
* - replace @repeated annotations on Seq or Array types by <repeated> types
4160+
* - map into annotations to $into annotations
41254161
* - add @inlineParam to inline parameters
41264162
* - add @erasedParam to erased parameters
41274163
* - wrap types of parameters that have an @allowConversions annotation with Into[_]
@@ -4134,7 +4170,7 @@ object Types extends TypeUtils {
41344170
def paramInfo(param: Symbol) =
41354171
var paramType = param.info
41364172
.annotatedToRepeated
4137-
//.mapIntoAnnot(defn.IntoType.appliedTo(_))
4173+
.mapIntoAnnot(defn.IntoAnnot, defn.IntoParamAnnot)
41384174
if param.is(Inline) then
41394175
paramType = addAnnotation(paramType, defn.InlineParamAnnot, param)
41404176
if param.is(Erased) then

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,9 @@ class PlainPrinter(_ctx: Context) extends Printer {
287287
case AnnotatedType(tpe, annot) =>
288288
if annot.symbol == defn.InlineParamAnnot || annot.symbol == defn.ErasedParamAnnot
289289
then toText(tpe)
290-
else if annot.symbol == defn.IntoAnnot && !printDebug then
291-
atPrec(GlobalPrec):
292-
Str("into ") ~ toText(tpe)
290+
else if (annot.symbol == defn.IntoAnnot || annot.symbol == defn.IntoParamAnnot)
291+
&& !printDebug
292+
then atPrec(GlobalPrec)( Str("into ") ~ toText(tpe) )
293293
else toTextLocal(tpe) ~ " " ~ toText(annot)
294294
case tp: TypeVar =>
295295
def toTextCaret(tp: Type) = if printDebug then toTextLocal(tp) ~ Str("^") else toText(tp)

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
244244
case _ =>
245245
val tsym = tycon.typeSymbol
246246
if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*"
247-
else if tp.isInto then atPrec(GlobalPrec)( "into " ~ toText(args.head) )
248247
else if defn.isFunctionSymbol(tsym) then toTextFunction(tp)
249248
else if isInfixType(tp) then
250249
val l :: r :: Nil = args: @unchecked

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1082,7 +1082,7 @@ trait Checking {
10821082

10831083
/** If `tree` is an application of a new-style implicit conversion (using the apply
10841084
* method of a `scala.Conversion` instance), check that the expected type is
1085-
* a convertible formal parameter type or that implicit conversions are enabled.
1085+
* annotated with @$into or that implicit conversions are enabled.
10861086
*/
10871087
def checkImplicitConversionUseOK(tree: Tree, expected: Type)(using Context): Unit =
10881088
val sym = tree.symbol

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

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Symbols.*, Types.*, Contexts.*, Flags.*, Names.*, NameOps.*, NameKinds.*
88
import StdNames.*, Denotations.*, Phases.*, SymDenotations.*
99
import NameKinds.DefaultGetterName
1010
import util.Spans.*
11-
import scala.collection.mutable
11+
import scala.collection.{mutable, immutable}
1212
import ast.*
1313
import MegaPhase.*
1414
import config.Printers.{checks, noPrinter, capt}
@@ -368,6 +368,52 @@ object RefChecks {
368368
&& atPhase(typerPhase):
369369
loop(member.info.paramInfoss, other.info.paramInfoss)
370370

371+
/** A map of all occurrences of `into` in a member type.
372+
* Key: number of parameter carrying `into` annotation(s)
373+
* Value: A list of all depths of into annotations, where each
374+
* function arrow increases the depth.
375+
* Example:
376+
* def foo(x: into A, y: => [X] => into (x: X) => into B): C
377+
* produces the map
378+
* (0 -> List(0), 1 -> List(1, 2))
379+
*/
380+
type IntoOccurrenceMap = immutable.Map[Int, List[Int]]
381+
382+
def intoOccurrences(tp: Type): IntoOccurrenceMap =
383+
384+
def traverseInfo(depth: Int, tp: Type): List[Int] = tp match
385+
case AnnotatedType(tp, annot) if annot.symbol == defn.IntoParamAnnot =>
386+
depth :: traverseInfo(depth, tp)
387+
case AppliedType(tycon, arg :: Nil) if tycon.typeSymbol == defn.RepeatedParamClass =>
388+
traverseInfo(depth, arg)
389+
case defn.FunctionOf(_, resType, _) =>
390+
traverseInfo(depth + 1, resType)
391+
case RefinedType(parent, rname, mt: MethodOrPoly) =>
392+
traverseInfo(depth, mt)
393+
case tp: MethodOrPoly =>
394+
traverseInfo(depth + 1, tp.resType)
395+
case tp: ExprType =>
396+
traverseInfo(depth, tp.resType)
397+
case _ =>
398+
Nil
399+
400+
def traverseParams(n: Int, formals: List[Type], acc: IntoOccurrenceMap): IntoOccurrenceMap =
401+
if formals.isEmpty then acc
402+
else
403+
val occs = traverseInfo(0, formals.head)
404+
traverseParams(n + 1, formals.tail, if occs.isEmpty then acc else acc + (n -> occs))
405+
406+
def traverse(n: Int, tp: Type, acc: IntoOccurrenceMap): IntoOccurrenceMap = tp match
407+
case tp: PolyType =>
408+
traverse(n, tp.resType, acc)
409+
case tp: MethodType =>
410+
traverse(n + tp.paramInfos.length, tp.resType, traverseParams(n, tp.paramInfos, acc))
411+
case _ =>
412+
acc
413+
414+
traverse(0, tp, immutable.Map.empty)
415+
end intoOccurrences
416+
371417
val checker =
372418
if makeOverridingPairsChecker == null then OverridingPairsChecker(clazz, self)
373419
else makeOverridingPairsChecker(clazz, self)
@@ -572,6 +618,8 @@ object RefChecks {
572618
overrideError(i"needs to be declared with @targetName(${"\""}${other.targetName}${"\""}) so that external names match")
573619
else
574620
overrideError("cannot have a @targetName annotation since external names would be different")
621+
else if intoOccurrences(memberTp(self)) != intoOccurrences(otherTp(self)) then
622+
overrideError("has different occurrences of `into` modifiers", compareTypes = true)
575623
else if other.is(ParamAccessor) && !isInheritedAccessor(member, other) then // (1.12)
576624
report.errorOrMigrationWarning(
577625
em"cannot override val parameter ${other.showLocated}",
@@ -1002,9 +1050,9 @@ object RefChecks {
10021050
end checkNoPrivateOverrides
10031051

10041052
def checkVolatile(sym: Symbol)(using Context): Unit =
1005-
if sym.isVolatile && !sym.is(Mutable) then
1053+
if sym.isVolatile && !sym.is(Mutable) then
10061054
report.warning(VolatileOnVal(), sym.srcPos)
1007-
1055+
10081056
/** Check that unary method definition do not receive parameters.
10091057
* They can only receive inferred parameters such as type parameters and implicit parameters.
10101058
*/

compiler/test/dotty/tools/vulpix/TestConfiguration.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ object TestConfiguration {
6666
val yCheckOptions = Array("-Ycheck:all")
6767

6868
val commonOptions = Array("-indent") ++ checkOptions ++ noCheckOptions ++ yCheckOptions
69+
val noYcheckCommonOptions = Array("-indent") ++ checkOptions ++ noCheckOptions
6970
val defaultOptions = TestFlags(basicClasspath, commonOptions)
71+
val noYcheckOptions = TestFlags(basicClasspath, noYcheckCommonOptions)
7072
val unindentOptions = TestFlags(basicClasspath, Array("-no-indent") ++ checkOptions ++ noCheckOptions ++ yCheckOptions)
7173
val withCompilerOptions =
7274
defaultOptions.withClasspath(withCompilerClasspath).withRunClasspath(withCompilerClasspath)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package scala.annotation.internal
2+
import annotation.experimental
3+
4+
/** An annotation on (part of) a parameter type that allows implicit conversions
5+
* for its arguments. The `into` modifier on parameter types in Scala 3 is
6+
* mapped to this annotation. We can also install a more generally accessible
7+
* alias so that Scala 2 libraries can use the feature.
8+
*/
9+
@experimental
10+
class into() extends annotation.StaticAnnotation
11+
12+
@experimental
13+
class $into() extends annotation.StaticAnnotation
14+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- [E120] Naming Error: tests/neg-custom-args/convertible.scala:15:6 ---------------------------------------------------
2+
15 |trait C[X] extends A[X]: // error
3+
| ^
4+
| Name clash between defined and inherited member:
5+
| def f(x: X): Unit in trait A at line 10 and
6+
| override def f(x: into X): Unit in trait C at line 16
7+
| have the same type after erasure.
8+
|
9+
| Consider adding a @targetName annotation to one of the conflicting definitions
10+
| for disambiguation.
11+
-- [E120] Naming Error: tests/neg-custom-args/convertible.scala:18:6 ---------------------------------------------------
12+
18 |class D[X] extends B[X], C[X] // error
13+
| ^
14+
| Name clash between inherited members:
15+
| override def f(x: X): Unit in trait B at line 13 and
16+
| override def f(x: into X): Unit in trait C at line 16
17+
| have the same type after erasure.
18+
|
19+
| Consider adding a @targetName annotation to one of the conflicting definitions
20+
| for disambiguation.
21+
there was 1 feature warning; re-run with -feature for details

tests/neg/into-override.check

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- [E164] Declaration Error: tests/neg/into-override.scala:16:15 -------------------------------------------------------
2+
16 | override def f(x: into X) = super.f(x) // error
3+
| ^
4+
| error overriding method f in trait A of type (x: X): Unit;
5+
| method f of type (x: into X): Unit has different occurrences of `into` modifiers
6+
|
7+
| longer explanation available when compiling with `-explain`
8+
-- [E164] Declaration Error: tests/neg/into-override.scala:18:6 --------------------------------------------------------
9+
18 |class D[X] extends B[X], C[X] // error
10+
| ^
11+
| error overriding method f in trait B of type (x: X): Unit;
12+
| method f in trait C of type (x: into X): Unit has different occurrences of `into` modifiers
13+
|
14+
| longer explanation available when compiling with `-explain`
15+
-- [E164] Declaration Error: tests/neg/into-override.scala:21:15 -------------------------------------------------------
16+
21 | override def f(x: X) = super.f(x) // error
17+
| ^
18+
| error overriding method f in trait C of type (x: into X): Unit;
19+
| method f of type (x: X): Unit has different occurrences of `into` modifiers
20+
|
21+
| longer explanation available when compiling with `-explain`

tests/neg/into-override.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//> using options -Xfatal-warnings
2+
3+
import language.experimental.into
4+
5+
class Text(val str: String)
6+
7+
given Conversion[String, Text] = Text(_)
8+
9+
trait A[X]:
10+
def f(x: X): Unit = ()
11+
12+
trait B[X] extends A[X]:
13+
override def f(x: X) = super.f(x)
14+
15+
trait C[X] extends A[X]:
16+
override def f(x: into X) = super.f(x) // error
17+
18+
class D[X] extends B[X], C[X] // error
19+
20+
trait E[X] extends C[X]:
21+
override def f(x: X) = super.f(x) // error
22+
23+
def f = new D[Text].f("abc")

tests/neg/into-syntax.check

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
-- [E040] Syntax Error: tests/neg/into-syntax.scala:7:22 ---------------------------------------------------------------
2+
7 | def f1(x: List[into Int]) = () // error // error
3+
| ^^^
4+
| ',' or ']' expected, but identifier found
5+
-- Error: tests/neg/into-syntax.scala:11:20 ----------------------------------------------------------------------------
6+
11 | def f4(x: into Int*) = () // error
7+
| ^
8+
| `*` cannot directly follow `into` parameter
9+
| the `into` parameter needs to be put in parentheses
10+
-- [E040] Syntax Error: tests/neg/into-syntax.scala:21:23 --------------------------------------------------------------
11+
21 | def f11(x: ((y: into Int) => into Int => Int)*) = () // error // error
12+
| ^^^
13+
| ')' expected, but identifier found
14+
-- Error: tests/neg/into-syntax.scala:24:14 ----------------------------------------------------------------------------
15+
24 | def f14(x: (into Int) => Int) = () // error
16+
| ^^^^
17+
| no `into` modifier allowed here
18+
-- Error: tests/neg/into-syntax.scala:25:14 ----------------------------------------------------------------------------
19+
25 | def f15(x: (into Int, into Int)) = () // error // error
20+
| ^^^^
21+
| no `into` modifier allowed here
22+
-- Error: tests/neg/into-syntax.scala:25:24 ----------------------------------------------------------------------------
23+
25 | def f15(x: (into Int, into Int)) = () // error // error
24+
| ^^^^
25+
| no `into` modifier allowed here
26+
-- Error: tests/neg/into-syntax.scala:26:14 ----------------------------------------------------------------------------
27+
26 | def f16(x: (into Int, into Int) => Int) = () // error // error
28+
| ^^^^
29+
| no `into` modifier allowed here
30+
-- Error: tests/neg/into-syntax.scala:26:24 ----------------------------------------------------------------------------
31+
26 | def f16(x: (into Int, into Int) => Int) = () // error // error
32+
| ^^^^
33+
| no `into` modifier allowed here
34+
-- [E040] Syntax Error: tests/neg/into-syntax.scala:27:27 --------------------------------------------------------------
35+
27 | def f17(x: into (y: into Int, z: into Int) => into Int) = () // error // error // error
36+
| ^^^
37+
| ')' expected, but identifier found
38+
-- [E019] Syntax Error: tests/neg/into-syntax.scala:27:44 --------------------------------------------------------------
39+
27 | def f17(x: into (y: into Int, z: into Int) => into Int) = () // error // error // error
40+
| ^
41+
| Missing return type
42+
|
43+
| longer explanation available when compiling with `-explain`
44+
-- [E006] Not Found Error: tests/neg/into-syntax.scala:7:17 ------------------------------------------------------------
45+
7 | def f1(x: List[into Int]) = () // error // error
46+
| ^^^^
47+
| Not found: type into - did you mean into.type?
48+
|
49+
| longer explanation available when compiling with `-explain`
50+
-- [E006] Not Found Error: tests/neg/into-syntax.scala:21:18 -----------------------------------------------------------
51+
21 | def f11(x: ((y: into Int) => into Int => Int)*) = () // error // error
52+
| ^^^^
53+
| Not found: type into - did you mean into.type?
54+
|
55+
| longer explanation available when compiling with `-explain`
56+
-- [E006] Not Found Error: tests/neg/into-syntax.scala:27:22 -----------------------------------------------------------
57+
27 | def f17(x: into (y: into Int, z: into Int) => into Int) = () // error // error // error
58+
| ^^^^
59+
| Not found: type into - did you mean into.type?
60+
|
61+
| longer explanation available when compiling with `-explain`

tests/neg/into-syntax.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//> using options -feature
2+
3+
import language.experimental.into
4+
5+
6+
object x1:
7+
def f1(x: List[into Int]) = () // error // error
8+
object x3:
9+
def f3(x: ((into Int))) = () // ok
10+
object x4:
11+
def f4(x: into Int*) = () // error
12+
object x5:
13+
def f5(x: ((into Int))*) = () // ok
14+
15+
object x6:
16+
def f6(x: (into Int)*) = () // ok
17+
def f7(x: (Int => into Int)*) = () // ok
18+
def f8(x: (Int => (into Int))*) = () // ok
19+
def f9(x: (y: Int) => into Int) = () // ok
20+
def f10(x: ((y: Int) => into Int)*) = () // ok
21+
def f11(x: ((y: into Int) => into Int => Int)*) = () // error // error
22+
23+
object x7:
24+
def f14(x: (into Int) => Int) = () // error
25+
def f15(x: (into Int, into Int)) = () // error // error
26+
def f16(x: (into Int, into Int) => Int) = () // error // error
27+
def f17(x: into (y: into Int, z: into Int) => into Int) = () // error // error // error

tests/pos/into-class.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import language.experimental.into
2+
3+
class Text(str: String)
4+
5+
case class C(x: into Text)
6+
7+
case class D(x: Text)
8+
9+
given Conversion[String, Text] = Text(_)
10+
11+
def Test =
12+
val c = C("a")
13+
val d = new C("b")
14+
val e = c.copy()
15+
val f = c.copy(x = "d")

0 commit comments

Comments
 (0)