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 some opaque type aliases in match type patterns #20913

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
14 changes: 13 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5309,12 +5309,14 @@ object Types extends TypeUtils {

enum MatchTypeCaseError:
case Alias(sym: Symbol)
case OpaqueAlias(sym: Symbol)
case RefiningBounds(name: TypeName)
case StructuralType(name: TypeName)
case UnaccountedTypeParam(name: TypeName)

def explanation(using Context) = this match
case Alias(sym) => i"a type alias `${sym.name}`"
case OpaqueAlias(sym) => i"an opaque type alias `${sym.name}` in the scope where its alias is known"
case RefiningBounds(name) => i"an abstract type member `$name` with bounds that need verification"
case StructuralType(name) => i"an abstract type member `$name` that does not refine a member in its parent"
case UnaccountedTypeParam(name) => i"an unaccounted type parameter `$name`"
Expand Down Expand Up @@ -5419,7 +5421,17 @@ object Types extends TypeUtils {
else
tycon.info match
case _: RealTypeBounds =>
recAbstractTypeConstructor(pat)
val tsym = tycon.typeSymbol
if tsym.isOpaqueAlias
&& ctx.owner.isContainedIn(tsym.owner) // (1) opaque alias is known here, and
&& !ctx.owner.isAbstractOrAliasType // (2) we are not in an embedded type
// without (2), one cannot define a match type referring to an opaque type in the
// scope of that opaque type. But that should be OK, we should simply not be
// able to normalize such types in terms in the same scope.
then
MatchTypeCaseError.OpaqueAlias(tsym)
else
recAbstractTypeConstructor(pat)
case TypeAlias(tl @ HKTypeLambda(onlyParam :: Nil, resType: RefinedType)) =>
/* Unlike for eta-expanded classes, the typer does not automatically
* dealias poly type aliases to refined types. So we have to give them
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotc/neg-best-effort-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ illegal-match-types.scala
i13780-1.scala
i20317a.scala
i11226.scala
opaque-matches.scala

# semantic db generation fails in the first compilation
i1642.scala
Expand Down
7 changes: 7 additions & 0 deletions tests/neg/named-tuples-strawman-2.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- [E191] Type Error: tests/neg/named-tuples-strawman-2.scala:28:24 ----------------------------------------------------
28 | inline def toTuple: DropNames[NT] = x.asInstanceOf // error
| ^^^^^^^^^^^^^
| The match type contains an illegal case:
| case NamedTupleOps.NamedTuple[_, x] => x
| The pattern contains an opaque type alias `NamedTuple` in the scope where its alias is known.
| (this error can be ignored for now with `-source:3.3`)
29 changes: 29 additions & 0 deletions tests/neg/named-tuples-strawman-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import compiletime.*
import compiletime.ops.int.*
import compiletime.ops.boolean.!

object NamedTupleDecomposition:
import NamedTupleOps.*

/** The names of the named tuple type `NT` */
type Names[NT <: AnyNamedTuple] <: Tuple = NT match
case NamedTuple[n, _] => n

/** The value types of the named tuple type `NT` */
type DropNames[NT <: AnyNamedTuple] <: Tuple = NT match
case NamedTuple[_, x] => x

object NamedTupleOps:

opaque type AnyNamedTuple = Any

opaque type NamedTuple[N <: Tuple, +X <: Tuple] >: X <: AnyNamedTuple = X

export NamedTupleDecomposition.*

object NamedTuple:
def apply[N <: Tuple, X <: Tuple](x: X): NamedTuple[N, X] = x

extension [NT <: AnyNamedTuple](x: NT)
inline def toTuple: DropNames[NT] = x.asInstanceOf // error
inline def names: Names[NT] = constValueTuple[Names[NT]]
15 changes: 15 additions & 0 deletions tests/neg/opaque-matches.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- [E191] Type Error: tests/neg/opaque-matches.scala:4:18 --------------------------------------------------------------
4 | type Fst[X] = X match // error
| ^
| The match type contains an illegal case:
| case NT.NT[a, b] => a
| The pattern contains a type alias `NT`.
| (this error can be ignored for now with `-source:3.3`)
5 | case NT[a, b] => a
-- [E191] Type Error: tests/neg/opaque-matches.scala:10:30 -------------------------------------------------------------
10 | inline def snd[NT](nt: NT): NTDecomposition.Snd[NT] = // error
| ^^^^^^^^^^^^^^^^^^^^^^^
| The match type contains an illegal case:
| case NT.NT[a, b] => b
| The pattern contains an opaque type alias `NT` in the scope where its alias is known.
| (this error can be ignored for now with `-source:3.3`)
19 changes: 19 additions & 0 deletions tests/neg/opaque-matches.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
object NT:
opaque type NT[A, B] = B

type Fst[X] = X match // error
case NT[a, b] => a

inline def fst[NT](nt: NT): Fst[NT] =
???

inline def snd[NT](nt: NT): NTDecomposition.Snd[NT] = // error
???

object NTDecomposition:

type Snd[X] = X match
case NT.NT[a, b] => b



41 changes: 25 additions & 16 deletions tests/run/named-tuples-strawman-2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,11 @@ object TupleOps:
for i <- toInclude.indices do
arr(i) = xs.productElement(toInclude(i).asInstanceOf[Int]).asInstanceOf[Object]
Tuple.fromArray(arr).asInstanceOf[Filter[X, P]]
end TupleOps

object NamedTupleDecomposition:
import NamedTupleOps.*
import TupleOps.ConcatDistinct

/** The names of the named tuple type `NT` */
type Names[NT <: AnyNamedTuple] <: Tuple = NT match
Expand All @@ -131,21 +133,38 @@ object NamedTupleDecomposition:
type DropNames[NT <: AnyNamedTuple] <: Tuple = NT match
case NamedTuple[_, x] => x

extension [N1 <: Tuple, V1 <: Tuple](nt1: NamedTuple[N1, V1])
inline def updateWith[N2 <: Tuple, V2 <: Tuple](nt2: NamedTuple[N2, V2])
: UpdateWith[NamedTuple[N1, V1], NamedTuple[N2, V2]] =
val names = constValueTuple[ConcatDistinct[N1, N2]].toArray
val names2 = constValueTuple[N2].toArray
val values1 = NamedTupleOps.toTuple(nt1)
val values2 = nt2.toTuple
val values = new Array[Object](names.length)
values1.toArray.copyToArray(values)
for i <- 0 until values2.size do
val idx = names.indexOf(names2(i))
values(idx) = values2.productElement(i).asInstanceOf[Object]
Tuple.fromArray(values).asInstanceOf[UpdateWith[NamedTuple[N1, V1], NamedTuple[N2, V2]]]

end NamedTupleDecomposition

object NamedTupleOps:
import TupleOps.*

opaque type AnyNamedTuple = Any

opaque type NamedTuple[N <: Tuple, +X <: Tuple] >: X <: AnyNamedTuple = X

export NamedTupleDecomposition.*
export NamedTupleDecomposition.{Names, DropNames}

object NamedTuple:
def apply[N <: Tuple, X <: Tuple](x: X): NamedTuple[N, X] = x

extension [NT <: AnyNamedTuple](x: NT)
inline def toTuple: DropNames[NT] = x.asInstanceOf
inline def names: Names[NT] = constValueTuple[Names[NT]]
extension [N <: Tuple, V <: Tuple](x: NamedTuple[N, V])
inline def toTuple: V = x.asInstanceOf

inline def names: N = constValueTuple[N]

/** Internal use only: Merge names and value components of two named tuple to
* impement `UpdateWith`.
Expand All @@ -169,18 +188,8 @@ object NamedTupleOps:
type UpdateWith[NT1 <: AnyNamedTuple, NT2 <: AnyNamedTuple] =
Merge[ConcatDistinct[Names[NT1], Names[NT2]], DropNames[NT1], Names[NT2], DropNames[NT2]]

extension [NT1 <: AnyNamedTuple](nt1: NT1)
inline def updateWith[NT2 <: AnyNamedTuple](nt2: NT2): UpdateWith[NT1, NT2] =
val names = constValueTuple[ConcatDistinct[Names[NT1], Names[NT2]]].toArray
val names2 = constValueTuple[Names[NT2]].toArray
val values1 = nt1.toTuple
val values2 = nt2.toTuple
val values = new Array[Object](names.length)
values1.toArray.copyToArray(values)
for i <- 0 until values2.size do
val idx = names.indexOf(names2(i))
values(idx) = values2.productElement(i).asInstanceOf[Object]
Tuple.fromArray(values).asInstanceOf[UpdateWith[NT1, NT2]]
export NamedTupleDecomposition.updateWith
end NamedTupleOps

@main def Test =
import TupleOps.*
Expand Down
Loading