apache/beam · error

substituting type : type not bound

Error message

substituting type %v: type not bound

What it means

typex.Substitute replaces every universal (variable) type in a FullType using the binding map produced by Bind. If a type variable's name is absent from the map, substitution cannot proceed and this error is returned.

Solutions

  1. Build the map via typex.Bind over the same models used to define the type variables, rather than hand-writing it.
  2. Check the variable name in the message against your map keys; fix the spelling or add the missing binding.
  3. If the type should have no free variables, verify it was constructed with concrete types, not typex.T/X placeholders.
  4. Call SubstituteFull on a small test FullType with the same map to isolate which variable is unbound.

Example fix

// before
m := map[string]reflect.Type{"x": reflect.TypeOf("")}
typex.Substitute(t, m) // variable is named "T"

// after
m := map[string]reflect.Type{"T": reflect.TypeOf("")}
typex.Substitute(t, m)
Defensive patterns

Strategy: validation

Validate before calling

func allBound(t typex.FullType, m map[string]reflect.Type) bool {
    switch t.Class() {
    case typex.Universal:
        _, ok := m[t.Type().Name()]
        return ok
    default:
        for _, c := range t.Components() {
            if !allBound(c, m) {
                return false
            }
        }
        return true
    }
}

Type guard

func isFullyConcrete(t typex.FullType) bool {
    if t.Class() == typex.Universal {
        return false
    }
    for _, c := range t.Components() {
        if !isFullyConcrete(c) {
            return false
        }
    }
    return true
}

Try / catch

out, err := typex.Substitute(t, m)
if err != nil {
    return fmt.Errorf("substitution: %w (check map keys against variable names)", err)
}

Prevention

When it happens

Trigger: Calling typex.Substitute(t, m) where t contains a Universal type whose Name() has no key in m — usually because Bind was never run on a type containing that variable, or the map came from a Bind over a different model set.

Common situations: Users hand-construct substitution maps (map[string]reflect.Type) with a typo in the variable name (e.g. "x" vs "T"); or reuse a Bind result across FullTypes that reference additional variables; seen when building custom coders or graph rewriting in Beam Go.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/049efaf83454d2ed. Report an issue: GitHub.

Appendix: source

Thrown at sdks/go/pkg/beam/core/typex/fulltype.go:416

func Substitute(list []FullType, m map[string]reflect.Type) ([]FullType, error) {
	var ret []FullType
	for _, t := range list {
		repl, err := substitute(t, m)
		if err != nil {
			return nil, err
		}
		ret = append(ret, repl)
	}
	return ret, nil
}

func substitute(t FullType, m map[string]reflect.Type) (FullType, error) {
	switch t.Class() {
	case Universal:
		name := t.Type().Name()
		repl, ok := m[name]
		if !ok {
			return nil, errors.Errorf("substituting type %v: type not bound", name)
		}
		return New(repl), nil
	case Container:
		comp, err := substituteList(t.Components(), m)
		if err != nil {
			return nil, err
		}
		if IsList(t.Type()) {
			return New(reflect.SliceOf(comp[0].Type()), comp...), nil
		}
		return nil, errors.Errorf("unexpected aggregate %v, only slices allowed", t)
	case Composite:
		comp, err := substituteList(t.Components(), m)
		if err != nil {
			return nil, err
		}
		return New(t.Type(), comp...), nil

View on GitHub (pinned to 12126d8942)