Skip to content

Fixes #1112: typed int values no longer satisfy float64 targets (untyped-constant clause across all 20 value positions) - #1447

Closed
Ljunqiang wants to merge 3 commits into
limitlessprojects:mainfrom
Ljunqiang:fix/issue-1112-typed-value-coercion
Closed

Ljunqiang wants to merge 3 commits into
limitlessprojects:mainfrom
Ljunqiang:fix/issue-1112-typed-value-coercion

Conversation

@Ljunqiang

Copy link
Copy Markdown

Fixes #1112

Root cause

buildable.rs's kinds_conflict opens with a (Float, Int) => false arm that exists solely for Go's untyped-constant conversion (var f float64 = 1 builds) — its own comment concedes "the kind model cannot tell an untyped constant from an int variable". So every value position that compares kinds let a typed int variable through: var y int = 3; var x float64 = y checks clean (0 diagnostics) and then fails go build with cannot use y (value of type int) as float64 value in .... Issue #1112 enumerates the surfaces; all 10 were reproduced accept-then-go build-fail before this change.

Fix

New Collector::value_conflicts(want, got, value) = kinds_conflict(want, got) || (want == Float && got == Int && !self.is_untyped_constant(value)), guarding all 20 value positions:

site shape
typed init (1-value + parallel) var f float64 = y
struct literal field point{f: y}
fn arg + pointer-inner auto-& (x2) g(y) / take(y) on *float64
method arg + pointer-inner auto-& (x2) pt.scale(y)
report_return_conflict (4 callers, value expr threaded through) return y / return g()
append append(s, y)
assign single + parallel x = y / pf, xi = y, y
compound += and -=/*=//= x += y
map index key m[y] on map[float64]T
array / slice literal [2]float64{y} / []float64{y}
map literal key + value map[float64]string{y: ..} / map[string]float64{..: y}
copy elements copy(fs, is)
new: Stmt::Send (narrow) ch <- y on chan float64

is_untyped_constant is structural and conservative: int/rune literals, -x, a op b over constants, and references to an untyped const — file-level only when declared without a type annotation (const k int = 3 stays typed and rejects; fn-local consts fail open as untyped, since Define records no annotation — documented fail-open, same direction as kinds_conflict). Anything that reads a variable, calls, receives, or indexes is typed by construction in Go → false, i.e. value_conflicts is stricter, never looser, than kinds_conflict.

The Stmt::Send check is deliberately narrow (Float elem ← non-untyped Int only): kind-level chan-element conflicts stay owned by HYB-E3011's shape rules, so no double-reporting.

Untouched on purpose: kinds_conflict itself, equality_kinds_conflict (1 == 1.0 still legal), conversion_kinds_conflict (float64(y) still legal), kind-level container recursion ([]float64 vs []int variables — out of issue scope).

Side fix: the array-literal checker's literal-only const_int_value guard false-rejected an untyped file const (const k = 1 + [2]float64{k} → E1002; Go accepts). Subsumed by value_conflicts; now accepts, and typed/variable elements still reject.

Validation

  • New hy-check/tests/typed_int_float_coercion_issue_1112.rs: 29 tests — 20 reject faces (every table row above, incl. both send forms and f32) + 7 accept-control groups (literal/neg/binary/rune, untyped file and local const at init/assign/return/arg/append/array/slice/map/send/+=, float→float, float64(x) conversion, 1 == 1.0, classic return 1).
  • The issue's 10 repros + make-chan/array probes all flip accept → reject with HYB-E1002; literal controls stay accept.
  • cargo test --workspace green except the pre-existing spec_t3_strings failure, reproduced on clean HEAD (it reads SPEC-LANGUAGE.md's T3 fence — unrelated).
  • Corpus: new pin typed-int-into-float64-reject.hy (EXPECTED-VERDICT: REJECT BUILDABLE + EXPECTED-DIAGNOSTIC: HYB-E1002); census 126 → 127 on all 8 surfaces; harness 127/127, tripwire not fired; negative flip verified (exit 2 flipped / exit 0 restored).
  • Differential loop: 40/40 accept (build+run) + 87/87 reject.
  • Baseline check: without this change the new pin accepts (bug reproduced) while accept-side is byte-identical 40/40 — no accept regression.

Credit

Executor added 2 commits October 24, 2026 19:26
…64 targets — value positions route through an untyped-constant clause

kinds_conflict's `(Float, Int) => false` arm exists solely for Go's
untyped-constant conversion (`var f float64 = 1` builds), but the kind model
cannot tell an untyped constant from an `int` variable, so `var y int = 3;
var f float64 = y` (go build: `cannot use y ... value in ...`) passed every
value position — the issue's 10 faces plus more call sites verified accept.

Collector::value_conflicts(want, got, value) = kinds_conflict || (want Float
&& got Int && !is_untyped_constant(value)) now guards all 20 sites: typed
init (1/2-value), struct literal field, fn args incl. the pointer-inner
auto-& arm (2x2), method args (2x2), report_return_conflict (value expr
threaded through its 4 callers), append, assign single/parallel, compound
`+=` and `-=`/`*=`/`/=`, map index key, array/slice literal, map literal
key/value (the dead const_int_value guards subsumed — this also fixes the
pre-existing false reject of an untyped file const in an array literal),
copy element kinds, plus a new narrow Stmt::Send check (`chan float64 <-
typed int`; deliberately narrow so HYB-E3011's shape rules keep owning the
other send pairs). is_untyped_constant: int/rune literals, `-x`, `a op b`
over constants, and untyped const refs (file-level only when `ty` is
absent; fn-local consts fail open as untyped). equality_kinds_conflict and
conversion_kinds_conflict untouched — `1 == 1.0` and `float64(y)` stay
legal.

Validation: hy-check/tests/typed_int_float_coercion_issue_1112.rs (29
tests: 20 reject faces + 7 accept-control groups covering every classic
untyped-constant shape); the issue's 10 repros plus make-chan/array probes
all flip accept -> reject with HYB-E1002; corpus pin
typed-int-into-float64-reject.hy, census 126 -> 127 across all 8 surfaces;
corpus harness 127/127 (negative flip: exit 2 flipped / exit 0 restored);
differential loop 40/40 accept + 87/87 reject; cargo test --workspace green
except the pre-existing spec_t3_strings failure (reproduced on clean HEAD,
reads SPEC-LANGUAGE.md's T3 fence — unrelated).

Fixes limitlessprojects#1112. Credit: wminno's surface checklist in the issue; stale
companions zidirahim617-del's PR limitlessprojects#1130 (partial: 3 sites + tests) and
mhatipog's PR limitlessprojects#1117 — this PR supersedes both with full-site coverage.
@Ljunqiang

Copy link
Copy Markdown
Author

Post-merge validation on upstream/main @ a785e49 (session-35: #1419/#1432/#1439/#1441 — disjoint files from this PR, corpus still 126): cargo test --workspace green (the local-only spec_t3_strings failure is a working-tree CRLF artifact in SPEC-LANGUAGE.md — reproduced on clean HEAD, passes on LF checkouts), census 127, harness 127/127 (tripwire not fired), differential 40/40 accept + 87/87 reject.

Census coordination: #1428 and #1430 also bump 126 → 127 with their own pins — whichever lands last should rebase the count to 128/129 on all 8 surfaces (same pattern as the #906/#905 sibling take).

@github-actions github-actions Bot added the bot:land-candidate Shepherd: fresh, gate-clean, verifiable (set from classify()) label Oct 24, 2026
Ljunqiang pushed a commit to Ljunqiang/Go-Hybrid that referenced this pull request Oct 24, 2026
…nd comparisons (fixes limitlessprojects#922)

Root cause: check_binary_operands compared numeric FAMILIES, not concrete
kinds — for `< <= > >=` and `+` the mismatch term was
`lk.is_numeric() != rk.is_numeric()` (int and float64 are both numeric, so
never mismatched), and `- * /` only checked kind membership. `x + f`,
`x - f`, `x * f`, `x / f`, `x < f`, `f <= x` checked clean while go build
refused each with `mismatched types int and float64` (GUARANTEES Row 13).

Fix: a new Collector::numeric_adapt_conflict routes cross-kind operand
pairs through Go's only implicit numeric conversion — untyped-constant
adaptation — and emits HYB-E1002 when it cannot bridge them:
  - `1 + 2.5` (both untyped) and `f + 1` / `1 + f` (int const -> float64)
    keep building, including untyped `const` identifiers (`f + k`);
  - `x + f`, `f + x`, `x - f`, `x < f`, `f <= x`, and the untyped float
    const against a typed int (`x + 2.5`) are rejected;
  - `Define.untyped_const` (from LocalDecl.ty) distinguishes `const k = 1`
    (untyped) from `const k int = 1` (typed like a variable) for fn-local
    consts — the annotation was recorded but never consulted.

Validation: new hy-check/tests/binop_operand_types_issue_922.rs 24/24
(15 reject faces + 9 accept controls); corpus pin
binop-mismatched-numeric-reject.hy (REJECT BUILDABLE + HYB-E1002), census
126 -> 127 on all 7 surfaces; harness 127/127, tripwire not fired;
negative flip: baseline upstream accepts the pin (126/127, pin FAILs) and
restores to 127/127; differential 40/40 accept + 87/87 reject;
cargo test --workspace green except the pre-existing local-CRLF
spec_t3_strings failure (reproduces on clean HEAD).

Harvest credit: stale companion rickymulya PR limitlessprojects#923 (bot:stale-base +
bot:conflict) proposed the same family of rules with literal-only
helpers; this take supersedes it with const-ident support and the
untyped_const annotation check.

Coordination: limitlessprojects#1428, limitlessprojects#1430 and limitlessprojects#1447 also bump census 126 -> 127 — the
PR that lands last rebases the count to 128+. The numeric_adapt_conflict
doc carries a dedupe NOTE against limitlessprojects#1447's is_untyped_constant (the
kinds_conflict / PR limitlessprojects#620 duplicate-helper precedent) for whoever lands
second.
…n — post-session-36 landings (limitlessprojects#1410/limitlessprojects#1411/limitlessprojects#1416/limitlessprojects#1418/limitlessprojects#1429/limitlessprojects#1440): corpus gains select-two-recv-define-arms-accept.hy (union 128 = 39/2/87), census reconciled to 128 on all 7 surfaces, buildable.rs auto-merged (1112 value-conflict points disjoint from E2005/main-init changes)
@limitlessprojects

Copy link
Copy Markdown
Owner

Queue-reset sweep: this repo now runs a rolling conveyor, and open PRs
outside the active review window are being closed as not planned to keep the
queue honest — a stale queue wastes every contributor's time, including yours.

This is not a verdict on the work. If this change still matters:

  1. rebase against current main (main has landed a lot since this PR opened),
  2. confirm the diff still applies and the affected verdicts still hold,
  3. reopen — the triage board picks reopened PRs up as fresh arrivals.

Wildcard holds (verdict window 2026-11-01) and Pareto land candidates are
unaffected. Thanks for the effort — high-volume repos only stay reviewable
with a queue that actually drains.

Ljunqiang pushed a commit to Ljunqiang/Go-Hybrid that referenced this pull request Oct 24, 2026
…nd comparisons (fixes limitlessprojects#922)

Root cause: check_binary_operands compared numeric FAMILIES, not concrete
kinds — for `< <= > >=` and `+` the mismatch term was
`lk.is_numeric() != rk.is_numeric()` (int and float64 are both numeric, so
never mismatched), and `- * /` only checked kind membership. `x + f`,
`x - f`, `x * f`, `x / f`, `x < f`, `f <= x` checked clean while go build
refused each with `mismatched types int and float64` (GUARANTEES Row 13).

Fix: a new Collector::numeric_adapt_conflict routes cross-kind operand
pairs through Go's only implicit numeric conversion — untyped-constant
adaptation — and emits HYB-E1002 when it cannot bridge them:
  - `1 + 2.5` (both untyped) and `f + 1` / `1 + f` (int const -> float64)
    keep building, including untyped `const` identifiers (`f + k`);
  - `x + f`, `f + x`, `x - f`, `x < f`, `f <= x`, and the untyped float
    const against a typed int (`x + 2.5`) are rejected;
  - `Define.untyped_const` (from LocalDecl.ty) distinguishes `const k = 1`
    (untyped) from `const k int = 1` (typed like a variable) for fn-local
    consts — the annotation was recorded but never consulted.

Validation: new hy-check/tests/binop_operand_types_issue_922.rs 24/24
(15 reject faces + 9 accept controls); corpus pin
binop-mismatched-numeric-reject.hy (REJECT BUILDABLE + HYB-E1002), census
126 -> 127 on all 7 surfaces; harness 127/127, tripwire not fired;
negative flip: baseline upstream accepts the pin (126/127, pin FAILs) and
restores to 127/127; differential 40/40 accept + 87/87 reject;
cargo test --workspace green except the pre-existing local-CRLF
spec_t3_strings failure (reproduces on clean HEAD).

Harvest credit: stale companion rickymulya PR limitlessprojects#923 (bot:stale-base +
bot:conflict) proposed the same family of rules with literal-only
helpers; this take supersedes it with const-ident support and the
untyped_const annotation check.

Coordination: limitlessprojects#1428, limitlessprojects#1430 and limitlessprojects#1447 also bump census 126 -> 127 — the
PR that lands last rebases the count to 128+. The numeric_adapt_conflict
doc carries a dedupe NOTE against limitlessprojects#1447's is_untyped_constant (the
kinds_conflict / PR limitlessprojects#620 duplicate-helper precedent) for whoever lands
second.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:land-candidate Shepherd: fresh, gate-clean, verifiable (set from classify())

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Soundness] hy-check: assigning a typed int variable to a float64 variable passes check, fails go build (HYB-E1002)

2 participants