Repository navigation
fix(hy-check): reject untyped nil local declarations (var x = nil / x := nil / let x = nil) with HYB-E4003, the N1 untyped leg (fixes #672) - #673
Merged
limitlessprojects merged 1 commit intoOct 18, 2026
Conversation
… := nil / let x = nil) with HYB-E4003, the N1 untyped leg (fixes limitlessprojects#672) The E4001 nil check at both local-declaration sites was ascription-gated (if let Some(t) = &l.ty): with no ascription, shape_of_expr(nil) is Unknown, the checker accepted the declaration with 0 diagnostics, and the emitter emitted �ar x = nil / x := nil — which Go rejects outright (use of untyped nil in variable declaration). Accepted-but-unbuildable (GUARANTEES row 13, BUILDABLE). - new registry code HYB-E4003 untyped-nil-declaration (rule N, tier0 N1): nil's two homes (error, foreign perimeter) must be visible in a DECLARED type; an untyped nil can never name either. Three fix routes: ascribe the nil home; declare without initializer for the zero value (N3); option[T] for absence. - loans.rs process_local: else-branch on the E4001 ascription gate emits E4003 for bare-nil initializers of var/let/:= (flat walk covers loop/match-arm/if-block bodies). - closures.rs check_stmt_boundaries: same else-branch for closure bodies. - local const c = nil is left to the E6003 constant-expression guard (issue limitlessprojects#640 / PR limitlessprojects#671) — Go's error there is 'nil is not constant'; the two loci do not double-fire. - 13 unit pins (hy-check/tests/untyped_nil_decl.rs): four spellings, closure/loop/match-arm/blank; controls that stay accepted (ascribed error-nil, bare nil as call argument/compare, zero-value declarations). - corpus fixture untyped-nil-decl-reject.hy (REJECT N1, exact diagnostic HYB-E4003) registered in VERDICTS.md; corpus 80/80, tripwire not fired; differential reject-side 57/57; cargo workspace 1150/0. Co-Authored-By: opencode <noreply@opencode.ai>
milkingndb-oss
force-pushed
the
e4003-untyped-nil-decl
branch
from
October 18, 2026 15:31
1ce7665 to
17ff816
Compare
This was referenced Oct 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #672
The bug
The N1 nil check at both local-declaration check sites was ascription-gated (
if let Some(t) = &l.ty). With no ascription -var x = nil/x := nil/let x = nil- there is no branch to take,shape_of_expr(nil)isTypeShape::Unknown, and the checker accepted the declaration with 0 diagnostics. The emitter then emittedvar x = nil/x := nilverbatim, which Go rejects outright:use of untyped nil in variable declaration. Accepted-but-unbuildable - GUARANTEES row 13 (BUILDABLE). Full repro table + controls in #672.The fix
HYB-E4003 untyped-nil-declaration(ruleN, tier0N1, categorynil-safety): nil has exactly two homes (N1) -errorand the foreign-Go perimeter (N2) - and both must be visible in a DECLARED type; an untyped nil can never name either. Three fix routes taught: ascribe the nil home (var e error = nil); declare without initializer for the zero value (N3:var ch chan int);option[T]for absence. A new code rather than reusing E4001 because E4001's message and fix-its are built around a named declared type (nil is not valid for {ty}) - here the defect is precisely that no type exists.loans.rsprocess_local: else-branch on the E4001 ascription gate emits E4003 for bare-nil initializers ofvar/let/:=kinds (the flat walk covers loop/match-arm/if-block bodies).closures.rscheck_stmt_boundaries: the same else-branch for closure bodies (the flat walk skips FnLit bodies; this walk owns them).const c = nilis left alone: Go's error there isnil is not constant- the E6003 constant-expression guard's locus (Soundness & buildability gaps inmake/newshape resolution, closure builtin validation, barechan Texpressions, and file-levelconstforward references #640, PR fix(hy-check): reject nil consts and support f32/f64/char and string aliases in const_init.rs #671). The two loci do not double-fire (l.kind != LocalKind::Constin both branches).Verification
cargo test --workspacespec_t3_stringsfailure is a pre-existing CRLF-in-checkout issue - reproduces on clean0ca08c2, unrelated)hy-check/tests/untyped_nil_decl.rsvar/:=/let/parallela, b := nil, 1), closure body, loop body, match arm, blank_; controls stay accepted (ascribedvar e error = nil, bare nil as call argument,ch == nilcompare, zero-value declarations); localconst c = nildoes not fire E4003untyped-nil-decl-reject.hypinsREJECT N1+ exact diagnosticHYB-E4003, registered additively in VERDICTS.md)hyb explain HYB-E4003Corpus/examples impact: zero - no existing corpus/example file contains an untyped-nil declaration (grepped); the fix is regression-safe by construction.
Claim check
Searched open issues + PRs for
untyped nil,var x = nil,use of untyped nil,E4003: no claim on this locus. #640/#670/#671 are theconst-nil family (E6003,expr_is_constant_inner); corpus 28 is composite-literal nested nils; the ascribed E4001 sites are unchanged.