Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bugfix: not-found or ambiguous constructor treated as var #5303

Merged
merged 3 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 35 additions & 14 deletions parser-typechecker/src/Unison/Syntax/TermParser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,11 @@ link :: (Monad m, Var v) => TermP v m
link = termLink <|> typeLink
where
typeLink = do
_ <- P.try (reserved "typeLink") -- type opens a block, gotta use something else
_ <- reserved "typeLink" -- type opens a block, gotta use something else
tok <- typeLink'
pure $ Term.typeLink (ann tok) (L.payload tok)
termLink = do
_ <- P.try (reserved "termLink")
_ <- reserved "termLink"
tok <- termLink'
pure $ Term.termLink (ann tok) (L.payload tok)

Expand Down Expand Up @@ -191,7 +191,7 @@ match = do
scrutinee <- term
_ <- optionalCloseBlock
_ <-
P.try (openBlockWith "with") <|> do
openBlockWith "with" <|> do
t <- anyToken
P.customFailure (ExpectedBlockOpen "with" t)
(_arities, cases) <- unzip <$> matchCases
Expand Down Expand Up @@ -225,7 +225,7 @@ matchCase = do
_ <- reserved "|"
guard <-
asum
[ Nothing <$ P.try (quasikeyword "otherwise"),
[ Nothing <$ quasikeyword "otherwise",
Just <$> infixAppOrBooleanOp
]
(_spanAnn, t) <- layoutBlock "->"
Expand Down Expand Up @@ -302,7 +302,7 @@ parsePattern = label "pattern" root
ctor :: CT.ConstructorType -> P v m (L.Token ConstructorReference)
ctor ct = do
-- this might be a var, so we avoid consuming it at first
tok <- P.try (P.lookAhead hqPrefixId)
tok <- P.lookAhead hqPrefixId

-- First, if:
--
Expand Down Expand Up @@ -360,7 +360,7 @@ parsePattern = label "pattern" root
| Set.null s
&& (isLower n || isIgnored n) ->
fail $ "not a constructor name: " <> show n
-- it was hash qualified and/or uppercase, and wasn't found in the env, that's a failure!
-- it was hash qualified and/or uppercase, and was either not found or ambiguous, that's a failure!
_ ->
failCommitted $
ResolutionFailures
Expand All @@ -381,14 +381,10 @@ parsePattern = label "pattern" root
]
unzipPatterns f elems = case unzip elems of (patterns, vs) -> f patterns (join vs)

effectBind0 = do
effectBind = do
tok <- ctor CT.Effect
leaves <- many leaf
_ <- reserved "->"
pure (tok, leaves)

effectBind = do
(tok, leaves) <- P.try effectBind0
(cont, vsp) <- parsePattern
pure $
let f patterns vs = (Pattern.EffectBind (ann tok <> ann cont) (L.payload tok) patterns cont, vs ++ vsp)
Expand All @@ -400,12 +396,37 @@ parsePattern = label "pattern" root

effect = do
start <- openBlockWith "{"
(inner, vs) <- effectBind <|> effectPure
end <- closeBlock

-- After the opening curly brace, we are expecting either an EffectBind or an EffectPure:
--
-- EffectBind EffectPure
--
-- { foo bar -> baz } { qux }
-- ^^^^^^^^^^^^^^ ^^^
--
-- We accomplish that as follows:
--
-- * First try EffectPure + "}"
-- * If that fails, back the parser up and try EffectBind + "}" instaed
--
-- This won't always result in the best possible error messages, but it's not exactly trivial to do better,
-- requiring more sophisticated look-ahead logic. So, this is how it works for now.
(inner, vs, end) <-
asum
[ P.try do
(inner, vs) <- effectPure
end <- closeBlock
pure (inner, vs, end),
do
(inner, vs) <- effectBind
end <- closeBlock
pure (inner, vs, end)
]

pure (Pattern.setLoc inner (ann start <> ann end), vs)

-- ex: unique type Day = Mon | Tue | ...
nullaryCtor = P.try do
nullaryCtor = do
tok <- ctor CT.Data
pure (Pattern.Constructor (ann tok) (L.payload tok) [], [])

Expand Down
24 changes: 24 additions & 0 deletions unison-src/transcripts/fix-5301.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
This transcripts demonstrates that pattern matching on a "constructor" (defined as a variable that begins with a capital
letter) that is either not found or ambiguouus fails. Previously, it would be treated as a variable binding.

```ucm
scratch/main> builtins.merge
```

```unison:error
type Foo = Bar Nat

foo : Foo -> Nat
foo = cases
Bar X -> 5
```

```unison:error
type Foo = Bar A
type A = X
type B = X

foo : Foo -> Nat
foo = cases
Bar X -> 5
```
64 changes: 64 additions & 0 deletions unison-src/transcripts/fix-5301.output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
This transcripts demonstrates that pattern matching on a "constructor" (defined as a variable that begins with a capital
letter) that is either not found or ambiguouus fails. Previously, it would be treated as a variable binding.

``` ucm
scratch/main> builtins.merge

Done.

```
``` unison
type Foo = Bar Nat

foo : Foo -> Nat
foo = cases
Bar X -> 5
```

``` ucm

Loading changes detected in scratch.u.



I couldn't resolve any of these symbols:

5 | Bar X -> 5


Symbol Suggestions

X No matches


```
``` unison
type Foo = Bar A
type A = X
type B = X

foo : Foo -> Nat
foo = cases
Bar X -> 5
```

``` ucm

Loading changes detected in scratch.u.



I couldn't resolve any of these symbols:

7 | Bar X -> 5


Symbol Suggestions

X A.X
B.X


```
2 changes: 1 addition & 1 deletion unison-src/transcripts/pattern-match-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ result : '{e, Give T} r -> {e} r
result f = handle !f with cases
{ x } -> x
{ give _ -> resume } -> result resume
{ give A -> resume } -> result resume
{ give T.A -> resume } -> result resume
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test wasn't quite testing the right thing: since the A constructor is ambiguous in the namespace, it ended up as a variable, essentially testing:

{ give _ -> resume } -> result resume
{ give _ -> resume } -> result resume

rather than the intended

{ give _ -> resume } -> result resume
{ give T.A -> resume } -> result resume

but both produce the same output (complaining that the last pattern is redundant)

```

## Exhaustive ability reinterpretations are accepted
Expand Down
4 changes: 2 additions & 2 deletions unison-src/transcripts/pattern-match-coverage.output.md
Original file line number Diff line number Diff line change
Expand Up @@ -1056,15 +1056,15 @@ result : '{e, Give T} r -> {e} r
result f = handle !f with cases
{ x } -> x
{ give _ -> resume } -> result resume
{ give A -> resume } -> result resume
{ give T.A -> resume } -> result resume
```

``` ucm

Loading changes detected in scratch.u.

This case would be ignored because it's already covered by the preceding case(s):
10 | { give A -> resume } -> result resume
10 | { give T.A -> resume } -> result resume


```
Expand Down
Loading