ifacereturn

package
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 1, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package ifacereturn reports handwritten function and method declarations through whose results an interface reaches a caller.

Running it

In this monorepo, through the CLI, which is the only invocation an operator here should need:

candace style ifacereturn

That wraps tools/check-ifacereturn.sh, which knows where this monorepo's modules are and sweeps every one of them in the pinned toolchain container.

For consumers of the published candacelabs/csf module, who have no private CLI, the portable form is a go run from a module root:

go run github.com/candacelabs/csf/tools/ifacereturn/cmd/ifacereturn ./...

It is the wide half of a two-lane enforcement of house rule CS-8, "return concrete implementations, only accept interfaces". The narrow lane is a lexical gate that blocks CI and deliberately reports only a receiverless function returning a bare, repository-declared, I-prefixed interface — five restrictions that exist so that every finding has a fix. This lane is type-aware (go/types answers "is this an interface" exactly, where a lexer can only guess), it reads methods as well as functions, and it reports stdlib and third-party interfaces the lexer cannot see.

It therefore reports code that has been ruled correct — sealed sum types, hook implementations whose signature a func type fixes, pass-throughs of a library's own contract, and the method-position returns that survived the 2026-09-02 data-shaped re-audit. That is the point rather than a defect: operator directive, 2026-09-02, "i want a ci lint check that checks to see if there are any interface return types and flags them". Keeping the ruled cases visible is what the lane is for, so it flags and never blocks, and a ruled case is answered in the rule's own exceptions record rather than silenced here. Generated files are excluded using Go's standard generated header convention. They still contribute types when checking handwritten code.

Since 2026-09-03 it also descends: a result that is a struct, or a pointer, slice, array, map or channel of one, is walked field by field, and an interface reached that way is reported with the path that reaches it (`NewView returns View.Store, which is the interface subject.IStore`). Operator ruling of that date: returning a struct that carries interface-typed fields is returning those interfaces, and the wrapper is not a boundary. A visited set makes a recursive type terminate and maxDepth caps a wide one; a func-typed field is deliberately not followed, because a callback a struct carries is a contract rather than an implementation handed over.

Direction is a property of the walk rather than a filter on it: only RESULTS are ever walked, so the parameter position CS-8 asks an interface to live in is unreachable from here by construction.

The one exemption is error. Go's own contract, implemented by everything, matched by errors.Is and errors.As, and returned by roughly every function in this tree; CS-8 states it as the rule's single structural exemption and this lane inherits it verbatim.

Type parameters are not findings either, and that is a correctness fix rather than a policy: types.IsInterface reports true for a type parameter, because a type parameter's underlying type is its constraint. `func f[S any]() S` returns the caller's type, not an interface the callee chose, and reporting it would be reporting the constraint syntax.

Index

Constants

View Source
const Doc = "" /* 148-byte string literal not displayed */

Doc is the analyzer's one-line description, and the text `-help` prints.

Variables

View Source
var Analyzer = &analysis.Analyzer{
	Name: "ifacereturn",
	Doc:  Doc,
	Run:  run,
}

Analyzer is the go/analysis entry point. Run collects the same findings Inspect does — there is one walk, so the analyzer, the command and the tests cannot drift apart.

Functions

This section is empty.

Types

type Finding

type Finding struct {
	// Pos is the offending result type expression, so a report points at the
	// type rather than at the declaration's name.
	Pos token.Pos
	// Declaration names the function or method, receiver included, in the
	// form a reader would grep for: `NewRealClock`, `(RealClock).NewTimer`.
	Declaration string
	// Position is the 1-based index of this result in the result list, and
	// Arity is how many results there are, so a multi-result signature says
	// which one is meant.
	Position int
	Arity    int
	// Interface is the interface's full name, package path included
	// (`github.com/candacelabs/csf/services/warden.IClock`, `io.Reader`,
	// `any`).
	Interface string
	// Path is how the interface is reached from the result, when it is not the
	// result itself: `View.Store`, or `View.Inner.Store` one level deeper. It
	// is empty for a result that IS the interface.
	//
	// Operator ruling, 2026-09-03: returning a struct that carries
	// interface-typed fields is returning those interfaces. A caller that
	// receives the struct receives every interface reachable from it, so the
	// wrapper is not a boundary and this lane says so by naming the way
	// through.
	Path string
}

Finding is one result position through which an interface reaches a caller.

func Inspect

func Inspect(files []*ast.File, info *types.Info) []Finding

Inspect walks handwritten function and method declarations in files and returns one Finding per interface-typed result, in source order. Generated files remain available to the type checker but receive no findings.

info must be the type information for those files; a result whose type cannot be resolved is skipped rather than guessed at, because a lint that invents a finding from an unresolved type is worse than one that misses it.

func InspectIn

func InspectIn(files []*ast.File, info *types.Info, scope *types.Package) []Finding

InspectIn is Inspect told which package it is analyzing.

The package decides whether an UNEXPORTED field of a returned struct counts as handed over: in-package it is reachable, out-of-package it is not. Inspect passes nil, which is the conservative reading — only exported fields are followed — and the analyzer passes pass.Pkg.

func (Finding) Message

func (finding Finding) Message() string

Message is the diagnostic text for one finding, and the string the analyzer reports. It names the whole signature position because "returns an interface" is not actionable without knowing which result, and it names the path because "somewhere inside this struct" is not actionable either.

Directories

Path Synopsis
cmd
ifacereturn command
Command ifacereturn reports every function and method result in a Go module through which an interface reaches a caller — the result's own type, or an interface-typed field of a struct it hands back.
Command ifacereturn reports every function and method result in a Go module through which an interface reaches a caller — the result's own type, or an interface-typed field of a struct it hands back.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL