Skip to content

Commit 85c11d1

Browse files
committed
Disallow variances in type lambdas
Since variances are associated conceptually with higher-kinded type variables, it makes no sense to write them on type lambdas. I believe it's better to disallow writing variances there because it will only need to variance-bike-shedding otherwise.
1 parent a78a41b commit 85c11d1

File tree

9 files changed

+66
-63
lines changed

9 files changed

+66
-63
lines changed

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2845,27 +2845,33 @@ object Parsers {
28452845
* HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (id [HkTypePamClause] | ‘_’) TypeBounds
28462846
*/
28472847
def typeParamClause(ownerKind: ParamOwner.Value): List[TypeDef] = inBrackets {
2848+
2849+
def variance(vflag: FlagSet): FlagSet =
2850+
if ownerKind == ParamOwner.Def || ownerKind == ParamOwner.TypeParam then
2851+
syntaxError(i"no `+/-` variance annotation allowed here")
2852+
in.nextToken()
2853+
EmptyFlags
2854+
else
2855+
in.nextToken()
2856+
vflag
2857+
28482858
def typeParam(): TypeDef = {
28492859
val isAbstractOwner = ownerKind == ParamOwner.Type || ownerKind == ParamOwner.TypeParam
28502860
val start = in.offset
28512861
val mods =
2852-
annotsAsMods() | {
2853-
if (ownerKind == ParamOwner.Class) Param | PrivateLocal
2854-
else Param
2855-
} | {
2856-
if (ownerKind == ParamOwner.Def) EmptyFlags
2857-
else if (isIdent(nme.raw.PLUS)) { in.nextToken(); Covariant }
2858-
else if (isIdent(nme.raw.MINUS)) { in.nextToken(); Contravariant }
2859-
else EmptyFlags
2860-
}
2862+
annotsAsMods()
2863+
| (if (ownerKind == ParamOwner.Class) Param | PrivateLocal else Param)
2864+
| (if isIdent(nme.raw.PLUS) then variance(Covariant)
2865+
else if isIdent(nme.raw.MINUS) then variance(Contravariant)
2866+
else EmptyFlags)
28612867
atSpan(start, nameStart) {
28622868
val name =
28632869
if (isAbstractOwner && in.token == USCORE) {
28642870
in.nextToken()
28652871
WildcardParamName.fresh().toTypeName
28662872
}
28672873
else ident().toTypeName
2868-
val hkparams = typeParamClauseOpt(ParamOwner.TypeParam)
2874+
val hkparams = typeParamClauseOpt(ParamOwner.Type)
28692875
val bounds = if (isAbstractOwner) typeBounds() else typeParamBounds(name)
28702876
TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
28712877
}

docs/docs/internals/syntax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
295295
TypTypeParam ::= {Annotation} id [HkTypeParamClause] SubtypeBounds
296296
297297
HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’
298-
HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] | ‘_’)
298+
HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (id [HkTypeParamClause] | ‘_’)
299299
SubtypeBounds
300300
301301
ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’]

docs/docs/reference/new-types/type-lambdas-spec.md

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ title: "Type Lambdas - More Details"
66
## Syntax
77

88
```
9-
Type ::= ... | HkTypeParamClause ‘=>>’ Type
10-
HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’
11-
HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] | ‘_’) TypeBounds
9+
Type ::= ... | TypeParamClause ‘=>>’ Type
10+
TypeParamClause ::= ‘[’ TypeParam {‘,’ TypeParam} ‘]’
11+
TypeParam ::= {Annotation} (id [HkTypeParamClause] | ‘_’) TypeBounds
1212
TypeBounds ::= [‘>:’ Type] [‘<:’ Type]
1313
```
1414

@@ -18,44 +18,28 @@ A type lambda such as `[X] =>> F[X]` defines a function from types to types. The
1818
If a parameter is bounded, as in `[X >: L <: H] =>> F[X]` it is checked that arguments to the parameters conform to the bounds `L` and `H`.
1919
Only the upper bound `H` can be F-bounded, i.e. `X` can appear in it.
2020

21-
A variance annotation on a parameter indicates a subtyping relationship on type instances. For instance, given
22-
```scala
23-
type TL1 = [+A] =>> F[A]
24-
type TL2 = [-A] =>> F[A]
25-
```
26-
and two types `S <: T`, we have
27-
```scala
28-
TL1[S] <: TL1[T]
29-
TL2[T] <: TL2[S]
30-
```
31-
It is checked that variance annotations on parameters of type lambdas are respected by the parameter occurrences on the type lambda's body.
32-
33-
**Note** No requirements hold for the variances of occurrences of type variables in their bounds. It is an open question whether we need to impose additional requirements here
34-
(`scalac` doesn't check variances in bounds either).
35-
3621
## Subtyping Rules
3722

3823
Assume two type lambdas
3924
```scala
40-
type TL1 = [v1 X >: L1 <: U1] =>> R1
41-
type TL2 = [v2 X >: L2 <: U2] =>> R2
25+
type TL1 = [X >: L1 <: U1] =>> R1
26+
type TL2 = [X >: L2 <: U2] =>> R2
4227
```
43-
where `v1` and `v2` are optional variance annotations: `+`, `-`, or absent.
4428
Then `TL1 <: TL2`, if
4529

4630
- the type interval `L2..U2` is contained in the type interval `L1..U1` (i.e.
4731
`L1 <: L2` and `U2 <: U1`),
48-
- either `v2` is absent or `v1 = v2`
4932
- `R1 <: R2`
5033

5134
Here we have relied on alpha renaming to bring match the two bound types `X`.
5235

5336
A partially applied type constructor such as `List` is assumed to be equivalent to
54-
its eta expansion. I.e, `List = [+X] =>> List[X]`. This allows type constructors
55-
to be compared with type lambdas.
37+
its eta expansion. I.e, `List = [X] =>> List[X]`. This allows type constructors to be compared with type lambdas.
5638

5739
## Relationship with Parameterized Type Definitions
5840

41+
type F[X] <: List[F[X]]
42+
5943
A parameterized type definition
6044
```scala
6145
type T[X] = R
@@ -64,6 +48,17 @@ is regarded as a shorthand for an unparameterized definition with a type lambda
6448
```scala
6549
type T = [X] =>> R
6650
```
51+
If the a type definition carries `+` or `-` variance annotations,
52+
it is checked that the variance annotations are satisfied by the type lambda.
53+
For instance,
54+
```scala
55+
type F2[A, +B] = A => B
56+
```scala
57+
expands to
58+
```scala
59+
type F2 = [A, B] =>> A => B
60+
```
61+
and at the same time it is checked that the parameter `B` appears covariantly in `A => B`.
6762

6863
A parameterized abstract type
6964
```scala
@@ -75,15 +70,15 @@ type T >: ([X] =>> L) <: ([X] =>> U)
7570
```
7671
However, if `L` is `Nothing` it is not parameterized, since `Nothing` is treated as a bottom type for all kinds. For instance,
7772
```scala
78-
type T[-X] <: X => ()
73+
type T[X] <: X => X
7974
```
8075
is expanded to
8176
```scala
82-
type T >: Nothing <: ([-X] =>> X => ())
77+
type T >: Nothing <: ([X] =>> X => X)
8378
```
8479
instead of
8580
```scala
86-
type T >: ([X] =>> Nothing) <: ([-X] =>> X => ())
81+
type T >: ([X] =>> Nothing) <: ([X] =>> X => X)
8782
```
8883

8984
The same expansions apply to type parameters. E.g.
@@ -94,6 +89,20 @@ is treated as a shorthand for
9489
```scala
9590
[F >: Nothing <: [X] =>> Coll[X]]
9691
```
92+
Abstract types and opaque type aliases remember the variances they were created with. So the type
93+
```scala
94+
def F2[-A, +B]
95+
```
96+
is known to be covariant in `A` and contravariant in `B` and can be instantiated only
97+
with types that satisfy these constraints. Likewise
98+
```scala
99+
opaque type O[X] = List[X]
100+
```
101+
`O` is known to be invariant (and not covariant, as its right hand side would suggest). On the other hand, a transparent alias
102+
```scala
103+
type O2[X] = List[X]
104+
```
105+
would be treated as covariant, `X` is used covariantly on its right-hand side.
97106

98107
**Note**: The decision to treat `Nothing` as universal bottom type is provisional, and might be changed afer further discussion.
99108

docs/docs/reference/new-types/type-lambdas.md

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,12 @@ A _type lambda_ lets one express a higher-kinded type directly, without
77
a type definition.
88

99
```scala
10-
[+X, Y] =>> Map[Y, X]
10+
[X, Y] =>> Map[Y, X]
1111
```
1212

13-
For instance, the type above defines a binary type constructor, with a
14-
covariant parameter `X` and a non-variant parameter `Y`. The
13+
For instance, the type above defines a binary type constructor, which
1514
constructor maps arguments `X` and `Y` to `Map[Y, X]`. Type parameters
16-
of type lambdas can have variances and bounds. A parameterized type
17-
definition or declaration such as
18-
19-
```scala
20-
type T[X] = (X, X)
21-
```
22-
23-
is a shorthand for a plain type definition with a type-lambda as its
24-
right-hand side:
25-
26-
```scala
27-
type T = [X] =>> (X, X)
28-
```
15+
of type lambdas can have bounds but they cannot carry `+` or `-` variance
16+
annotations.
2917

3018
[More details](./type-lambdas-spec.md)

tests/neg/i6475.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object Foo1 { type T[+A] = (A, Int) }
2+
object Foo2 { type T[+A] = [+B] =>> (A, B) } // error no `+/-` variance annotation allowed here
3+
object Foo3 { type T[+A] = [+B] =>> [C] =>> (A, B) } // error
4+
object Foo4 { type T = [+A] =>> [+B] =>> [C] =>> (A, B) } // error // error

tests/neg/type-lambdas-posttyper.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ object Test extends App {
1616
type TL3[+X] = Ref[X] // error: covariant type parameter X occurs in nonvariant position in Test.Ref[X]
1717
type TL4[-X] = X => X // error: contravariant type parameter X occurs in covariant position in X => X
1818

19-
def f[F <: [+X] =>> Any](x: F[String]): F[Any] = x
19+
def f[F[+X]](x: F[String]): F[Any] = x
2020

2121
val sref = new Ref[String]("abc")
2222
val aref: Ref[Any] = f[TL3](sref)
@@ -27,15 +27,15 @@ object Test extends App {
2727
type Neg2[-X] >: X // error
2828
type Neg3[-X] <: X // error
2929

30-
type Neg4 = [-X] =>> X // error
30+
type Neg4[-X] = X // error
3131
type Neg5[-X] >: X <: Any // error
3232
type Neg6[-X] <: X // error
3333

3434
type Pos1[+X] = Ref[X] // error
3535
type Pos2[+X] >: Ref[X] // error
3636
type Pos3[+X] <: Ref[X] // error
3737

38-
type Pos4 = [+X] =>> Ref[X] // error
38+
type Pos4[+X] = Ref[X] // error
3939
type Pos5[+X] >: Ref[X] <: Any // error
4040
type Pos6[+X] <: Ref[X] // error
4141
}

tests/pos-custom-args/erased/i7868.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ object Coproduct {
2525
def upCast[A, B](a: A) with (erased evidence: (A <:< B) ): B = a.asInstanceOf[B]
2626

2727
def from[Set, Value, Index <: Int](value: Value) with (erased at: At[Set, Value, Index]) : ValueOf[Index] ?=> Coproduct[Set, Value, Index] = {
28-
Coproduct[Set, Value, Index](upCast(value: Value).with(at.cast.liftCo[[+X] =>> Value & X]), valueOf[Index])
28+
Coproduct[Set, Value, Index](upCast(value: Value).with(at.cast.liftCo[[X] =>> Value & X]), valueOf[Index])
2929
}
3030

3131
}

tests/pos/i6475.scala

Lines changed: 0 additions & 4 deletions
This file was deleted.

tests/pos/reference/type-lambdas.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package typeLambdas
22

33
object Test {
44

5-
type T = [+X, Y] =>> Map[Y, X]
5+
type T[+X, Y] = Map[Y, X]
66

77
type CTL = [X] =>> [Y] =>> (X, Y)
88
type T3 = CTL[Int][String]

0 commit comments

Comments
 (0)