diff --git a/App/lib/core/Gen/MatchGenMixin.vp b/App/lib/core/Gen/MatchGenMixin.vp index f0e1196..32190ed 100644 --- a/App/lib/core/Gen/MatchGenMixin.vp +++ b/App/lib/core/Gen/MatchGenMixin.vp @@ -315,6 +315,7 @@ novirtual class MatchGenMixin: if mc is not None: pk: int = self._opt_pattern_kind(mc.pattern) if pk == 1: return 1 + if pk == 2 and (self._pattern_is_nil(mc.pattern) != 0 or self._pattern_is_none_singleton(mc.pattern) != 0): return 1 return 0 # _handle_optional_ptr_match - match over a nullable pointer (`own U?` / `T?`): @@ -363,10 +364,26 @@ novirtual class MatchGenMixin: 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)`, `None`, `Nil()` and `_`", "TypeError") + # Both arms must be covered: the compiler enforces what the language server reports. + if (some_case is None and def_case is None) or (nil_case is None and def_case is None): + miss: str = "" + if some_case is None and def_case is None: + miss = "Some(...)" + if nil_case is None and def_case is None: + if string.strlen(miss) > 0: miss = f"{miss}, None" + else: miss = "None" + msg: bytes = pool.alloc(sizet(320)) + if msg is not None: + viperlib.snprintf(msg, sizet(320), + "match is not exhaustive: missing branch(es): %s (add the missing `case` or `case _`)", miss) + self.Gen.TypeGenErr.fatal_error(node, str(msg), "TypeError") mlirlite.build_cond_br(builder, cond, some_bb, nil_bb) # Some branch. mlirlite.position_at_end(builder, some_bb) self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH) + # A wildcard arm also covers the non-null branch when no `Some` arm was written. + some_body: ast.MatchCase* = some_case + if some_body is None: some_body = def_case if some_case is not None: sp: ast.AST* = some_case.pattern if sp is not None and sp.kind() == ast.ASTKind.MatchClass: @@ -381,12 +398,12 @@ novirtual class MatchGenMixin: mlirlite.build_store(builder, subject_val, slot) self.Gen.VarGen.define_var(trans.SymTab, ma_s.name, slot) self.Gen.VarGen.set_var_life(trans.SymTab, ma_s.name, GenTypes.LIFE_FRAME) - if some_case.children is not None: - bi: int = 0 - while bi < int(len(some_case.children)): - st: ast.AST* = some_case.children.get(sizet(bi)) - bi += 1 - if st is not None: self.Gen.StmtGen.translate_stmt(trans, st) + if some_body is not None and some_body.children is not None: + bi: int = 0 + while bi < int(len(some_body.children)): + st: ast.AST* = some_body.children.get(sizet(bi)) + bi += 1 + if st is not None: self.Gen.StmtGen.translate_stmt(trans, st) self.Gen.VarGen.exit_scope(trans.SymTab) if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, end_bb) # Nil branch (explicit Nil arm wins over the wildcard arm). @@ -1109,6 +1126,14 @@ novirtual class MatchGenMixin: gzero: mlirlite.Value* = mlirlite.const_int(pool, gbits if gbits > 0 else 32, 0) return mlirlite.build_icmp(builder, mlirlite.ICMP_NE, gv, gzero) + def _pattern_is_none_singleton(self, pat: ast.AST*) -> int: + """1 for the `None` literal pattern (MatchSingleton with a CONST_NONE value).""" + if pat is None or pat.kind() != ast.ASTKind.MatchSingleton: return 0 + v: ast.AST* = (ast.MatchSingleton*)(pat).value + if v is not None and v.kind() == ast.ASTKind.Constant: + if (ast.Constant*)(v).const_kind == ast.CONST_NONE: return 1 + return 0 + def _pattern_is_nil(self, pat: ast.AST*) -> int: """1 when a pattern is spelled `Nil` / `Nil()`.""" if pat is None: return 0 diff --git a/App/lib/lsp/LspChecks.vp b/App/lib/lsp/LspChecks.vp index 08bd086..b48b901 100644 --- a/App/lib/lsp/LspChecks.vp +++ b/App/lib/lsp/LspChecks.vp @@ -1541,7 +1541,7 @@ def _WalkChecks(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*, miss: str = "" nil_name: str = "None" if nullable_s == 0: nil_name = "Nil" - if has_some == 0: miss = "Some(...)" + if has_some == 0 and has_wild == 0: miss = "Some(...)" if has_nil == 0 and has_wild == 0: if string.strlen(miss) > 0: miss = f"{miss}, {nil_name}" else: miss = nil_name