wavetermdev/waveterm · error

Map argument must be map[string]any

Error message

Map argument must be map[string]any

What it means

If the component function's single argument is a map, validateCFunc requires it to be exactly map[string]any (string keys, empty-interface values, no methods on the element type). Any other map shape — e.g. map[string]string or map[string]int — is rejected with "Map argument must be map[string]any". Pointer-to-map arguments are dereferenced before this check.

Source

Thrown at tsunami/engine/rootelem.go:259

	if rval.Kind() != reflect.Func {
		return fmt.Errorf("Component function must be a function")
	}
	rtype := rval.Type()
	if rtype.NumIn() != 1 {
		return fmt.Errorf("Component function must take exactly 1 argument")
	}
	if rtype.NumOut() != 1 {
		return fmt.Errorf("Component function must return exactly 1 value")
	}
	// first argument can be a map[string]any, or a struct, or ptr to struct (we'll reflect the value into it)
	arg1Type := rtype.In(0)
	if arg1Type.Kind() == reflect.Ptr {
		arg1Type = arg1Type.Elem()
	}
	if arg1Type.Kind() == reflect.Map {
		if arg1Type.Key().Kind() != reflect.String ||
			!(arg1Type.Elem().Kind() == reflect.Interface && arg1Type.Elem().NumMethod() == 0) {
			return fmt.Errorf("Map argument must be map[string]any")
		}
	} else if arg1Type.Kind() != reflect.Struct &&
		!(arg1Type.Kind() == reflect.Interface && arg1Type.NumMethod() == 0) {
		return fmt.Errorf("Component function argument must be map[string]any, struct, or any")
	}
	return nil
}

func (r *RootElem) RegisterComponent(name string, cfunc any) error {
	if err := validateCFunc(cfunc); err != nil {
		return err
	}
	r.CFuncs[name] = cfunc
	return nil
}

func callVDomFn(fnVal any, data vdom.VDomEvent) {
	if fnVal == nil {

View on GitHub (pinned to a4447c1563)

Solutions

  1. Change the parameter type to map[string]any and convert inside the function body
  2. If you want type safety, use a struct parameter instead of a typed map
  3. Remove named/aliased map types in favor of the literal map[string]any

Example fix

// before
root.RegisterComponent("table", func(cols map[string]string) any { ... })
// after
root.RegisterComponent("table", func(props map[string]any) any {
    cols := toStringMap(props["cols"]) // convert inside
    return renderTable(cols)
})
Defensive patterns

Strategy: validation

Validate before calling

t := reflect.TypeOf(cfunc)
if t != nil && t.Kind() == reflect.Func && t.NumIn() == 1 {
    at := t.In(0)
    if at.Kind() == reflect.Ptr { at = at.Elem() }
    if at.Kind() == reflect.Map && (at.Key().Kind() != reflect.String || at.Elem().Kind() != reflect.Interface) {
        return fmt.Errorf("map param must be map[string]any, got %v", t.In(0))
    }
}

Type guard

func takesMapStringAny(v any) bool {
    t := reflect.TypeOf(v)
    if t == nil || t.Kind() != reflect.Func || t.NumIn() != 1 { return false }
    at := t.In(0)
    if at.Kind() == reflect.Ptr { at = at.Elem() }
    return at.Kind() == reflect.Map && at.Key().Kind() == reflect.String &&
        at.Elem().Kind() == reflect.Interface && at.Elem().NumMethod() == 0
}

Try / catch

if err := root.RegisterComponent(name, fn); err != nil {
    return fmt.Errorf("map param must be map[string]any: %w", err)
}

Prevention

When it happens

Trigger: RegisterComponent with func(map[string]string) any; func(map[string]interface{}) on a named map type whose element is not plain any; a custom key type like map[int]any.

Common situations: Declaring typed maps to get compile-time safety, not realizing the reflection contract demands map[string]any; passing an aliased map type whose element kind is not empty interface.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/0ac5c3a167b1ca0c. Report an issue: GitHub.