Skip to content

Commit 0583132

Browse files
committed
Remove unsound simplification of negated record types in positive positions
1 parent f990dfe commit 0583132

File tree

11 files changed

+281
-168
lines changed

11 files changed

+281
-168
lines changed

shared/src/main/scala/mlscript/TyperHelpers.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -646,9 +646,6 @@ abstract class TyperHelpers { Typer: Typer =>
646646
case ComposedType(false, l, r) => l.negNormPos(f, p) | r.negNormPos(f, p)
647647
case NegType(n) => f(n).withProv(p)
648648
case tr: TypeRef if !preserveTypeRefs && tr.canExpand => tr.expandOrCrash.negNormPos(f, p)
649-
case _: RecordType | _: FunctionType => BotType // Only valid in positive positions!
650-
// Because Top<:{x:S}|{y:T}, any record type negation neg{x:S}<:{y:T} for any y=/=x,
651-
// meaning negated records are basically bottoms.
652649
case rw => NegType(f(rw))(p)
653650
}
654651
def withProvOf(ty: SimpleType): ST = withProv(ty.prov)

shared/src/test/diff/fcp/Overloads.mls

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,33 +93,43 @@ if true then IISS else BBNN
9393
//│ res: (0 | 1 | true) -> number
9494

9595

96-
// * Note that type normalization is currently very aggressive at approximating negative non-tag types, to simplify the result:
96+
// * Note that type normalization used to be very aggressive at approximating non-tag type negations,
97+
// * to simplify the result, but this was changed as it was unsound
9798

9899
def test: ~(int -> int)
99-
//│ test: in ~(int -> int) out nothing
100+
//│ test: ~(int -> int)
100101

101-
// * Note about this known unsoundness: see test file BooleanFail.mls
102+
// * See also test file BooleanFail.mls about this previous unsoundness
103+
:e
102104
test = 42
103105
not test
104106
//│ 42
105107
//│ <: test:
106108
//│ ~(int -> int)
107-
//│ res: bool
109+
//│ ╔══[ERROR] Type mismatch in application:
110+
//│ ║ l.105: not test
111+
//│ ║ ^^^^^^^^
112+
//│ ╟── type `~(int -> int)` is not an instance of type `bool`
113+
//│ ║ l.99: def test: ~(int -> int)
114+
//│ ║ ^^^^^^^^^^^^^
115+
//│ ╟── but it flows into reference with expected type `bool`
116+
//│ ║ l.105: not test
117+
//│ ╙── ^^^^
118+
//│ res: bool | error
108119

109-
// :ds
110120
def test: ~(int -> int) & ~bool
111-
//│ test: in ~bool & ~(int -> int) out nothing
121+
//│ test: ~bool & ~(int -> int)
112122

113123
def test: ~(int -> int) & bool
114-
//│ test: in bool out nothing
124+
//│ test: bool
115125

116126
def test: ~(int -> int) & ~(bool -> bool)
117-
//│ test: in ~(nothing -> (bool | int)) out nothing
127+
//│ test: ~(nothing -> (bool | int))
118128

119129
def test: ~(int -> int | bool -> bool)
120-
//│ test: in ~(nothing -> (bool | int)) out nothing
130+
//│ test: ~(nothing -> (bool | int))
121131

122132
def test: ~(int -> int & string -> string) & ~(bool -> bool & number -> number)
123-
//│ test: in ~(nothing -> (number | string) & int -> number & nothing -> (bool | string) & nothing -> (bool | int)) out nothing
133+
//│ test: in ~(nothing -> (number | string) & int -> number & nothing -> (bool | string) & nothing -> (bool | int)) out ~(nothing -> (bool | int) & nothing -> (bool | string) & int -> number & nothing -> (number | string))
124134

125135

shared/src/test/diff/mlscript/Annoying.mls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ id (error: A) with { x = 1 } : A | { x: 'a }
190190
//│ res: A | {x: nothing}
191191

192192
def negWeird: ~(~(~(A & { x: int })))
193-
//│ negWeird: in ~(A & {x: int}) out ~A
193+
//│ negWeird: ~(A & {x: int})
194194

195195
def v = negWeird with { x = 1 }
196196
//│ v: ~A\x & {x: 1} | {x: 1} & ~{x: int}

shared/src/test/diff/mlscript/BooleanFail.mls

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,52 @@
33

44
// * The MLscript subtyping system is currently ill-formed in some corner cases.
55
// * Notably, it considers functions and classes to intersect to nothing
6-
// * and also considers positive negated function/record types equivalent to nothing.
6+
// * and also used to considers positive negated function/record types equivalent to nothing.
77

88
// * (This isn't the case in MLstruct, which has a sound subtyping lattice.)
99

1010

11-
// * Example 1
11+
12+
// * Example 1 – now fixed
13+
1214
oops = 42 : ~(int -> int)
13-
not oops
14-
//│ oops: nothing
15+
//│ oops: ~(int -> int)
1516
//│ = 42
16-
//│ res: bool
17+
18+
:e
19+
not oops
20+
//│ ╔══[ERROR] Type mismatch in application:
21+
//│ ║ l.19: not oops
22+
//│ ║ ^^^^^^^^
23+
//│ ╟── type `~(int -> int)` is not an instance of type `bool`
24+
//│ ║ l.14: oops = 42 : ~(int -> int)
25+
//│ ║ ^^^^^^^^^^^^^
26+
//│ ╟── but it flows into reference with expected type `bool`
27+
//│ ║ l.19: not oops
28+
//│ ╙── ^^^^
29+
//│ res: bool | error
1730
//│ = false
1831

1932

20-
// * OTOH, this doesn't lead to immediate unsoundness:
33+
// * This was accepted but didn't immediately lead to immediate unsoundness:
2134
def f: (~{x: int}) -> 'a
22-
f = id
23-
//│ f: in nothing -> nothing out ~{x: int} -> nothing
35+
//│ f: ~{x: int} -> nothing
2436
//│ = <missing implementation>
37+
38+
:e
39+
f = id
2540
//│ 'a -> 'a
2641
//│ <: f:
27-
//│ nothing -> nothing
42+
//│ ~{x: int} -> nothing
43+
//│ ╔══[ERROR] Type mismatch in def definition:
44+
//│ ║ l.39: f = id
45+
//│ ║ ^^^^^^
46+
//│ ╟── type `~{x: int}` does not match type `'a`
47+
//│ ║ l.34: def f: (~{x: int}) -> 'a
48+
//│ ║ ^^^^^^^^^^^
49+
//│ ╟── Note: constraint arises from type variable:
50+
//│ ║ l.34: def f: (~{x: int}) -> 'a
51+
//│ ╙── ^^
2852
//│ = [Function: id]
2953

3054
:e
@@ -33,64 +57,75 @@ f id
3357
f {}
3458
f (forall 'a. fun (x: 'a) -> x)
3559
//│ ╔══[ERROR] Type mismatch in application:
36-
//│ ║ l.31: f 0
60+
//│ ║ l.55: f 0
3761
//│ ║ ^^^
3862
//│ ╟── integer literal of type `0` does not match type `~{x: int}`
39-
//│ ║ l.31: f 0
63+
//│ ║ l.55: f 0
4064
//│ ║ ^
4165
//│ ╟── Note: constraint arises from type negation:
42-
//│ ║ l.21: def f: (~{x: int}) -> 'a
66+
//│ ║ l.34: def f: (~{x: int}) -> 'a
4367
//│ ╙── ^^^^^^^^^^^
4468
//│ res: error
4569
//│ = 0
4670
//│ ╔══[ERROR] Type mismatch in application:
47-
//│ ║ l.32: f id
71+
//│ ║ l.56: f id
4872
//│ ║ ^^^^
4973
//│ ╟── reference of type `?a -> ?a` does not match type `~{x: int}`
50-
//│ ║ l.32: f id
74+
//│ ║ l.56: f id
5175
//│ ║ ^^
5276
//│ ╟── Note: constraint arises from type negation:
53-
//│ ║ l.21: def f: (~{x: int}) -> 'a
77+
//│ ║ l.34: def f: (~{x: int}) -> 'a
5478
//│ ╙── ^^^^^^^^^^^
5579
//│ res: error
5680
//│ = [Function: id]
5781
//│ ╔══[ERROR] Type mismatch in application:
58-
//│ ║ l.33: f {}
82+
//│ ║ l.57: f {}
5983
//│ ║ ^^^^
6084
//│ ╟── record literal of type `anything` does not match type `~{x: int}`
61-
//│ ║ l.33: f {}
85+
//│ ║ l.57: f {}
6286
//│ ║ ^^
6387
//│ ╟── Note: constraint arises from type negation:
64-
//│ ║ l.21: def f: (~{x: int}) -> 'a
88+
//│ ║ l.34: def f: (~{x: int}) -> 'a
6589
//│ ╙── ^^^^^^^^^^^
6690
//│ res: error
6791
//│ = {}
6892
//│ ╔══[ERROR] Type mismatch in application:
69-
//│ ║ l.34: f (forall 'a. fun (x: 'a) -> x)
93+
//│ ║ l.58: f (forall 'a. fun (x: 'a) -> x)
7094
//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7195
//│ ╟── function of type `'a -> 'a` does not match type `~{x: int}`
72-
//│ ║ l.34: f (forall 'a. fun (x: 'a) -> x)
96+
//│ ║ l.58: f (forall 'a. fun (x: 'a) -> x)
7397
//│ ║ ^^^^^^^^^^^^^^^^
7498
//│ ╟── Note: constraint arises from type negation:
75-
//│ ║ l.21: def f: (~{x: int}) -> 'a
99+
//│ ║ l.34: def f: (~{x: int}) -> 'a
76100
//│ ╙── ^^^^^^^^^^^
77101
//│ res: error
78102
//│ = [Function (anonymous)]
79103

80104

81-
// * Example 2
105+
// * Example 2 – now fixed
82106

83107
def g(x: 'a | {f: nothing}) = x.f(0)
84108
//│ g: {f: 0 -> 'a} -> 'a
85109
//│ = [Function: g]
86110

111+
:e
87112
foo = forall 'x. fun (x: 'x) -> g(x)
88-
//│ foo: anything -> nothing
113+
//│ ╔══[ERROR] Type mismatch in application:
114+
//│ ║ l.112: foo = forall 'x. fun (x: 'x) -> g(x)
115+
//│ ║ ^^^^
116+
//│ ╟── expression of type `'x & ~{f: nothing}` does not have field 'f'
117+
//│ ╟── Note: constraint arises from field selection:
118+
//│ ║ l.107: def g(x: 'a | {f: nothing}) = x.f(0)
119+
//│ ║ ^^^
120+
//│ ╟── from type variable:
121+
//│ ║ l.107: def g(x: 'a | {f: nothing}) = x.f(0)
122+
//│ ╙── ^^
123+
//│ foo: anything -> error
89124
//│ = [Function: foo]
90125

91126
:re
92127
foo 0
93-
//│ res: nothing
128+
//│ res: error
94129
//│ Runtime error:
95130
//│ TypeError: x.f is not a function
96131

@@ -135,11 +170,11 @@ b = a
135170
//│ <: b:
136171
//│ Foo & {x: int}
137172
//│ ╔══[ERROR] Type mismatch in def definition:
138-
//│ ║ l.133: b = a
173+
//│ ║ l.168: b = a
139174
//│ ║ ^^^^^
140175
//│ ╟── expression of type `anything` is not an instance of type `int`
141176
//│ ╟── Note: constraint arises from type reference:
142-
//│ ║ l.109: def b: int -> int & Foo | {x: int} & Foo
177+
//│ ║ l.144: def b: int -> int & Foo | {x: int} & Foo
143178
//│ ╙── ^^^
144179

145180

0 commit comments

Comments
 (0)