Skip to content

Commit 213f8cb

Browse files
committed
Document new condition for structural type subtyping
1 parent 3f33526 commit 213f8cb

File tree

3 files changed

+38
-8
lines changed

3 files changed

+38
-8
lines changed

Diff for: compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1740,7 +1740,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
17401740
// A relaxed version of isSubType, which compares method types
17411741
// under the standard arrow rule which is contravarient in the parameter types,
17421742
// but under the condition that signatures might have to match (see sigsOK)
1743-
// This releaxed version is needed to correctly compare dependent function types.
1743+
// This relaxed version is needed to correctly compare dependent function types.
17441744
// See pos/i12211.scala.
17451745
def isSubInfo(info1: Type, info2: Type, symInfo: Type): Boolean =
17461746
info2 match

Diff for: docs/docs/reference/changed-features/structural-types-spec.md

+35-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ RefineStatSeq ::= RefineStat {semi RefineStat}
1212
RefineStat ::= ‘val’ VarDcl | ‘def’ DefDcl | ‘type’ {nl} TypeDcl
1313
```
1414

15-
## Implementation of structural types
15+
## Implementation of Structural Types
1616

1717
The standard library defines a universal marker trait
1818
[`scala.Selectable`](https://github.com/lampepfl/dotty/blob/master/library/src/scala/Selectable.scala):
@@ -82,21 +82,49 @@ Note that `v`'s static type does not necessarily have to conform to `Selectable`
8282
conversion that can turn `v` into a `Selectable`, and the selection methods could also be available as
8383
[extension methods](../contextual/extension-methods.md).
8484

85-
## Limitations of structural types
85+
## Limitations of Structural Types
8686

8787
- Dependent methods cannot be called via structural call.
88-
- Overloaded methods cannot be called via structural call.
89-
- Refinements do not handle polymorphic methods.
9088

91-
## Differences with Scala 2 structural types
89+
- Refinements may not introduce overloads: If a refinement specifies the signature
90+
of a method `m`, and `m` is also defined in the parent type of the refinement, then
91+
the new signature must properly override the existing one.
92+
93+
- Subtyping of structural refinements must preserve erased parameter types: Assume
94+
we want to prove `S <: T { def m(x: A): B }`. Then, as usual, `S` must have a member method `m` that can take an argument of type `A`. Furthermore, if `m` is not a member of `T` (i.e. the refinement is structural), an additional condition applies. In this case, the member _definition_ `m` of `S` will have a parameter
95+
with type `A'` say. The additional condition is that the erasure of `A'` and `A` is the same. Here is an example:
96+
97+
```scala
98+
class Sink[A] { def put(x: A): Unit = {} }
99+
val a = Sink[String]()
100+
val b: { def put(x: String): Unit } = a // error
101+
b.put("abc") // looks for a method with a `String` parameter
102+
```
103+
The second to last line is not well-typed, since the erasure of the parameter type of `put` in class `Sink` is `Object`, but the erasure of the refinement of the type of `b` is `String`. This additional condition is necessary, since we will have to resort to reflection to call a structural member like `put` in the type of `b` above. The condition ensures that the statically known parameter types of the refinement correspond up to erasure to the parameter types of the selected call target at runtime.
104+
105+
The usual reflection dispatch algorithms need to know exact erased parameter types. For instance, if the example above would typecheck, the call
106+
`b.put("abc")` on the last line would look for a method `put` in the runtime type of `b` that takes a `String` parameter. But the `put` method is the one from class `Sink`, which takes an `Object` parameter. Hence the call would fail at runtime with a `NoSuchMethodException`.
107+
108+
One might hope for a "more intelligent" reflexive dispatch algorithm that does not require exact parameter type matching. Unfortunately, this can always run into ambiguities. For instance, continuing the example above, we might introduce a new subclass `Sink1` of `Sink` and change the definition of `a` as follows:
109+
110+
```scala
111+
class Sink1[A] extends Sink[A] { def put(x: "123") = ??? }
112+
val a: Sink[String] = Sink1[String]()
113+
```
114+
115+
Now there are two `put` methods in the runtime type of `b` with erased parameter
116+
types `Object` and `String`, respectively. Yet dynamic dispatch still needs to go
117+
to the first `put` method, even though the second looks like a better match.
118+
119+
## Differences with Scala 2 Structural Types
92120

93121
- Scala 2 supports structural types by means of Java reflection. Unlike
94122
Scala 3, structural calls do not rely on a mechanism such as
95123
`Selectable`, and reflection cannot be avoided.
96-
- In Scala 2, structural calls to overloaded methods are possible.
124+
- In Scala 2, refinements can introduce overloads.
97125
- In Scala 2, mutable `var`s are allowed in refinements. In Scala 3,
98126
they are no longer allowed.
99-
127+
- Scala 2 does not impose the "same-erasure" restriction on subtyping of structural types. It allows some calls to fail at runtime instead.
100128

101129
## Context
102130

Diff for: tests/neg/i12211.scala

+2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ val x: { def f(x: Any): String } = new { def f(x: Any) = x.toString }
55
val y: { def f(x: String): String } = x // error: type mismatch (different signatures)
66

77
class Sink[A] { def put(x: A): Unit = {} }
8+
class Sink1[A] extends Sink[A] { def put(x: "123") = ??? }
89

910
@main def Test =
1011
println(y.f("abc"))
1112
val a = new Sink[String]
1213
val b: { def put(x: String): Unit } = a // error: type mismatch (different signatures)
1314
b.put("") // gave a NoSuchMethodException: Sink.put(java.lang.String)
15+
val c: Sink[String] = Sink1[String]()

0 commit comments

Comments
 (0)