Skip to content

Infer return-only generic type arguments from context - #13

Merged
mitchellnemitz merged 8 commits into
mainfrom
impl-2026-07-24-context-directed-type-inference
Jul 24, 2026
Merged

mitchellnemitz merged 8 commits into
mainfrom
impl-2026-07-24-context-directed-type-inference

Conversation

@mitchellnemitz

Copy link
Copy Markdown
Owner

Summary

A generic function call now infers the type parameters its value arguments leave unbound from the call site's expected type. A return-only type parameter no longer has to be restated as an explicit type argument:

let xs: int[] = empty_list()      # was: empty_list[int]()

This is a type-checker-only change. The compiled shell is byte-identical to the explicit-type-argument form, so no runtime or codegen behavior changes; the only difference is that programs which previously required explicit type arguments now also compile without them.

How it works

The checker already computes an expected type and threads it to the let/final/return/assignment/array-literal/dict-literal positions. The one place that discarded it was the call-expression case in exprType. This forwards that expected type through the call chain into checkGenericUserCall, and after value arguments have bound what they can (before the cannot-infer error), unifies the callee's declared return type against the expected type to bind any still-unbound type parameter.

Precedence holds: value arguments bind first and always win; an explicit type-argument list disables context inference entirely; an expected type that itself carries an unresolved type variable is not concrete and does not drive inference. A context type that contradicts a value-argument binding reuses the existing unify-conflict error rather than a new error kind.

Inference positions

let/final binding, return, concretely-typed call argument, assignment RHS, and array-element / dict-value. Field-assignment and index-assignment RHS also infer, because those handlers already thread the target type as context; that behavior is locked by tests rather than special-cased away.

Boundaries (unchanged)

Variable, parameter, and return-type annotations stay mandatory. An unpinned type parameter is still an error (no defaulting). A nested call whose enclosing parameter is itself an unbound type variable, e.g. identity(empty_list()), still needs explicit type arguments.

Tests

  • Checker tests for every position, for precedence and conflict, and for the nested-unbound and type-variable-carrying boundaries.
  • Codegen byte-identity tests: context-inferred output equals the explicit-type-argument output at every position; numeric-bounded params still monomorphize (int and float outputs differ); comparable-bounded params are type-erased; bound violations still error.
  • A cross-shell golden fixture with identical stdout under dash, busybox ash, bash, and zsh.
  • gofmt, go vet, and go test ./... clean; the build-test and golden CI jobs pass locally under act.

mitchellnemitz and others added 8 commits July 24, 2026 12:20
Thread the contextual expected type into generic call checking and bind
still-unbound type parameters by unifying the declared return type against
it. Covers FR-001/FR-002/FR-004a/FR-005/SC-001.

Also updates the pre-existing TestGenericCannotInferReturnOnly (generics_test.go):
its `let xs: int[] = make()` case is now exactly the scenario this feature
makes inferable, so the case is changed to a discarded call (no context)
to keep asserting the still-uninferable no-context case; not in the plan's
named file list but required to avoid a regression in the full package run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Locks FR-004b..e and SC-002/SC-003/SC-004.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Locks FR-002/FR-003/FR-005/FR-006/FR-009/FR-010 and SC-007/SC-008/SC-009/SC-010.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix plan test-fixture defects surfaced at grounding (feature behavior is
correct; only the assertions were wrong):
- ExplicitArgsWinOverContext: array type renders as [int]/[string], not
  int[]/string[]; align the expected substring.
- WantWithTypeVar{Errors,ExplicitOK}: 'wrap' is a reserved builtin
  (builtins.go); rename the fixture fn to 'wraplist'.
- AnnotationStillRequired: a let without a type annotation is rejected at
  parse time (before Check), so assert parser.Parse errors rather than
  checking info.Errors via check() (which fatals on parse error).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Byte-identity (SC-005), numeric monomorphization + violation (SC-006),
comparable erasure + violation (SC-014), FR-008.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Return-only inference across positions, identical under dash/ash/bash/zsh.
SC-005 (runtime), SC-011, SC-013, SI-001.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update the generics inference note and CHANGELOG. FR-011.

SC-011 audit: grep for identifier[Type](...) explicit-type-argument call
sites across all *.wisp files outside docs/ found ~20 sites (identity[int],
empty_list[int], add[float]/add[int], box.identity[int]/box.empty[string],
json.decode[int|float|bool|string]), all confined to testdata/golden/
(including the call_type_args* and call_type_args_qualified fixtures, left
untouched) and examples/ function declarations; every hit lives in a surface
already compiled by go test/the golden suite, so no over-broad simplification
was applied and a regression would have failed Task 7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…turn

Adversarial implementation review (Significant, lens A+B): SC-005 requires
byte-identity for every SC-001..004 position, but array-element had only a
type-check test and the golden fixture covers dict-value (not array-element);
the per-position codegen comment was inaccurate. Add direct inferred-vs-explicit
byte-identity cases for array-element and dict-value and correct the comment.

Also close the SC-010 Minor: assert parameter and return-type annotations stay
grammar-mandatory (parse errors), not just the let case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mitchellnemitz
mitchellnemitz merged commit aa0d67b into main Jul 24, 2026
8 checks passed
@mitchellnemitz
mitchellnemitz deleted the impl-2026-07-24-context-directed-type-inference branch July 24, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant