Widen the naming fence to the tests, and record what each stem names

This commit is contained in:
2026-09-21 21:50:16 +02:00
committed by Lillem4n
parent d128621fc1
commit a52adc6b0d
7 changed files with 55 additions and 30 deletions
+12 -1
View File
@@ -1042,7 +1042,7 @@ the Development section below, and who ships a register the four above then draw
all, and making them anyway cost about a fifth of the cheapest render, so it makes all, and making them anyway cost about a fifth of the cheapest render, so it makes
them on the first read instead, at two heap allocations for a render that does share them on the first read instead, at two heap allocations for a render that does share
a draw. The allocation gate over a repeat of a reference path and over a named draw a draw. The allocation gate over a repeat of a reference path and over a named draw
group prices that, and pins the two measures that keep a draw set off the heap. group prices that, and pins the two measures that keep a hold set off the heap.
- **A category never references itself, and a record's fences run at load.** A category - **A category never references itself, and a record's fences run at load.** A category
is one unit: a reference back into it — `{/users.first}` inside `users` — describes a is one unit: a reference back into it — `{/users.first}` inside `users` — describes a
draw other than the fields beside it, so `New` refuses it and the sibling path stays draw other than the fields beside it, so `New` refuses it and the sibling path stays
@@ -1077,6 +1077,17 @@ the Development section below, and who ships a register the four above then draw
- **`Column` carries text, not a Go value.** `Value` is the rendered string beside - **`Column` carries text, not a Go value.** `Value` is the rendered string beside
`DataType` and `Null`, which each serializer writes as the load check proved it; a `DataType` and `Null`, which each serializer writes as the load check proved it; a
`Value any` would hand every caller a type switch. `Value any` would hand every caller a type switch.
- **`hold` names what a render keeps, `draw` what it draws.** Goal 2 wants a name to
reach one unit: `hold`, `holdSet` and `renderScope` are what a render holds while it
renders, and `draw`, `drawGroup` and `drawfence.go`'s units are the draws and the
load fence over them. So `holdset.go` is fenced by `drawfence.go`, which does not
pair by name as `hold.go` and `holdfence.go` do: that fence is over reference draws,
wherever a render keeps them. Valid while the load fences sit in files of their own.
- **A function may not spell a method; two types may.** A call writes a method with
its receiver and a function bare, so one name on both greps as one unit, which is
the reaching cost goal 2 counts, while the receiver before a shared method name
says which type answers. A type is outside the rule, never being called.
`TestNoFunctionSpellsAMethod` holds it over the package, its tests included.
- **The package stays flat.** Go ties a package to one directory, so folders would - **The package stays flat.** Go ties a package to one directory, so folders would
split the API into packages. split the API into packages.
- **The performance gate asserts allocations, not wall-clock time.** `AllocsPerRun` - **The performance gate asserts allocations, not wall-clock time.** `AllocsPerRun`
+9 -4
View File
@@ -47,20 +47,25 @@ func namesAPass(t *testing.T, where, text string, declared, funcs map[string]boo
} }
} }
func goFiles(t *testing.T) []string { func packageDir(t *testing.T) *build.Package {
t.Helper() t.Helper()
pkg, err := build.ImportDir(".", 0) pkg, err := build.ImportDir(".", 0)
if err != nil { if err != nil {
t.Fatalf("read the package directory: %v", err) t.Fatalf("read the package directory: %v", err)
} }
return pkg.GoFiles return pkg
} }
func sourceFiles(t *testing.T) (*token.FileSet, []*ast.File) { func sourceFiles(t *testing.T) (*token.FileSet, []*ast.File) {
t.Helper()
return parseFiles(t, packageDir(t).GoFiles)
}
func parseFiles(t *testing.T, names []string) (*token.FileSet, []*ast.File) {
t.Helper() t.Helper()
fset := token.NewFileSet() fset := token.NewFileSet()
var files []*ast.File var files []*ast.File
for _, name := range goFiles(t) { for _, name := range names {
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments) f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
if err != nil { if err != nil {
t.Fatalf("parse %s: %v", name, err) t.Fatalf("parse %s: %v", name, err)
@@ -88,7 +93,7 @@ func vocabulary(t *testing.T, files []*ast.File) string {
} }
// declaredSymbols is every name the package declares at the top level, a method and // declaredSymbols is every name the package declares at the top level, a method and
// a struct field keyed under its type: draws.pin, tableRead.whole. // a struct field keyed under its type: hold.pin, tableRead.whole.
func declaredSymbols(files []*ast.File) map[string]bool { func declaredSymbols(files []*ast.File) map[string]bool {
names := declaredFuncs(files) names := declaredFuncs(files)
for _, f := range files { for _, f := range files {
+21 -13
View File
@@ -2,33 +2,41 @@ package fejkdata
import ( import (
"go/ast" "go/ast"
"slices"
"sort" "sort"
"strings"
"testing" "testing"
) )
// TestNoFunctionSpellsAMethod holds a call to one unit. A method is written with // TestNoFunctionSpellsAMethod holds a call to one unit, over the tests as well,
// its receiver and a function bare, so one name on both reads alike at a call site // since a test file is this package too.
// and greps as one unit; two types may share a method name, since the receiver
// standing before it says which.
func TestNoFunctionSpellsAMethod(t *testing.T) { func TestNoFunctionSpellsAMethod(t *testing.T) {
_, files := sourceFiles(t) funcs, methods := declaredCalls(namespaceFiles(t))
funcs, methods := declaredCalls(files)
names := make([]string, 0, len(funcs)) names := make([]string, 0, len(funcs))
for name := range funcs { for name := range funcs {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
if recv := methods[name]; recv != "" { if recv := methods[name]; len(recv) > 0 {
t.Errorf("func %s and %s.%s are two units under one name; name them apart", name, recv, name) t.Errorf("func %s and %s are two units under one name; name them apart", name, strings.Join(recv, " and "))
} }
} }
} }
// declaredCalls is every name the package declares as a function, and every name // namespaceFiles is every file declaring into the package namespace, its in-package
// it declares as a method with one type declaring it. // tests included.
func declaredCalls(files []*ast.File) (funcs map[string]bool, methods map[string]string) { func namespaceFiles(t *testing.T) []*ast.File {
funcs, methods = map[string]bool{}, map[string]string{} t.Helper()
pkg := packageDir(t)
_, files := parseFiles(t, slices.Concat(pkg.GoFiles, pkg.TestGoFiles))
return files
}
// declaredCalls is what each name is called as: a function, and the methods
// spelling it under the types declaring them.
func declaredCalls(files []*ast.File) (funcs map[string]bool, methods map[string][]string) {
funcs, methods = map[string]bool{}, map[string][]string{}
for _, f := range files { for _, f := range files {
for _, decl := range f.Decls { for _, decl := range f.Decls {
d, isFunc := decl.(*ast.FuncDecl) d, isFunc := decl.(*ast.FuncDecl)
@@ -36,7 +44,7 @@ func declaredCalls(files []*ast.File) (funcs map[string]bool, methods map[string
continue continue
} }
if recv := receiverType(d.Recv); recv != "" { if recv := receiverType(d.Recv); recv != "" {
methods[d.Name.Name] = recv methods[d.Name.Name] = append(methods[d.Name.Name], recv+"."+d.Name.Name)
continue continue
} }
funcs[d.Name.Name] = true funcs[d.Name.Name] = true
+1 -1
View File
@@ -211,7 +211,7 @@ func TestLayoutNamesEverySourceFile(t *testing.T) {
listed[name] = true listed[name] = true
} }
} }
for _, name := range goFiles(t) { for _, name := range packageDir(t).GoFiles {
if !listed[name] { if !listed[name] {
t.Errorf("the README Layout names no %s, so nothing says what it holds", name) t.Errorf("the README Layout names no %s, so nothing says what it holds", name)
} }
+5 -5
View File
@@ -42,14 +42,14 @@ func shippedShape(f *Generator) string {
walk(join(prefix, name), n.children[name]) walk(join(prefix, name), n.children[name])
} }
case *choice: case *choice:
facts[prefix] = reads(n) facts[prefix] = readsFact(n)
case *table: case *table:
facts[prefix] = "\tformat " + strconv.Quote(n.format.format) + tableFacts(n) + reads(n) facts[prefix] = "\tformat " + strconv.Quote(n.format.format) + tableFacts(n) + readsFact(n)
for _, name := range n.columns { for _, name := range n.columns {
facts[join(prefix, name)] = "\tstring" facts[join(prefix, name)] = "\tstring"
} }
case *template: case *template:
facts[prefix] = "\tformat " + strconv.Quote(n.format) + reads(n) facts[prefix] = "\tformat " + strconv.Quote(n.format) + readsFact(n)
if _, columns, err := recordOf(n); err == nil { if _, columns, err := recordOf(n); err == nil {
for _, c := range columns { for _, c := range columns {
fact := "\t" + c.DataType.String() fact := "\t" + c.DataType.String()
@@ -85,8 +85,8 @@ func tableFacts(t *table) string {
return b.String() return b.String()
} }
// reads names the categories any template under n references, sorted. // readsFact names the categories any template under n references, sorted.
func reads(n node) string { func readsFact(n node) string {
set := map[string]bool{} set := map[string]bool{}
var collect func(node) var collect func(node)
collect = func(n node) { collect = func(n node) {
+3 -3
View File
@@ -369,7 +369,7 @@ type formatOps struct {
heldLocal bool // some held name is kept by the expansion itself rather than the render's draws heldLocal bool // some held name is kept by the expansion itself rather than the render's draws
} }
func (c *formatOps) hold(a arm, label string) { func (c *formatOps) holdName(a arm, label string) {
if c.held == nil { if c.held == nil {
c.held = map[string]bool{} c.held = map[string]bool{}
c.holder = map[string]string{} c.holder = map[string]string{}
@@ -396,7 +396,7 @@ func (c *formatOps) function(body string, refs map[string]refBinding) {
var operands []arm var operands []arm
for _, operand := range tokenOperands(body) { for _, operand := range tokenOperands(body) {
a := splitArm(operand, refs) a := splitArm(operand, refs)
c.hold(a, fmt.Sprintf("%s operand %q", name, operand)) c.holdName(a, fmt.Sprintf("%s operand %q", name, operand))
operands = append(operands, a) operands = append(operands, a)
} }
c.ops = append(c.ops, op{kind: 'b', call: builtins[name].prep(args), operands: operands}) c.ops = append(c.ops, op{kind: 'b', call: builtins[name].prep(args), operands: operands})
@@ -406,7 +406,7 @@ func (c *formatOps) field(body string, refs map[string]refBinding) {
arms := splitArms(body, refs) arms := splitArms(body, refs)
for _, a := range arms { for _, a := range arms {
if len(a.tail) > 0 { if len(a.tail) > 0 {
c.hold(a, "token {"+a.name+"}") c.holdName(a, "token {"+a.name+"}")
} }
} }
c.ops = append(c.ops, op{kind: 'f', arms: arms}) c.ops = append(c.ops, op{kind: 'f', arms: arms})
+4 -3
View File
@@ -15,9 +15,10 @@ scoring run names too little to steer one. A fresh run replaces the items below.
Scores, newest last: 5.9 on 2026-09-20 (Navigation 7.0, Locality 5.2, Shape 5.9, Scores, newest last: 5.9 on 2026-09-20 (Navigation 7.0, Locality 5.2, Shape 5.9,
Self-sufficiency 5.7), eight of nine seats capped by Locality or Self-sufficiency, all Self-sufficiency 5.7), eight of nine seats capped by Locality or Self-sufficiency, all
nine naming `hold.go`, `draw.go` and `family.go` first for reading cost: 1,138 lines in nine naming `hold.go`, `draw.go` — now `holdset.go` and `drawfence.go` — and
which each file carries both a load-time fence and render-time machinery, so no file `family.go` first for reading cost: 1,138 lines in which each file carries both a
name says whether a unit runs at `New` or at `Fake`. load-time fence and render-time machinery, so no file name says whether a unit runs at
`New` or at `Fake`.
This round, in order: This round, in order: