Semantic Diff

#8914 Warn about untagged payload patterns shadowed by literal cases

2 commits, rendered from their records.

Summary

RangeCommitsChangelogAreasJS output changesFormatsFailed checks
f52f1d1988f666515ad0debb7b7c0150ece798c9..pr-89142๐Ÿ› Bug fixtypecheckertests0none2

Changelog draft

๐Ÿ› Bug fix
  • Warn about untagged variant payload patterns that can never match because a literal case has the same value, like S("a") with @as("a") A | S(string), and treat @as(true) Yes | Ans(bool) as fully covered by Yes | Ans(false).

Files changed by more than one commit

compiler/ml/parmatch.mlf39e21efc, 29e3ee9e9
tests/build_tests/super_errors/expected/warning_12_untagged_literal_shadows_or_pattern.res.expectedf39e21efc, 29e3ee9e9
tests/build_tests/super_errors/fixtures/warning_12_untagged_literal_shadows_or_pattern.resf39e21efc, 29e3ee9e9

f52f1d1988f666515ad0debb7b7c0150ece798c9..pr-8914

f39e21efc ยท Christoph Knittel ยท Oct 19, 2026

Warn about untagged payload patterns shadowed by literal cases

๐Ÿ› Bug fix

A payload pattern whose values are all literal-case values can never match (warning 11/12), and a literal case covers the equal bool payload value in the exhaustiveness check. Fixes #8806.

๐Ÿ› Bug fixWarn about untagged variant payload patterns that can never match because a literal case has the same value, like S("a") with @as("a") A | S(string), and treat @as(true) Yes | Ans(bool) as fully covered by Yes | Ans(false).
17 files ยท +587 โˆ’108 ยท changelog 1 ยท compiler 2 ยท typechecker 4 ยท tests 10
Achanges/untagged-literal-overlap-warnings.fix.md+1โˆ’0
Mcompiler/ext/warnings.ml+8โˆ’6
Mcompiler/ext/warnings.mli+2โˆ’2
Mcompiler/ml/lambda.ml+5โˆ’24
Mcompiler/ml/parmatch.ml+233โˆ’5
Mcompiler/ml/variant_runtime.ml+24โˆ’7
Mcompiler/ml/variant_runtime.mli+8โˆ’0
Atests/build_tests/super_errors/expected/warning_11_untagged_bool_payload_overlap.res.expected+12โˆ’0
Atests/build_tests/super_errors/expected/warning_11_untagged_literal_shadows_payload.res.expected+65โˆ’0
Atests/build_tests/super_errors/expected/warning_11_untagged_literal_shadows_payload_kinds.res.expected+38โˆ’0
Atests/build_tests/super_errors/expected/warning_12_untagged_literal_shadows_or_pattern.res.expected+12โˆ’0
Atests/build_tests/super_errors/fixtures/warning_11_untagged_bool_payload_overlap.res+16โˆ’0
Atests/build_tests/super_errors/fixtures/warning_11_untagged_literal_shadows_payload.res+44โˆ’0
Atests/build_tests/super_errors/fixtures/warning_11_untagged_literal_shadows_payload_kinds.res+26โˆ’0
Atests/build_tests/super_errors/fixtures/warning_12_untagged_literal_shadows_or_pattern.res+8โˆ’0
Mtests/tests/src/unboxed_variant_fold_test.mjs+74โˆ’63
Mtests/tests/src/unboxed_variant_fold_test.res+11โˆ’1
with Claude Opus 5.5
29e3ee9e9 ยท Christoph Knittel ยท Oct 20, 2026

Report shadowed payload alternatives when only warning 12 is on

typecheckertests

The check for untagged payload patterns shadowed by literal cases ran only when warning 11 was active, so with -w -11+12 or @warning("-11") a dead or-pattern alternative got no warning 12. It now runs when either warning is active; the usefulness analysis is still gated on warning 11.

Example ReScript

@unboxed type t = | @as("a") A | S(string)@warning("-11")let g = x => switch x { | S("a") => 1 | S("a" | "c") => 2 | S(_) | A => 3 }

Diagnostic output

Before: no warnings
After: Warning 12 on the "a" in S("a" | "c"); the dead S("a") case stays
silent because warning 11 is off.
No effect
Output: unchangedFormats: nonePPX: unchangedEditor: unchanged
Tests
super_errors tests/build_tests/~ super_errors/fixtures/warning_12_untagged_literal_shadows_or_pattern.res
3 files ยท +28 โˆ’2 ยท typechecker 1 ยท tests 2
Mcompiler/ml/parmatch.ml+5โˆ’2
Mtests/build_tests/super_errors/expected/warning_12_untagged_literal_shadows_or_pattern.res.expected+13โˆ’0
Mtests/build_tests/super_errors/fixtures/warning_12_untagged_literal_shadows_or_pattern.res+10โˆ’0
with Claude Opus 5.5