diff --git a/README.md b/README.md index d7ac5d0..04922b6 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/doc_test.go b/doc_test.go index 4952312..77f9f23 100644 --- a/doc_test.go +++ b/doc_test.go @@ -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 { diff --git a/naming_test.go b/naming_test.go index 04604fc..075a58e 100644 --- a/naming_test.go +++ b/naming_test.go @@ -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 diff --git a/readme_test.go b/readme_test.go index cb300bb..49eebbc 100644 --- a/readme_test.go +++ b/readme_test.go @@ -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) } diff --git a/shape_test.go b/shape_test.go index 665eb41..6107d07 100644 --- a/shape_test.go +++ b/shape_test.go @@ -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) { diff --git a/template.go b/template.go index 41bf9b4..31f704d 100644 --- a/template.go +++ b/template.go @@ -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}) diff --git a/todo.md b/todo.md index a639190..a31fd4d 100644 --- a/todo.md +++ b/todo.md @@ -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: