Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow local term references in staged types #16362

Merged
merged 2 commits into from
Jan 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,10 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
/** If the type refers to a locally defined symbol (either directly, or in a pickled type),
* check that its staging level matches the current level.
* - Static types and term are allowed at any level.
* - If a type reference is used a higher level, then it is inconsistent. Will attempt to heal before failing.
* - If a term reference is used a different level, then it is inconsistent.
* - If a type reference is used a higher level, then it is inconsistent.
* Will attempt to heal before failing.
* - If a term reference is used a higher level, then it is inconsistent.
* It cannot be healed because the term will not exist in any future stage.
*
* If `T` is a reference to a type at the wrong level, try to heal it by replacing it with
* a type tag of type `quoted.Type[T]`.
Expand Down Expand Up @@ -206,6 +208,8 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
tryHeal(prefix.symbol, tp, pos)
case _ =>
mapOver(tp)
case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level > levelOf(tp.symbol) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we check level > here but level != in healTypeOfTerm?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In healTypeOfTerm we are looking for terms that are used in other stages. Terms must be used at the same stage they are defined, hence the level !=.

For types the rules are a bit different. For types in general it is always safe to use the type in a previous stage and one needs a type Type[T] for generic types if it is used in a future stage. This is in general where the > comes in for type. But in this particular case, we have a type reference to a local variable that will not exist in the next stage and we cannot create a valid Type[T] for it. Therefore it emmits an error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation, I think it'd be helpful to add something like that as a comment in the code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added to the comment.

levelError(tp.symbol, tp, pos)
case tp: ThisType if level != -1 && level != levelOf(tp.cls) =>
levelError(tp.cls, tp, pos)
case tp: AnnotatedType =>
Expand Down
6 changes: 6 additions & 0 deletions tests/neg-macros/i15917.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import scala.quoted.*

def m(using Quotes): Expr[Option[_]] =
val s = 3
type st = s.type
'{ Some(${ Expr(s) }: st) } // error
35 changes: 35 additions & 0 deletions tests/neg-macros/i16355a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//> using scala "3.2.1"
import scala.quoted.Expr
import scala.quoted.Type
import scala.quoted.quotes
import scala.quoted.Quotes

object macros {

inline transparent def mkNames[A]: List[Any] = ${ mkNamesImpl[A] }

def mkNamesImpl[A: Type](using Quotes): Expr[List[Any]] = {
import quotes.reflect._

val fieldNames = TypeRepr.of[A].typeSymbol.declaredFields.map(_.name)

val types = fieldNames
.map { f =>
val t1 = ConstantType(StringConstant(f))
t1.asType match {
case '[t1Type] => TypeRepr.of[(t1Type, "aa")]
}
}
.reduceLeft[TypeRepr](OrType(_, _))

types.asType match {
case '[ttt] =>
Expr.ofList[ttt](
fieldNames.map { v =>
Expr[(v.type, "aa")](v -> "aa").asExprOf[ttt] // error
}
)
}
}

}
4 changes: 4 additions & 0 deletions tests/neg-macros/i16355b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import scala.quoted._
def test(v: String)(using Quotes): Any =
Type.of : Type[v.type] // error
Type.of[v.type] // error