Skip to content

Commit d53fa51

Browse files
committed
Add proposed specification of provably disjoint.
1 parent afbd365 commit d53fa51

File tree

1 file changed

+117
-2
lines changed

1 file changed

+117
-2
lines changed

content/match-types-spec.md

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,123 @@ At the top level, `variance = 1` and `scrutIsWidenedAbstract = false`.
283283

284284
#### Disjointness
285285

286-
This proposal does not affect the check for provably disjoint types in match types.
287-
If a case is legal and does not match, the existing disjointness check is used to decide whether we can move on to the next case.
286+
This proposal initially did not include a discussion of disjointness.
287+
After initial review, it became apparent that it should also provide a specification for the "provably disjoint" test involved in match type reduction.
288+
Additional study revealed that, while *specifiable*, the current algorithm is very ad hoc and severely lacks desirable properties such as preservation along subtyping (see towards the end of this section).
289+
290+
Therefore, this proposal now also includes a proposed specification for "provably disjoint".
291+
To the best of our knowledge, it is strictly stronger than what is currently implemented, with one exception.
292+
293+
The current implementation can prove that `{ type A = Int }` is provably disjoint from `{ type A = Boolean }`.
294+
However, it is not able to prove disjointness between any of the following:
295+
296+
* `{ type A = Int }` and `{ type A = Boolean; type B = String }` (adding another type member)
297+
* `{ type A = Int; type B = Int }` and `{ type B = String; type A = String }` (switching the order of type members)
298+
* `{ type A = Int }` and class `C` that defines a member `type A = String`.
299+
300+
Therefore, we drop the very ad hoc case of one-to-one type member refinements.
301+
302+
On to the specification.
303+
304+
A scrutinee `X` is *provably disjoint* from a pattern `P` iff it is probably disjoint from the type `P'` obtained by replacing every type capture in `P` by a wildcard type argument with the same bounds.
305+
306+
We note `X ⋔ Y` to say that `X` and `Y` are provably disjoint.
307+
Intuitively, that notion is based on the following properties of the Scala language:
308+
309+
* Single inheritance of classes
310+
* Final classes cannot be extended
311+
* Sealed traits have a known set of direct children
312+
* Constant types with distinct values are nonintersecting
313+
* Singleton paths to distinct `enum` case values are nonintersecting
314+
315+
However, a precise definition of provably-disjoint is complicated and requires some helpers.
316+
We start with the notion of "simple types", which are a minimal subset of Scala types that capture the concepts mentioned above.
317+
318+
A "simple type" is one of:
319+
320+
* `Nothing`
321+
* `AnyKind`
322+
* `p.C[...Xi]` a possibly parameterized class type, where `p` and `...Xi` are arbitrary types (not just simple types)
323+
* `c` a literal type
324+
* `p.C.x` where `C` is an `enum` class and `x` is one of its value `case`s
325+
* `X₁ & X₂` where `X₁` and `X₂` are both simple types
326+
* `X₁ | X₂` where `X₁` and `X₂` are both simple types
327+
328+
We define `⌈X⌉` a function from a full Scala type to a simple type.
329+
Intuitively, it returns the "smallest" simple type that is a supertype of `X`.
330+
It is defined as follows:
331+
332+
* `⌈X⌉ = X` if `X` is a simple type
333+
* `⌈X⌉ = ⌈U⌉` if `X` is a stable type but not a simple type and its underlying type is `U`
334+
* `⌈X⌉ = ⌈H⌉` if `X` is a non-class type designator with upper bound `H`
335+
* `⌈X⌉ = AnyKind` if `X` is a type lambda
336+
* `⌈X⌉ = ⌈Y⌉` if `X` is a match type that reduces to `Y`
337+
* `⌈X⌉ = ⌈H⌉` if `X` is a match type that does not reduce and `H` is its upper bound
338+
* `⌈X₁ & X₂⌉ = ⌈X₁⌉ & ⌈X₂⌉`
339+
* `⌈X₁ | X₂⌉ = ⌈X₁⌉ | ⌈X₂⌉`
340+
341+
The following properties hold about `⌈X⌉` (we have paper proofs for those):
342+
343+
* `X <: ⌈X⌉` for all type `X`.
344+
* If `S <: T`, and the subtyping derivation does not use the "lower-bound rule" of `<:` anywhere, then `⌈S⌉ <: ⌈T⌉`.
345+
346+
The "lower-bound rule" states that `S <: T` if `T = q.X `and `q.X` is a non-class type designator and `S <: L` where `L` is the lower bound of the underlying type definition of `q.X`".
347+
That rule is known to break transitivy of subtyping in Scala already.
348+
349+
Second, we define the relation `` on *classes* (including traits and hidden classes of objects) as:
350+
351+
* `C ⋔ D` if `C ∉ baseClasses(D)` and `D` is `final`
352+
* `C ⋔ D` if `D ∉ baseClasses(C)` and `C` is `final`
353+
* `C ⋔ D` if there exists `class`es `C' ∈ baseClasses(C)` and `D' ∈ baseClasses(D)` such that `C' ∉ baseClasses(D')` and `D' ∉ baseClasses(C')`.
354+
* `C ⋔ D` if `C` is `sealed` without anonymous child and `Ci ⋔ D` for all direct children `Ci` of `C`
355+
* `C ⋔ D` if `D` is `sealed` without anonymous child and `C ⋔ Di` for all direct children `Di` of `D`
356+
357+
We can now define `` for *types*.
358+
359+
For arbitrary types `X` and `Y`, we define `X ⋔ Y` as `⌈X⌉ ⋔ ⌈Y⌉`.
360+
361+
Two simple types `S` and `T` are provably disjoint if there is a finite derivation tree for `S ⋔ T` using the following rules.
362+
Most rules go by pair, which makes the whole relation symmetric:
363+
364+
* `Nothing` is disjoint from everything (including itself):
365+
* `Nothing ⋔ T`
366+
* `S ⋔ Nothing`
367+
* A union type is disjoint from another type if both of its parts are disjoint from that type:
368+
* `S ⋔ T1 | T2` if `S ⋔ T1` and `S ⋔ T2`
369+
* `S1 | S2 ⋔ T` if `S1 ⋔ T` and `S2 ⋔ T`
370+
* An intersection type is disjoint from another type if at least one of its parts is disjoint from that type:
371+
* `S ⋔ T1 & T2` if `S ⋔ T1` or `S ⋔ T2`
372+
* `S1 & S2 ⋔ T` if `S1 ⋔ T` or `S1 ⋔ T`
373+
* An `enum` value case is disjoint from any other `enum` value case (identified by either not being in the same `enum` class, or having a different name):
374+
* `p.C.x ⋔ q.D.y` if `C != D` or `x != y` (recall: these are enum value cases)
375+
* Two literal types are disjoint if they are different:
376+
* `c ⋔ d` if `c != d` (where they are literal types)
377+
* An `enum` value case is always disjoint from a literal type:
378+
* `c ⋔ q.D.y`
379+
* `p.C.x ⋔ d`
380+
* An `enum` value case or a constant is disjoint from a class type if it does not extend that class (because it's essentially final):
381+
* `p.C.x ⋔ q.D[...Ti]` if `baseType(p.C.x, D)` is not defined
382+
* `p.C[...Si] ⋔ q.D.y` if `baseType(q.D.y, C)` is not defined
383+
* `c ⋔ q.D[...Ti]` if `baseType(c, D)` is not defined
384+
* `p.C[...Si] ⋔ d` if `baseType(d, C)` is not defined
385+
* Two class types are disjoint if the classes themselves are disjoint, or if there exist a common super type with conflicting type arguments.
386+
* `p.C[...Si] ⋔ q.D[...Ti]` if `C ⋔ D`
387+
* `p.C[...Si] ⋔ q.D[...Ti]` if there exists a class `E` such that `baseType(p.C[...Si], E) = a.E[...Ai]` and `baseType(q.D[...Ti], E) = b.E[...Bi]` and there exists a pair `(Ai, Bi)` such that
388+
* `Ai ⋔ Bi` and it is in invariant position, or
389+
* `Ai ⋔ Bi` and it is in covariant position and there exists a field of that type parameter in `E`
390+
391+
It is worth noting that this definition disregards prefixes entirely.
392+
`p.C` and `q.C` are never provably disjoint, even if `p` could be proven disjoint from `q`.
393+
394+
We have a proof sketch of the following property for ``:
395+
396+
* If `S <: T` and `T ⋔ U`, then `S ⋔ U`.
397+
398+
This is a very desirable property, which does not hold at all for the current implementation of match types.
399+
It means that if we make the scrutinee of a match type more precise (a subtype) through substitution, and the match type previously reduced, then the match type will still reduce to the same case.
400+
401+
Note: if `` were a "true" disjointness relationship, and not a *provably*-disjoint relationship, that property would trivially hold based on elementary set theoretic properties.
402+
It would amount to proving that if `S ⊆ T` and `T ⋂ U = ∅`, then `S ⋂ U = ∅`.
288403

289404
### Compatibility
290405

0 commit comments

Comments
 (0)