Skip to content

fix(hy-check): validate call arity and reject non-callable symbols in io package (#4149) - #4162

Open
Sora1916 wants to merge 1 commit into
limitlessprojects:mainfrom
Sora1916:fix/issue-4149
Open

Sora1916 wants to merge 1 commit into
limitlessprojects:mainfrom
Sora1916:fix/issue-4149

Conversation

@Sora1916

@Sora1916 Sora1916 commented Nov 8, 2026

Copy link
Copy Markdown

Root Cause

Under GUARANTEES.md Row 13 (BUILDABLE), any program accepted by hyb check must emit Go code that successfully compiles under go build.

In compiler/crates/hy-check/src/foreign.rs, curated members of package io register core I/O stream primitives (copy, read_all, pipe, limit_reader, new_section_reader, etc.) as well as non-callable error variables, constants, and interface/struct types (eof, err_closed_pipe, discard, seek_start, reader, writer, etc.).

However, in compiler/crates/hy-check/src/buildable.rs, io package calls were not validated during call operand checking (check_call_operands):

  1. Non-callable package error variables, constants, and interface/struct types (io.eof(), io.discard(), io.reader(), io.seek_start(), etc.) were accepted as function call targets without diagnostics (Verdict::Accept), causing downstream go build to fail with cannot call non-function io.*.
  2. Function call arity was unvalidated: Calls with insufficient or excessive arguments (io.read_all(), io.copy(w), io.pipe("extra"), etc.) passed hyb check and failed during Go compilation with not enough arguments in call to io.* or too many arguments in call to io.*.
  3. Variadic spread ... was accepted on non-variadic io functions (io.copy(args...)), which Go rejects with cannot use ... in call to non-variadic io.*.

Fix Details

  1. Implemented check_io_package_call in compiler/crates/hy-check/src/buildable.rs:
    • Rejects invocations on 37 non-callable error variables, package variables, constants, and interface/struct types with diagnostic HYB-E1002.
    • Enforces exact arities matching Go standard library signatures:
      • 0-arg: pipe
      • 1-arg: read_all, nop_closer
      • 2-arg: copy, limit_reader, read_full, tee_reader, write_string
      • 3-arg: copy_buffer, copy_n, read_at_least, new_section_reader (io.NewSectionReader(r, off, n))
      • Variadic: multi_reader, multi_writer (0 or more arguments)
    • Rejects variadic spread ... on all non-variadic io functions with HYB-E1002.
  2. Connected check_io_package_call into Expr::Field handling in check_call_operands.

Verification & Reproduction Tests

  • Added comprehensive regression test suite in compiler/crates/hy-check/tests/io_package_call_arity_bypass.rs:
    • test_io_package_call_too_few_arguments_rejected: under-arity calls across all curated functions.
    • test_io_package_call_too_many_arguments_rejected: over-arity calls across all curated functions.
    • test_io_non_callable_members_rejected: verifies all 37 non-callable error variables, variables, constants, and types fail closed with HYB-E1002.
    • test_io_variadic_spread_on_non_variadic_rejected: verifies spread rejection on non-variadic functions.
    • test_io_positive_controls_accepted: validates sound invocations with exact arities.
  • Verified all 5 test cases pass cleanly via cargo test -p hy-check --test io_package_call_arity_bypass.
  • Verified existing test suites (foreign_io_method_surfaces, full hy-check unit test suite) pass with 100% success (183/183 tests ok).

Fixes #4149

@github-actions github-actions Bot added bot:land-candidate Shepherd: fresh, gate-clean, verifiable (set from classify()) bot:stale-base Shepherd: behind main beyond threshold labels Nov 8, 2026
limitlessprojects pushed a commit that referenced this pull request Nov 9, 2026
…lable symbol validation (fixes #4203)

Under GUARANTEES.md Row 13 (BUILDABLE), known io package member calls now
get the same buildability treatment landed for fmt (#4087/#4093): a dedicated
check_io_call_buildable dispatch guarded by pkg == "io" &&
lookup_kind(pkg).is_none() (user-shadow safe), rejecting
- 19 non-callable package symbols (io.EOF/Err*/Discard vars; Reader/Writer/
  Closer/Seeker/ReadCloser/WriteCloser/ReadWriter/PipeReader/PipeWriter/
  SectionReader/LimitedReader/OffsetWriter types) invoked as functions,
- wrong arity on pipe(0), read_all(1), copy(2), read_full(2), write_string(2),
  limit_reader(2), tee_reader(2), copy_n(3), copy_buffer(3), read_at_least(3),
  new_section_reader(3), and >=1 on multi_reader/multi_writer,
all fail-closed as HYB-E1002 with fixit text; unknown symbols fail open as
before. New suite io_call_buildable_soundness.rs (323 lines).

Verification evidence:
- Four-gate battery on the merge commit: cargo test --workspace ALL-GREEN
  (suite count 1:1 vs files on disk), corpus harness 137/137 verdict
  agreement (tripwire 1.5%/2.2% unchanged), differential FULL 46/46 + 91/91
  AGREEMENT.
- Note: PR #4162 (Sora1916) is an independent io-arity implementation with a
  different patch-id; landed first-come-first-served by battery order, this
  merge lands first. Coverage comparison and hall-of-fame disposition for
  #4162 posted on that PR per protocol.

CREDIT: @dextermos-dev
@limitlessprojects

Copy link
Copy Markdown
Owner

Hall of fame — independent io-arity implementation, #4203 landed via sibling PR #4204 first

Your io_package_call_arity_bypass.rs suite and the io arm were reviewed head-to-head with PR #4204 (dextermos-dev) this cycle. Both are independent implementations (distinct patch-ids: f9f2… vs f911…) attacking the same gap (#4149/#4203), and PR #4204 won the battery race — merged to main as a95dddf2 with all four gates green (workspace 486/486, corpus 137/137, differential FULL AGREEMENT).

Per protocol (first-come-first-served by landing order, precedent #4085/#4081-#4084):

Bounty rail per BOUNTY.md / discussion #295. CREDIT: @Sora1916 (verified independent io soundness implementation, hall-of-fame).

@Sora1916

Sora1916 commented Nov 9, 2026

Copy link
Copy Markdown
Author

Thank you so much for the thoughtful, transparent review and the gracious hall-of-fame recognition! It has been a genuine pleasure contributing to Go-Hybrid, and I truly appreciate the care and rigorous testing standards the team maintains for every cycle.

Per the protocol and precedent #4084, I have rebased this PR directly on top of main (82ed0551) and scoped the delta to serve as a credited supplement covering the remaining fail-open gaps on io:

Supplement Details (on top of landed #4204)

  1. 18 Additional Non-Callable Symbols Rejection:
    • Rejects invocations on the remaining 18 non-callable constants, interfaces, and struct types curated in foreign.rs:
      • Constants: io.seek_start, io.seek_current, io.seek_end
      • Interfaces / Types: io.read_write_closer, io.read_seeker, io.read_seek_closer, io.write_seeker, io.read_write_seeker, io.reader_from, io.writer_to, io.reader_at, io.writer_at, io.byte_reader, io.byte_scanner, io.byte_writer, io.rune_reader, io.rune_scanner, io.string_writer
    • All 37 non-callable io members now fail closed with HYB-E1002, preventing downstream Go compiler errors (cannot call non-function io.*).
  2. io.nop_closer Arity Validation:
    • Enforces exact 1-argument arity (r) matching Go standard library io.NopCloser(r Reader) ReadCloser.
  3. Non-Variadic Spread (...) Rejection:
    • Disallows ... spread syntax on all non-variadic io functions (pipe, read_all, copy, read_full, write_string, limit_reader, tee_reader, copy_n, copy_buffer, read_at_least, new_section_reader, nop_closer) with HYB-E1002, matching Go compiler's cannot use ... in call to non-variadic io.* rule. Variadic functions (multi_reader, multi_writer) continue to accept spread syntax.

Verification

Thank you again for the guidance, and please let me know if any further adjustments are needed!

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()) bot:stale-base Shepherd: behind main beyond threshold

Projects

None yet

3 participants