Skip to content

Commit 8cf1b49

Browse files
authored
allow _ as the fallback 'wildcard' case (#56)
* allow `_` as the fallback 'wildcard' case * bump version * make coverage happy * test on 1.10.0-rc1
1 parent 003c9d4 commit 8cf1b49

File tree

5 files changed

+73
-14
lines changed

5 files changed

+73
-14
lines changed

.github/workflows/CI.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ jobs:
1414
- '1.6'
1515
- '1.8'
1616
- '1.9'
17+
- '1.10.0-rc1'
1718
- 'nightly'
1819
os:
1920
- ubuntu-latest

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "SumTypes"
22
uuid = "8e1ec7a9-0e02-4297-b0fe-6433085c89f2"
33
authors = ["MasonProtter <[email protected]>"]
4-
version = "0.5.0"
4+
version = "0.5.1"
55

66
[deps]
77
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@ The `@cases` macro still falls far short of a full on pattern matching system, l
165165

166166
### Defining many repetitive cases simultaneously
167167

168-
`@cases` does not allow for fallback branches, and it also does not allow one to write inexhaustive cases. To avoid making some code overly verbose and repetitive, we instead provide syntax for defining many cases in one line:
169-
168+
Generally, it's good to explicitly handle all cases of a sum type, but sometimes you just want one set of behaviour for
169+
a large set of cases. One option, is 'collections' of cases like so:
170170
``` julia
171171
@sum_type Re begin
172172
Empty
@@ -209,6 +209,16 @@ count_classes(r::Re, c=0) = @cases r begin
209209
end;
210210
```
211211

212+
SumTypes also lets you use `_` as a case predicate that accepts anything, but this only works in the final position, and
213+
does not allow destructuring:
214+
215+
``` julia
216+
isEmpty(x::Re) = @cases x begin
217+
Empty => true
218+
_ => false
219+
end
220+
```
221+
212222
<!-- </details> -->
213223

214224

src/cases.jl

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,24 @@
1919
end
2020

2121
macro cases(to_match, block)
22-
@assert block.head == :block
22+
esc(_cases(to_match, block))
23+
end
24+
25+
function _cases(to_match, block)
26+
block.head == :block || error("The second argument to @cases must be a code block")
2327
lnns = filter(block.args) do arg
2428
arg isa LineNumberNode
2529
end
2630
# Base.remove_linenums!(block)
2731

2832
stmts = []
29-
foreach(block.args) do arg
33+
wildcard_stmt = Ref{Any}()
34+
35+
foreach(enumerate(block.args)) do (i, arg)
3036
if arg isa LineNumberNode
3137
return nothing
3238
end
33-
arg.head == :call && arg.args[1] == :(=>) || throw(error("Malformed case $arg"))
39+
arg.head == :call && arg.args[1] == :(=>) || error("Malformed case $arg")
3440
lhs = arg.args[2]
3541
rhs = arg.args[3]
3642
if isexpr(lhs, :call) # arg.args[2] isa Expr && arg.args[2].head == :call
@@ -43,7 +49,15 @@ macro cases(to_match, block)
4349
iscall = false
4450
end
4551
if variant isa Symbol
46-
push!(stmts, (;variant=variant, rhs=rhs, fieldnames=fieldnames, iscall=iscall))
52+
if variant === :_
53+
if i == length(block.args)
54+
wildcard_stmt[] = rhs
55+
else
56+
error("The wildcard variant _ can only be used as the last option to @cases")
57+
end
58+
else
59+
push!(stmts, (;variant=variant, rhs=rhs, fieldnames=fieldnames, iscall=iscall))
60+
end
4761
elseif isexpr(variant, :vect)
4862
for subvariant variant.args
4963
if !(subvariant isa Symbol)
@@ -90,13 +104,20 @@ macro cases(to_match, block)
90104
end
91105
# push!(to_push, :($matching_error()))
92106
deparameterize(x) = x isa Symbol ? x : x isa Expr && x.head == :curly ? x.args[1] : throw("Invalid variant name $x")
107+
if isdefined(wildcard_stmt, :x)
108+
push!(to_push, wildcard_stmt[])
109+
exhaustive_stmt = nothing
110+
else
111+
exhaustive_stmt = :($assert_exhaustive(Val{$tags($Typ)},
112+
Val{$(Expr(:tuple, QuoteNode.(deparameterize.(variants))...))}))
113+
end
93114
quote
94115
let $data = $to_match
95116
$Typ = $typeof($data)
96117
$check_sum_type($Typ)
97-
$assert_exhaustive(Val{$tags($Typ)}, Val{$(Expr(:tuple, QuoteNode.(deparameterize.(variants))...))})
118+
$exhaustive_stmt
98119
$unwrapped = $unwrap($data)
99120
$ex
100121
end
101-
end |> esc
122+
end
102123
end

test/runtests.jl

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,24 @@ Base.getproperty(f::Result, s::Symbol) = error("Don't do that!")
9999
@test_throws Exception SumTypes._sum_type(
100100
:(Blah{T}), :(begin
101101
foo{U}(::U)
102-
end ))
102+
end ))
103+
@test_throws Exception SumTypes._cases(:(Fruit), :(1))
104+
@test_throws Exception SumTypes._cases(:(Fruit), :([1, 2, 3]))
105+
@test_throws Exception SumTypes._cases(:(Fruit), :(begin
106+
true
107+
_ => false
108+
banana => false
109+
end))
110+
@test_throws Exception SumTypes._cases(:(Fruit), :(begin
111+
apple => true
112+
_ => false
113+
banana => false
114+
end))
115+
@test_throws Exception SumTypes._cases(:(Fruit), :(begin
116+
apple => true
117+
[banana, orange()] => false
118+
_ => false
119+
end))
103120

104121
let x = Left([1]), y = Left([1.0]), z = Right([1])
105122
@test x == y
@@ -320,10 +337,10 @@ end
320337
Empty
321338
Class(::UInt8)
322339
Rep(::Re)
323-
Alt(::Re, ::Re)
324-
Cat(::Re, ::Re)
340+
Alt(::Re, ::Re)
341+
Cat(::Re, ::Re)
325342
Diff(::Re, ::Re)
326-
And(::Re, ::Re)
343+
And(::Re, ::Re)
327344
end;
328345

329346
count_classes(r::Re, c=0) = @cases r begin
@@ -332,14 +349,24 @@ count_classes(r::Re, c=0) = @cases r begin
332349
Rep(x) => c + count_classes(x)
333350
[Alt, Cat, Diff, And](x, y) => c + count_classes(x) + count_classes(y)
334351
end;
335-
352+
353+
is_empty(r::Re) = @cases r begin
354+
Empty => true
355+
_ => false
356+
end
357+
336358
@testset "Collection of variants" begin
337359
@test foo(A(1, 1)) == 2
338360
@test foo(B(1, 1.5)) == 2.5
339361
@test foo(C("3")) == 3
340362
@test foo(D(:a => 4)) == 4
341363

342364
@test count_classes(And(Alt(Rep(Class(0x1)), And(Class(0x1), Empty)), Class(0x0))) == 3
365+
366+
@test is_empty(Empty)
367+
for r (Class(1), Rep(Class(1)), Alt(Empty, Empty), Cat(Empty, Empty), Diff(Empty, Empty), And(Empty, Empty))
368+
@test !is_empty(r)
369+
end
343370
end
344371

345372
end

0 commit comments

Comments
 (0)