Skip to content

Detect opaque aliases in inline val types #13857

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

Merged
merged 2 commits into from
Nov 2, 2021
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
29 changes: 18 additions & 11 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1363,42 +1363,49 @@ object Types {
case Atoms.Unknown => Atoms.Unknown
case _ => Atoms.Unknown

private def dealias1(keep: AnnotatedType => Context ?=> Boolean)(using Context): Type = this match {
private def dealias1(keep: AnnotatedType => Context ?=> Boolean, keepOpaques: Boolean)(using Context): Type = this match {
case tp: TypeRef =>
if (tp.symbol.isClass) tp
else tp.info match {
case TypeAlias(alias) => alias.dealias1(keep)
case TypeAlias(alias) if !(keepOpaques && tp.symbol.is(Opaque)) =>
alias.dealias1(keep, keepOpaques)
case _ => tp
}
case app @ AppliedType(tycon, _) =>
val tycon1 = tycon.dealias1(keep)
if (tycon1 ne tycon) app.superType.dealias1(keep)
val tycon1 = tycon.dealias1(keep, keepOpaques)
if (tycon1 ne tycon) app.superType.dealias1(keep, keepOpaques)
else this
case tp: TypeVar =>
val tp1 = tp.instanceOpt
if (tp1.exists) tp1.dealias1(keep) else tp
if (tp1.exists) tp1.dealias1(keep, keepOpaques) else tp
case tp: AnnotatedType =>
val tp1 = tp.parent.dealias1(keep)
val tp1 = tp.parent.dealias1(keep, keepOpaques)
if keep(tp) then tp.derivedAnnotatedType(tp1, tp.annot) else tp1
case tp: LazyRef =>
tp.ref.dealias1(keep)
tp.ref.dealias1(keep, keepOpaques)
case _ => this
}

/** Follow aliases and dereferences LazyRefs, annotated types and instantiated
* TypeVars until type is no longer alias type, annotated type, LazyRef,
* or instantiated type variable.
*/
final def dealias(using Context): Type = dealias1(keepNever)
final def dealias(using Context): Type = dealias1(keepNever, keepOpaques = false)

/** Follow aliases and dereferences LazyRefs and instantiated TypeVars until type
* is no longer alias type, LazyRef, or instantiated type variable.
* Goes through annotated types and rewraps annotations on the result.
*/
final def dealiasKeepAnnots(using Context): Type = dealias1(keepAlways)
final def dealiasKeepAnnots(using Context): Type = dealias1(keepAlways, keepOpaques = false)

/** Like `dealiasKeepAnnots`, but keeps only refining annotations */
final def dealiasKeepRefiningAnnots(using Context): Type = dealias1(keepIfRefining)
final def dealiasKeepRefiningAnnots(using Context): Type = dealias1(keepIfRefining, keepOpaques = false)

/** Follow non-opaque aliases and dereferences LazyRefs, annotated types and instantiated
* TypeVars until type is no longer alias type, annotated type, LazyRef,
* or instantiated type variable.
*/
final def dealiasKeepOpaques(using Context): Type = dealias1(keepNever, keepOpaques = true)

/** Approximate this type with a type that does not contain skolem types. */
final def deskolemized(using Context): Type =
Expand Down Expand Up @@ -1426,7 +1433,7 @@ object Types {
def tryNormalize(using Context): Type = NoType

private def widenDealias1(keep: AnnotatedType => Context ?=> Boolean)(using Context): Type = {
val res = this.widen.dealias1(keep)
val res = this.widen.dealias1(keep, keepOpaques = false)
if (res eq this) res else res.widenDealias1(keep)
}

Expand Down
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/InlineVals.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ class InlineVals extends MiniPhase:
then
val rhs = tree.rhs
val tpt = tree.tpt
tpt.tpe.widenTermRefExpr.dealias.normalized match
tpt.tpe.widenTermRefExpr.dealiasKeepOpaques.normalized match
case tp: ConstantType =>
if !isPureExpr(rhs) then
val details = if enclosingInlineds.isEmpty then "" else em"but was: $rhs"
report.error(s"inline value must be pure$details", rhs.srcPos)
case tp =>
if tp.derivesFrom(defn.UnitClass) then
if tp.typeSymbol.is(Opaque) then
report.error(em"The type of an `inline val` cannot be an opaque type.\n\nTo inline, consider using `inline def` instead", rhs)
else if tp.derivesFrom(defn.UnitClass) then
report.error(em"`inline val` of type `Unit` is not supported.\n\nTo inline a `Unit` consider using `inline def`", rhs)
else if tp.derivesFrom(defn.StringClass) || defn.ScalaValueClasses().exists(tp.derivesFrom(_)) then
val pos = if tpt.span.isZeroExtent then rhs.srcPos else tpt.srcPos
Expand Down
11 changes: 11 additions & 0 deletions tests/neg/i13851.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
opaque type One = 1
inline val One: One = 1 // error

opaque type Max = Int.MaxValue.type
inline val Max: Max = Int.MaxValue // error

inline val MaxValue: Int.MaxValue.type = Int.MaxValue

opaque type Two = 2
type Bis = Two
inline val Two: Bis = 2 // error
10 changes: 10 additions & 0 deletions tests/neg/i13851b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
object Num {
opaque type One = 1
inline val One: One = 1 // error

opaque type Two = 2
inline def Two: Two = 2
}

def test1 = Num.One
def test2 = Num.Two
6 changes: 6 additions & 0 deletions tests/neg/i13852.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
inline val `1`: 1 = 1
def get1: 1 = `1`

opaque type One = 1
inline val One: One = 1 // error
def getOne: One = One