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
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
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
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
@@ -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
`DataType` and `Null`, which each serializer writes as the load check proved it; a
`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
split the API into packages.
- **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()
pkg, err := build.ImportDir(".", 0)
if err != nil {
t.Fatalf("read the package directory: %v", err)
}
return pkg.GoFiles
return pkg
}
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()
fset := token.NewFileSet()
var files []*ast.File
for _, name := range goFiles(t) {
for _, name := range names {
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
if err != nil {
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
// 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 {
names := declaredFuncs(files)
for _, f := range files {
+21 -13
View File
@@ -2,33 +2,41 @@ package fejkdata
import (
"go/ast"
"slices"
"sort"
"strings"
"testing"
)
// TestNoFunctionSpellsAMethod holds a call to one unit. A method is written with
// its receiver and a function bare, so one name on both reads alike at a call site
// and greps as one unit; two types may share a method name, since the receiver
// standing before it says which.
// TestNoFunctionSpellsAMethod holds a call to one unit, over the tests as well,
// since a test file is this package too.
func TestNoFunctionSpellsAMethod(t *testing.T) {
_, files := sourceFiles(t)
funcs, methods := declaredCalls(files)
funcs, methods := declaredCalls(namespaceFiles(t))
names := make([]string, 0, len(funcs))
for name := range funcs {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
if recv := methods[name]; recv != "" {
t.Errorf("func %s and %s.%s are two units under one name; name them apart", name, recv, name)
if recv := methods[name]; len(recv) > 0 {
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
// it declares as a method with one type declaring it.
func declaredCalls(files []*ast.File) (funcs map[string]bool, methods map[string]string) {
funcs, methods = map[string]bool{}, map[string]string{}
// namespaceFiles is every file declaring into the package namespace, its in-package
// tests included.
func namespaceFiles(t *testing.T) []*ast.File {
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 _, decl := range f.Decls {
d, isFunc := decl.(*ast.FuncDecl)
@@ -36,7 +44,7 @@ func declaredCalls(files []*ast.File) (funcs map[string]bool, methods map[string
continue
}
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
}
funcs[d.Name.Name] = true
+1 -1
View File
@@ -211,7 +211,7 @@ func TestLayoutNamesEverySourceFile(t *testing.T) {
listed[name] = true
}
}
for _, name := range goFiles(t) {
for _, name := range packageDir(t).GoFiles {
if !listed[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])
}
case *choice:
facts[prefix] = reads(n)
facts[prefix] = readsFact(n)
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 {
facts[join(prefix, name)] = "\tstring"
}
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 {
for _, c := range columns {
fact := "\t" + c.DataType.String()
@@ -85,8 +85,8 @@ func tableFacts(t *table) string {
return b.String()
}
// reads names the categories any template under n references, sorted.
func reads(n node) string {
// readsFact names the categories any template under n references, sorted.
func readsFact(n node) string {
set := map[string]bool{}
var collect func(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
}
func (c *formatOps) hold(a arm, label string) {
func (c *formatOps) holdName(a arm, label string) {
if c.held == nil {
c.held = map[string]bool{}
c.holder = map[string]string{}
@@ -396,7 +396,7 @@ func (c *formatOps) function(body string, refs map[string]refBinding) {
var operands []arm
for _, operand := range tokenOperands(body) {
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)
}
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)
for _, a := range arms {
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})
+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,
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
which each file carries both a load-time fence and render-time machinery, so no file
name says whether a unit runs at `New` or at `Fake`.
nine naming `hold.go`, `draw.go` — now `holdset.go` and `drawfence.go` — and
`family.go` first for reading cost: 1,138 lines in which each file carries both a
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: