vitessio/vitess · error

interface %s implemented by %s (%s as %T) without ptr

Error message

interface %s implemented by %s (%s as %T) without ptr

What it means

asthelpergen walks all types that implement a target interface and, when onlyReferences is set, requires every implementing type to be a pointer or an interface (reference types). This error is thrown when a value (non-pointer) concrete type implements the interface, which the generators cannot emit helpers for. The message names the interface, the implementing type, and its underlying kind.

Source

Thrown at go/tools/asthelpergen/asthelpergen.go:175

	}
}

func findImplementations(scope *types.Scope, iff *types.Interface, impl func(types.Type) error) error {
	const onlyReferences = false

	for _, name := range scope.Names() {
		obj := scope.Lookup(name)
		if _, ok := obj.(*types.TypeName); !ok {
			continue
		}
		baseType := obj.Type()
		if types.Implements(baseType, iff) {
			if onlyReferences {
				switch tt := baseType.Underlying().(type) {
				case *types.Interface:
					// This is OK; interfaces are references
				default:
					return fmt.Errorf("interface %s implemented by %s (%s as %T) without ptr", iff.String(), baseType, tt.String(), tt)
				}
			}
			if types.TypeString(baseType, noQualifier) == visitableName {
				// skip the visitable interface
				continue
			}
			if err := impl(baseType); err != nil {
				return err
			}
			continue
		}
		pointerT := types.NewPointer(baseType)
		if types.Implements(pointerT, iff) {
			if err := impl(pointerT); err != nil {
				return err
			}
			continue
		}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Change the implementing type's methods to pointer receivers and use *T wherever the interface is required
  2. Remove the value-type implementer from the package set passed via options.Packages if it should not be generated
  3. If references are not actually required, set onlyReferences=false in the generator Options

Example fix

// before
func (n Ident) SQLNode() {}
// after
func (n *Ident) SQLNode() {}
Defensive patterns

Strategy: validation

Validate before calling

types := collector.Implementations(interfaceName)
for _, t := range types {
    if _, isPtr := t.(*types.Pointer); !isPtr {
        if _, isIface := t.Underlying().(*types.Interface); !isIface {
            return fmt.Errorf("value type %s implements %s; use pointer receiver", t, interfaceName)
        }
    }
}

Type guard

func isReferenceType(t types.Type) bool {
    switch t.Underlying().(type) {
    case *types.Interface, *types.Pointer:
        return true
    }
    return false
}

Prevention

When it happens

Trigger: Running GenerateASTHelpers for an interface where a struct (or other non-reference underlying type) implements the interface by value while onlyReferences=true is configured, e.g. `type MyStruct struct{...}` with value-receiver methods satisfying e.g. SQLNode.

Common situations: A developer adds a new AST type with value receivers; refactoring moves methods from pointer receivers to value receivers; generating helpers for a new interface without auditing all implementers.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/d2a45a63a9bc3ae7. Report an issue: GitHub.