fix(match): reject a bare Nil pattern
A bare `Nil` parses as a capture, so a nullable match silently treated it as the null branch. Both the compiler and the language server now reject it and point at `None` (nullable `T?`) or `Nil()` (builtin `Option[T]`). Exhaustiveness reports `None` as the missing nullable branch, and the builtin generic type names no longer trigger the unknown-name warning.
This commit is contained in:
1 parent
7289b027ec
commit
7917992deb
2 files changed
+30
-9
No files matched your search
@@ -288,7 +288,7 @@ novirtual class MatchGenMixin:
|
||||
return 0
|
||||
|
||||
# _opt_pattern_kind - classify a nullable-pointer Option pattern:
|
||||
# 1 = Some(...) / Some, 2 = Nil / Nil(), 3 = wildcard `_`, 0 = unsupported.
|
||||
# 1 = Some(...) / Some, 2 = None / Nil(), 3 = wildcard `_`, 0 = unsupported.
|
||||
def _opt_pattern_kind(self, pat: ast.AST*) -> int:
|
||||
if pat is None: return 0
|
||||
k: int = pat.kind()
|
||||
@@ -318,7 +318,7 @@ novirtual class MatchGenMixin:
|
||||
return 0
|
||||
|
||||
# _handle_optional_ptr_match - match over a nullable pointer (`own U?` / `T?`):
|
||||
# Some(v) -> non-null branch, v = the pointer; Nil -> null branch.
|
||||
# Some(v) -> non-null branch, v = the pointer; None / Nil() -> null branch.
|
||||
def _handle_optional_ptr_match(self, trans: HT.Translator*, node: ast.Match*, subject_val: mlirlite.Value*) -> int:
|
||||
pool: memhub.MemBuddy* = trans.Pool
|
||||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||||
@@ -355,11 +355,14 @@ novirtual class MatchGenMixin:
|
||||
if mc.guard is not None:
|
||||
self.Gen.TypeGenErr.fatal_error(mc, "nullable Option match does not support a guard; remove the guard", "NotImplementedError")
|
||||
pk: int = self._opt_pattern_kind(mc.pattern)
|
||||
if pk == 2 and mc.pattern is not None and mc.pattern.kind() == ast.ASTKind.MatchAs:
|
||||
# A bare `Nil` parses as a capture, so accepting it would silently swallow every value.
|
||||
self.Gen.TypeGenErr.fatal_error(mc.pattern, "`Nil` is not a pattern by itself; use `None` for a nullable `T?` value, or `Nil()` for the builtin `Option[T]` renum", "TypeError")
|
||||
if pk == 1: some_case = mc
|
||||
elif pk == 2: nil_case = mc
|
||||
elif pk == 3: def_case = mc
|
||||
elif mc.pattern is not None:
|
||||
self.Gen.TypeGenErr.fatal_error(mc.pattern, "nullable Option match only accepts `Some(v)`, `Nil` / `Nil()` and `_`", "TypeError")
|
||||
self.Gen.TypeGenErr.fatal_error(mc.pattern, "nullable Option match only accepts `Some(v)`, `None`, `Nil()` and `_`", "TypeError")
|
||||
mlirlite.build_cond_br(builder, cond, some_bb, nil_bb)
|
||||
# Some branch.
|
||||
mlirlite.position_at_end(builder, some_bb)
|
||||
@@ -557,7 +560,7 @@ novirtual class MatchGenMixin:
|
||||
ma: ast.MatchAs* = ast.MatchAs*(pat)
|
||||
if ma.pattern is None:
|
||||
if ma.name is not None and string.strcmp(ma.name, "Nil") == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(pat, "`Nil` is only valid for a nullable Option match (`own T?` / `T?`), not a renum match", "TypeError")
|
||||
self.Gen.TypeGenErr.fatal_error(pat, "`Nil` is not a pattern by itself; use `None` for a nullable `T?` value, or `Nil()` for the builtin `Option[T]` renum", "TypeError")
|
||||
is_def = 1
|
||||
has_default = 1
|
||||
default_case_node = case_node
|
||||
@@ -1129,7 +1132,7 @@ novirtual class MatchGenMixin:
|
||||
nc: ast.MatchCase* = ast.MatchCase*(node.cases.get(sizet(ni)))
|
||||
ni += 1
|
||||
if nc is not None and self._pattern_is_nil(nc.pattern) != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(nc.pattern, "`Nil` is only valid for a nullable Option match (`own T?` / `T?`) or the builtin Option renum; it is not a wildcard", "TypeError")
|
||||
self.Gen.TypeGenErr.fatal_error(nc.pattern, "`Nil` is not a pattern by itself; use `None` for a nullable `T?` value, or `Nil()` for the builtin `Option[T]` renum", "TypeError")
|
||||
# Same as the main path: locally track this simple match's br end_bb and overwrite _ctrl_merge_reachable at the end.
|
||||
sm_merge_reach: int = 0
|
||||
|
||||
|
||||
@@ -1035,6 +1035,8 @@ def _IsKnownPlainName(name: str) -> int:
|
||||
if string.strcmp(name, "self") == 0 or string.strcmp(name, "cls") == 0: return 1
|
||||
if string.strcmp(name, "None") == 0 or string.strcmp(name, "True") == 0 or string.strcmp(name, "False") == 0: return 1
|
||||
if string.strcmp(name, "Some") == 0 or string.strcmp(name, "Nil") == 0: return 1
|
||||
if string.strcmp(name, "Option") == 0 or string.strcmp(name, "Box") == 0: return 1
|
||||
if string.strcmp(name, "RawT") == 0: return 1
|
||||
if LspSymbols._IsBuiltinTypeName(name) != 0: return 1
|
||||
if string.strcmp(name, "print") == 0 or string.strcmp(name, "len") == 0 or string.strcmp(name, "range") == 0: return 1
|
||||
if string.strcmp(name, "malloc") == 0 or string.strcmp(name, "free") == 0 or string.strcmp(name, "sizeof") == 0: return 1
|
||||
@@ -1045,6 +1047,15 @@ def _IsKnownPlainName(name: str) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _LspBareNil(pat: ast.AST*) -> int:
|
||||
"""1 when a pattern is a bare `Nil` capture (not the `Nil()` call form)."""
|
||||
if pat is None: return 0
|
||||
if pat.kind() != ast.ASTKind.MatchAs: return 0
|
||||
ma: ast.MatchAs* = (ast.MatchAs*)(pat)
|
||||
if ma.pattern is None and ma.name is not None and string.strcmp(ma.name, "Nil") == 0: return 1
|
||||
return 0
|
||||
|
||||
|
||||
def _LspNilPattern(pat: ast.AST*) -> int:
|
||||
"""1 when a pattern is spelled `Nil` / `Nil()` (not the MatchSingleton None)."""
|
||||
if pat is None: return 0
|
||||
@@ -1517,16 +1528,23 @@ def _WalkChecks(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
|
||||
elif pk_o == 2: has_nil = 1
|
||||
elif pk_o == 3: has_wild = 1
|
||||
elif _LspNilPattern(ck.pattern) != 0: has_nil = 1
|
||||
if nullable_s == 0 and opt_renum == 0 and _LspNilPattern(ck.pattern) != 0:
|
||||
bad_nil: int = 0
|
||||
if _LspBareNil(ck.pattern) != 0:
|
||||
bad_nil = 1
|
||||
elif nullable_s == 0 and opt_renum == 0 and _LspNilPattern(ck.pattern) != 0:
|
||||
bad_nil = 1
|
||||
if bad_nil != 0:
|
||||
_EmitTypeError(pool, doc, ck.pattern,
|
||||
"`Nil` is only valid for a nullable Option match (`own T?` / `T?`) or the builtin Option renum", errors)
|
||||
"`Nil` is not a pattern by itself; use `None` for a nullable `T?` value, or `Nil()` for the builtin `Option[T]` renum", errors)
|
||||
_WalkChecks(pool, doc, a, ws, table_pool, ck.children, errors)
|
||||
if nullable_s != 0 or opt_renum != 0:
|
||||
miss: str = ""
|
||||
nil_name: str = "None"
|
||||
if nullable_s == 0: nil_name = "Nil"
|
||||
if has_some == 0: miss = "Some(...)"
|
||||
if has_nil == 0 and has_wild == 0:
|
||||
if string.strlen(miss) > 0: miss = f"{miss}, Nil"
|
||||
else: miss = "Nil"
|
||||
if string.strlen(miss) > 0: miss = f"{miss}, {nil_name}"
|
||||
else: miss = nil_name
|
||||
if string.strlen(miss) > 0:
|
||||
msg_e: bytes = pool.alloc(sizet(320))
|
||||
if msg_e is not None:
|
||||
|
||||
Reference in new issue
Block a user