Skip to content

Commit a019a4e

Browse files
committed
Enforce restrictions on typelevel methods
Typelevel methods are transparent methods with toplevel matches. We now track this property with a flag. To be done: merge with the Macro flag. Typelevel methods - cannot be eta-expanded - cannot override anything Also, fixes the "missing args" error message, which previously claimed that `()` was missing even for nonempty argument lists.
1 parent e96c20f commit a019a4e

14 files changed

+60
-26
lines changed

compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
433433
val Flag_METHOD: Flags = Flags.Method.bits
434434
val ExcludedForwarderFlags: Flags = {
435435
Flags.Specialized | Flags.Lifted | Flags.Protected | Flags.JavaStatic |
436-
Flags.Bridge | Flags.VBridge | Flags.Private | Flags.Macro
436+
Flags.Bridge | Flags.Private | Flags.TypeLevel | Flags.Macro
437437
}.bits
438438

439439
def isQualifierSafeToElide(qual: Tree): Boolean = tpd.isIdempotentExpr(qual)

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,8 @@ object Flags {
351351
/** A bridge method. Set by Erasure */
352352
final val Bridge = termFlag(34, "<bridge>")
353353

354-
/** Symbol is a Java varargs bridge */ // (needed?)
355-
final val VBridge = termFlag(35, "<vbridge>") // TODO remove
354+
/** Symbol is a typelevel method, cannot be used in normal code */
355+
final val TypeLevel = termFlag(35, "<type-level>")
356356

357357
/** Symbol is a method which should be marked ACC_SYNCHRONIZED */
358358
final val Synchronized = termFlag(36, "<synchronized>")

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ object SymDenotations {
600600

601601
/** Is this a denotation of a stable term (or an arbitrary type)? */
602602
final def isStable(implicit ctx: Context) =
603-
isType || is(Stable) || !(is(UnstableValue) || info.isInstanceOf[ExprType])
603+
isType || is(Stable) || !is(UnstableValue) && !info.isInstanceOf[ExprType]
604604

605605
/** Is this a denotation of a class that does not have - either direct or inherited -
606606
* initaliazion code?

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ Standard-Section: "ASTs" TopLevelStat*
182182
LAZY
183183
OVERRIDE
184184
TRANSPARENT
185+
TYPELEVEL // transparent method containing toplevel matches (TODO: merge with MACRO)
185186
MACRO // transparent method containing toplevel splices
186187
STATIC // mapped to static Java member
187188
OBJECT // an object or its class
@@ -295,8 +296,8 @@ object TastyFormat {
295296
final val IMPLICIT = 13
296297
final val LAZY = 14
297298
final val OVERRIDE = 15
298-
299-
final val TRANSPARENT = 17
299+
final val TRANSPARENT = 16
300+
final val TYPELEVEL = 17
300301
final val STATIC = 18
301302
final val OBJECT = 19
302303
final val TRAIT = 20
@@ -473,6 +474,7 @@ object TastyFormat {
473474
| LAZY
474475
| OVERRIDE
475476
| TRANSPARENT
477+
| TYPELEVEL
476478
| MACRO
477479
| STATIC
478480
| OBJECT
@@ -529,6 +531,7 @@ object TastyFormat {
529531
case LAZY => "LAZY"
530532
case OVERRIDE => "OVERRIDE"
531533
case TRANSPARENT => "TRANSPARENT"
534+
case TYPELEVEL => "TYPELEVEL"
532535
case MACRO => "MACRO"
533536
case STATIC => "STATIC"
534537
case OBJECT => "OBJECT"

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

+1
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,7 @@ class TreePickler(pickler: TastyPickler) {
601601
if (flags is Case) writeByte(CASE)
602602
if (flags is Override) writeByte(OVERRIDE)
603603
if (flags is Transparent) writeByte(TRANSPARENT)
604+
if (flags is TypeLevel) writeByte(TYPELEVEL)
604605
if (flags is Macro) writeByte(MACRO)
605606
if (flags is JavaStatic) writeByte(STATIC)
606607
if (flags is Module) writeByte(OBJECT)

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

+1
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,7 @@ class TreeUnpickler(reader: TastyReader,
585585
case LAZY => addFlag(Lazy)
586586
case OVERRIDE => addFlag(Override)
587587
case TRANSPARENT => addFlag(Transparent)
588+
case TYPELEVEL => addFlag(TypeLevel)
588589
case MACRO => addFlag(Macro)
589590
case STATIC => addFlag(JavaStatic)
590591
case OBJECT => addFlag(Module)

compiler/src/dotty/tools/dotc/core/unpickleScala2/PickleBuffer.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ object PickleBuffer {
232232
EXPANDEDNAME -> Scala2ExpandedName,
233233
IMPLCLASS -> (Scala2PreSuper, ImplClass),
234234
SPECIALIZED -> Specialized,
235-
VBRIDGE -> VBridge,
235+
VBRIDGE -> EmptyFlags,
236236
VARARGS -> JavaVarargs,
237237
ENUM -> Enum)
238238

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

+3-8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import NameKinds.{ClassifiedNameKind, InlineAccessorName, UniqueInlineName, Tran
1919
import ProtoTypes.selectionProto
2020
import SymDenotations.SymDenotation
2121
import Annotations._
22+
import PrepareTransparent.foreachTopLevelMatch
2223
import transform.{ExplicitOuter, AccessProxies}
2324
import Inferencing.fullyDefinedType
2425
import config.Printers.inlining
@@ -641,14 +642,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
641642
import reducer._
642643

643644
/** Mark all toplevel matches with the `TopLevelMatch` attachment */
644-
def prepare(tree: untpd.Tree): Unit = tree match {
645-
case tree: untpd.Match =>
646-
tree.pushAttachment(TopLevelMatch, ())
647-
tree.cases.foreach(prepare)
648-
case tree: untpd.Block =>
649-
prepare(tree.expr)
650-
case _ =>
651-
}
645+
def prepare(tree: untpd.Tree): Unit =
646+
foreachTopLevelMatch(tree, _.pushAttachment(TopLevelMatch, ()))
652647

653648
override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = {
654649
tpe match {

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

+13
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ object PrepareTransparent {
4343
if (!defn.ScalaPredefModule.moduleClass.derivesFrom(tree.symbol.maybeOwner))
4444
methPart(tree).putAttachment(ContextualImplicit, ())
4545

46+
def foreachTopLevelMatch(tree: untpd.Tree, op: untpd.Tree => Unit) = {
47+
def recur(tree: untpd.Tree): Unit = tree match {
48+
case tree: untpd.Match =>
49+
op(tree)
50+
tree.cases.foreach(recur)
51+
case tree: untpd.Block =>
52+
recur(tree.expr)
53+
case _ =>
54+
}
55+
recur(tree)
56+
}
57+
4658
class InlineAccessors extends AccessProxies {
4759

4860
/** If an inline accessor name wraps a unique inline name, this is taken as indication
@@ -241,6 +253,7 @@ object PrepareTransparent {
241253
val typedBody =
242254
if (ctx.reporter.hasErrors) rawBody
243255
else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody)
256+
foreachTopLevelMatch(originalBody, _ => inlined.setFlag(TypeLevel))
244257
val inlineableBody = addReferences(inlined, originalBody, typedBody)
245258
inlining.println(i"Body to inline for $inlined: $inlineableBody")
246259
inlineableBody

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ object RefChecks {
143143
* 1.8.1 M's type is a subtype of O's type, or
144144
* 1.8.2 M is of type []S, O is of type ()T and S <: T, or
145145
* 1.8.3 M is of type ()S, O is of type []T and S <: T, or
146-
* 1.9 M must not be a Dotty macro def
146+
* 1.9 M must not be a typelevel def or a Dotty macro def
147147
* 1.10. If M is a 2.x macro def, O cannot be deferred unless there's a concrete method overriding O.
148148
* 1.11. If M is not a macro def, O cannot be a macro def.
149149
* 2. Check that only abstract classes have deferred members
@@ -376,6 +376,8 @@ object RefChecks {
376376
overrideError("may not override a non-lazy value")
377377
} else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) {
378378
overrideError("must be declared lazy to override a lazy value")
379+
} else if (member.is(TypeLevel)) { // (1.9)
380+
overrideError("is a type-level method, may not override anything")
379381
} else if (member.is(Macro, butNot = Scala2x)) { // (1.9)
380382
overrideError("is a macro, may not override anything")
381383
} else if (other.is(Deferred) && member.is(Scala2Macro) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.10)

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -2128,7 +2128,9 @@ class Typer extends Namer
21282128
def readaptSimplified(tree: Tree)(implicit ctx: Context) = readapt(simplify(tree, pt, locked))
21292129

21302130
def missingArgs(mt: MethodType) = {
2131-
ctx.error(MissingEmptyArgumentList(methPart(tree).symbol), tree.pos)
2131+
val meth = methPart(tree).symbol
2132+
if (mt.paramNames.length == 0) ctx.error(MissingEmptyArgumentList(meth), tree.pos)
2133+
else ctx.error(em"missing arguments for $meth", tree.pos)
21322134
tree.withType(mt.resultType)
21332135
}
21342136

@@ -2338,10 +2340,12 @@ class Typer extends Namer
23382340

23392341
// Reasons NOT to eta expand:
23402342
// - we reference a constructor
2343+
// - we reference a typelevel method
23412344
// - we are in a pattern
23422345
// - the current tree is a synthetic apply which is not expandable (eta-expasion would simply undo that)
23432346
if (arity >= 0 &&
23442347
!tree.symbol.isConstructor &&
2348+
!tree.symbol.is(TypeLevel) &&
23452349
!ctx.mode.is(Mode.Pattern) &&
23462350
!(isSyntheticApply(tree) && !isExpandableApply))
23472351
simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked)

tests/neg/typelevel-noeta.scala

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
object Test {
3+
def anyValue[T]: T = ???
4+
5+
transparent def test(x: Int) = x match {
6+
case _: Byte =>
7+
case _: Char =>
8+
}
9+
10+
transparent def test2() = 1 match {
11+
case _: Byte =>
12+
case _: Char =>
13+
}
14+
15+
val x: Any = test // error
16+
17+
test // error
18+
19+
val x2: Any = test2 // error
20+
21+
test2 // error
22+
}

tests/neg/typelevel-defaultValue.scala renamed to tests/neg/typelevel-nomatch.scala

+2
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ object Test {
88
}
99

1010
test[String]
11+
12+
test
1113
}

tests/neg/typelevel.scala

-9
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,4 @@ object Test {
5555
class Deco2(val as: HList) extends java.lang.Cloneable with java.lang.Comparable[Deco2] {
5656
transparent def ++ (bs: HList) = concat(as, bs)
5757
}
58-
59-
def anyValue[T]: T = ???
60-
61-
transparent def test[T] = anyValue[T] match {
62-
case _: Byte =>
63-
case _: Char =>
64-
}
65-
66-
test[String]
6758
}

0 commit comments

Comments
 (0)