Skip to content

initial commit of bingen#1

Closed
ameijer wants to merge 9 commits into
mainfrom
atm/commit-bingen
Closed

initial commit of bingen#1
ameijer wants to merge 9 commits into
mainfrom
atm/commit-bingen

Conversation

@ameijer
Copy link
Copy Markdown
Member

@ameijer ameijer commented Apr 20, 2026

open sourcing of OpenCost bingen

Copilot AI review requested due to automatic review settings April 20, 2026 12:55
@github-advanced-security
Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Comment thread tests/opencost/window.go
}

hrs64, _ := strconv.ParseInt(match[2], 10, 64)
hrs := sig * int(hrs64)
Comment thread tests/opencost/window.go
hrs := sig * int(hrs64)

mins64, _ := strconv.ParseInt(match[3], 10, 64)
mins := sig * int(mins64)
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Initial open-source drop of bingen, a Go code generator for binary codecs driven by @bingen:* annotations, including CI configuration and a set of test fixtures/packages.

Changes:

  • Add CLI (cmd/bingen) plus generator/type/annotation parsing implementation under pkg/.
  • Add pkg/util buffer + pooling utilities used by generated codecs.
  • Add extensive tests/ fixtures and generated codec outputs, plus GitHub Actions workflows and Sonar configuration.

Reviewed changes

Copilot reviewed 49 out of 52 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
tests/var_test.go Tests for variable-name generation behavior.
tests/streaming/types.go Streaming test model types.
tests/opencost/window.go OpenCost window/time utilities used as a generation fixture.
tests/opencost/bingen.go @bingen annotations + go:generate directive for opencost fixture.
tests/opencost/assetprops.go OpenCost asset property types (fixture).
tests/opencost/allocationprops.go OpenCost allocation property types (fixture).
tests/indent_test.go Basic indentation test for generator indent helper.
tests/generator_test.go Integration tests that run the generator against fixtures.
tests/failing/widget.go Negative test fixture for invalid field annotations.
tests/failing/bingen.go @bingen annotations for failing fixture package.
tests/containerv2/containerv2_codecs.go Generated codec output for containerv2 fixture.
tests/containerv2/container.go Versioned container fixture type (with migrate hook).
tests/containerv2/bingen.go @bingen annotations for containerv2 fixture package.
tests/container/container_codecs.go Generated codec output for container fixture.
tests/container/container.go Container fixture type (v1).
tests/container/bingen.go @bingen annotations for container fixture package.
tests/aliases/bingen.go @bingen annotations for alias fixture package.
tests/aliases/aliases.go Alias/typedef fixture types.
sonar-project.properties Sonar configuration for sources/tests/coverage exclusions.
pkg/util/stringutil/stringutil.go String interning/banking utility for reducing duplicate allocations.
pkg/util/bufferpool_test.go Unit tests for buffer pooling behavior and concurrency.
pkg/util/bufferpool.go Tiered sync.Pool byte-slice pooling implementation.
pkg/util/bufferhelper.go Low-level binary read/write helpers for primitive types.
pkg/util/buffer.go Main Buffer type providing binary read/write primitives + string optimizations.
pkg/types/types.go AST-based type collection and generator IR creation.
pkg/meta/annotationset.go Parsing/collection of global @bingen annotations into version sets.
pkg/meta/annotation.go Annotation parsing primitives and constants.
pkg/generator/vars/vars.go Variable naming helpers and target formatting utilities.
pkg/generator/support.go Generated-code templates for config, string tables, stream helpers, etc.
pkg/generator/streaming.go Generator support for producing streamable decoders.
pkg/generator/indent.go Indentation helper used by generator output formatting.
pkg/generator/debug.go Debug scope utilities for generator output.
pkg/generator/contextscope.go Scope abstraction for indentation-aware writing.
pkg/generator/context.go Generator context implementation (vars, indentation, options).
go.mod Declares module path and Go version.
cmd/bingen/bingen.go CLI entrypoint that loads types and writes generated codec source.
README.md Public documentation for installation, usage, and feature set.
.gitignore Basic ignore rules for IDEs/binaries/coverage output.
.github/workflows/vulnerability-scan.yaml Trivy vulnerability scanning workflow.
.github/workflows/update-go-version.yaml Scheduled automation to bump Go version in go.mod.
.github/workflows/sonar.yaml SonarCloud scan workflow triggered from Build/Test.
.github/workflows/scorecard.yml OSSF Scorecard workflow for supply-chain checks.
.github/workflows/golangci-lint.yaml Lint workflow via golangci-lint action.
.github/workflows/format-check.yaml gofmt enforcement workflow.
.github/workflows/codeql.yaml CodeQL static analysis workflow.
.github/workflows/build-test.yaml Build/test/coverage workflow for Go packages.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +136 to +140
if defaultValue == "" {
return
}
target := ctx.NextVar()
ctx.Writeln("var %s %s = %s // default", target, t.TypeName(), defaultValue)
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In StreamSetTypeDefault, non-basic non-nilable fields with an empty default return without yielding anything. That means version-gated fields can be silently omitted from the stream, contradicting the "stream each field" contract and making consumers handle missing fields inconsistently. Consider always yielding the type’s zero value (or an explicit sentinel) when the field is absent, even if no default is provided.

Suggested change
if defaultValue == "" {
return
}
target := ctx.NextVar()
ctx.Writeln("var %s %s = %s // default", target, t.TypeName(), defaultValue)
target := ctx.NextVar()
if defaultValue == "" {
ctx.Writeln("var %s %s // default", target, t.TypeName())
} else {
ctx.Writeln("var %s %s = %s // default", target, t.TypeName(), defaultValue)
}

Copilot uses AI. Check for mistakes.
Comment thread tests/opencost/window.go
Comment on lines +167 to +183
if window == "month" {
// now
start := now
// 00:00 today, accounting for timezone offset
start = start.Truncate(time.Hour * 24)
start = start.Add(offset)
// 00:00 1st of this month
start = start.Add(-24 * time.Hour * time.Duration(start.Day()-1))

end := now

return NewWindow(&start, &end), nil
}

if window == "month" {
// now
start := now
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two identical if window == "month" branches back-to-back. The second one is dead/unreachable and likely meant to be a different named window. Remove the duplicate or fix the condition to the intended window keyword.

Copilot uses AI. Check for mistakes.
Comment thread tests/opencost/window.go
Comment on lines +430 to +434
func (w Window) MarshalJSON() ([]byte, error) {
buffer := bytes.NewBufferString("{")
buffer.WriteString(fmt.Sprintf("\"start\":\"%s\",", w.start.Format(time.RFC3339)))
buffer.WriteString(fmt.Sprintf("\"end\":\"%s\"", w.end.Format(time.RFC3339)))
buffer.WriteString("}")
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MarshalJSON dereferences w.start and w.end without nil checks, but Window explicitly allows open intervals (nil start/end). Calling MarshalJSON on an open window will panic. Either return an error for open windows or encode nil/omitted start/end safely.

Copilot uses AI. Check for mistakes.
Comment on lines +281 to +292
var labelStrs []string
for k, prop := range p.Labels {
labelStrs = append(labelStrs, fmt.Sprintf("%s:%s", k, prop))
}
strs = append(strs, fmt.Sprintf("Labels:{%s}", strings.Join(strs, ",")))

var AnnotationStrs []string
for k, prop := range p.Annotations {
AnnotationStrs = append(AnnotationStrs, fmt.Sprintf("%s:%s", k, prop))
}
strs = append(strs, fmt.Sprintf("Annotations:{%s}", strings.Join(strs, ",")))

Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AllocationProperties.String() builds labelStrs/AnnotationStrs but then formats Labels/Annotations using strings.Join(strs, ",") instead of joining the label/annotation slices. This produces incorrect output (and for Labels/Annotations currently reuses the already-built strs). Use the dedicated labelStrs and AnnotationStrs slices when formatting these sections.

Copilot uses AI. Check for mistakes.
Comment thread tests/generator_test.go
Comment on lines +35 to +45
dirs := strings.Split(wd, "/")
if dirs[len(dirs)-1] == "tests" {
testDir = wd
return
}

for i := len(dirs); i >= 0; i-- {

tDir := filepath.Join(filepath.Join(dirs[:i]...), "tests")
if dirExists(tDir) {
testDir = tDir
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getTestDir splits the working directory using strings.Split(wd, "/"), which is not portable (will fail on Windows paths). Use filepath utilities (e.g., filepath.Dir/filepath.SplitList or splitting on os.PathSeparator) to walk up directories in an OS-agnostic way.

Copilot uses AI. Check for mistakes.
Comment thread tests/opencost/window.go
Comment on lines +341 to +345
func (w Window) Duration() time.Duration {
if w.IsOpen() {
// TODO test
return time.Duration(math.Inf(1.0))
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Window.Duration() attempts to represent an open window duration as time.Duration(math.Inf(1.0)). Converting +Inf to an integer-based time.Duration is not meaningful and can yield overflow/implementation-dependent results. Consider returning a separate (time.Duration, bool)/error for open windows, or using a well-defined sentinel (e.g. math.MaxInt64) documented as "infinite".

Copilot uses AI. Check for mistakes.
Comment thread tests/opencost/window.go
Comment on lines +590 to +593
thanosDur := 3 * time.Hour
if offset < thanosDur && true {
diff := thanosDur - offset
offset += diff
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if offset < thanosDur && true has a redundant && true, which makes this look like a leftover debug flag or incomplete conditional. If this is intended to be gated (e.g., only when using Thanos), replace the constant with a real condition or remove it.

Copilot uses AI. Check for mistakes.
Comment thread tests/opencost/window.go
Comment on lines +632 to +634
func DurationOffsetStrings(_ time.Duration, _ time.Duration) (string, string) {
return "", ""
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DurationOffsetStrings is currently a stub that always returns empty strings. This breaks DurationOffsetForPrometheus and Window.DurationOffsetStrings, which will produce invalid query fragments. Implement the actual formatting logic or remove/guard the callers so this can’t return empty results silently.

Copilot uses AI. Check for mistakes.
Comment thread pkg/util/bufferpool.go
Comment on lines +17 to +22
for i := 0; i < 17; i++ {
length := 1 << i
bp.pools[i].New = func() any {
return make([]byte, length)
}
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newBufferPool captures the loop-local length variable in the sync.Pool.New closure. Because length is reassigned each iteration, all pools will end up allocating the same (final) size slice, defeating tiering and wasting memory. Capture the value per-iteration (e.g., assign to a new local and close over that) before setting New.

Copilot uses AI. Check for mistakes.
}

fiScope := ctx.PushScope("fi = BingenFieldInfo{")
fiScope.Writeln("Type: reflect.TypeFor[%s](),", f.Type.Name())
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BingenFieldInfo.Type is built using reflect.TypeFor[%s]() with f.Type.Name(). For pointer fields, Name() does not include the *, so the reflected type will be wrong (e.g. T instead of *T). Use the pointer-qualified type name (e.g. f.Type.TypeName()) when generating this reflect type.

Suggested change
fiScope.Writeln("Type: reflect.TypeFor[%s](),", f.Type.Name())
fiScope.Writeln("Type: reflect.TypeFor[%s](),", f.Type.TypeName())

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 20, 2026 13:12
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 49 out of 52 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/generator/context.go
Comment on lines +99 to +115
func (gcf *genContextFactory) NewContext(opts *types.GenerateTypeOpts) GeneratorContext {
if opts == nil {
opts = &types.GenerateTypeOpts{}
}

return &genContext{
b: gcf.buffer,
i: NewIndent(4),
vsc: fmt.Sprintf("%sCodecVersion", titleName(opts.SetName)),
opts: opts,
loops: vars.NewAlphaVarNames(vars.SkipAllBut(vars.I, vars.J)...),
maps: vars.NewAlphaVarNames(vars.SkipAllBut(vars.Z, vars.V)...),
vars: vars.NewAlphaVarNames(vars.I, vars.J, vars.K, vars.Z, vars.V),
oks: vars.NewAlphaVarNames(),
errs: vars.NewAlphaVarNames(),
errHandler: gcf.errorHandler,
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NewContext ignores the indentionSize provided to NewGeneratorContextFactory and always uses NewIndent(4). This makes the factory parameter ineffective and prevents callers from controlling indentation. Use gcf.indentionSize when constructing the Indent implementation.

Copilot uses AI. Check for mistakes.
Comment thread tests/opencost/window.go
Comment on lines +181 to +193
if window == "month" {
// now
start := now
// 00:00 today, accounting for timezone offset
start = start.Truncate(time.Hour * 24)
start = start.Add(offset)
// 00:00 1st of this month
start = start.Add(-24 * time.Hour * time.Duration(start.Day()-1))

end := now

return NewWindow(&start, &end), nil
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two identical if window == "month" blocks; the second one is dead code (the first one returns). Remove the duplicate block to avoid confusion and keep parsing logic maintainable.

Copilot uses AI. Check for mistakes.
Comment thread tests/opencost/window.go
Comment on lines +590 to +595
thanosDur := 3 * time.Hour
if offset < thanosDur && true {
diff := thanosDur - offset
offset += diff
duration -= diff
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if offset < thanosDur && true { contains a redundant && true, which makes the condition harder to read and suggests a leftover debug toggle. Remove && true (or replace with a real feature flag/config check if intended).

Copilot uses AI. Check for mistakes.
Comment thread tests/indent_test.go
Comment on lines +10 to +15
func TestBasicIndent(t *testing.T) {
i := generator.NewIndent(4)
i.OutN(2)
i.In()
fmt.Printf("%sTest\n", i)
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only prints the indentation result and has no assertions, so it can pass even if Indent is broken. Consider asserting on the expected indentation string (or using t.Log if the output is only diagnostic).

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +4 to +8
### Install
Go modules are verified against a public database on known modules. Since this repository is private, you have to explicitly instruct `go get` to bypass public verification. To do this, set the `GOPRIVATE` environment variable to include this repository. For example:
```bash
export GOPRIVATE=github.com/opencost/bingen
```
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README install instructions state the repository is private and require setting GOPRIVATE, but the PR description says this is being open sourced. Update the README to reflect the intended public installation flow (or clarify if parts are still private) to avoid confusing new users.

Copilot uses AI. Check for mistakes.
Comment thread tests/var_test.go
Comment on lines +19 to +23
r := b.String()
if r != "" {
t.Errorf(r)
return false
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

t.Errorf(r) treats r as a format string; if r ever contains % it will be interpreted as formatting verbs and can cause confusing output. Use t.Error(r) or t.Errorf("%s", r) to log the built message safely.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +41
// create the key and value using the func (the key could be deallocated later)
value := f()
sb.m[value] = value
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoadOrStoreFunc stores the computed value into the map using value as the key (sb.m[value] = value) instead of using the provided key. This breaks de-duplication (subsequent lookups by the original key will miss) and can cause unbounded growth. Store under key (or consistently use value for both lookup and storage, but then the initial existence check must also use value).

Suggested change
// create the key and value using the func (the key could be deallocated later)
value := f()
sb.m[value] = value
// create the value using the func; store it under the provided lookup key
value := f()
sb.m[key] = value

Copilot uses AI. Check for mistakes.
Comment thread tests/opencost/window.go
Comment on lines +429 to +436
// TODO:CLEANUP make this unmarshalable (make Start and End public)
func (w Window) MarshalJSON() ([]byte, error) {
buffer := bytes.NewBufferString("{")
buffer.WriteString(fmt.Sprintf("\"start\":\"%s\",", w.start.Format(time.RFC3339)))
buffer.WriteString(fmt.Sprintf("\"end\":\"%s\"", w.end.Format(time.RFC3339)))
buffer.WriteString("}")
return buffer.Bytes(), nil
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MarshalJSON dereferences w.start/w.end unconditionally (w.start.Format(...), w.end.Format(...)). For open windows (where either is nil), this will panic during JSON marshaling. Handle nil start/end explicitly (e.g., encode as null/empty string, or return an error for open windows).

Copilot uses AI. Check for mistakes.
Comment thread tests/opencost/window.go
Comment on lines +526 to +529
func (w Window) Set(start, end *time.Time) {
w.start = start
w.end = end
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set uses a value receiver (func (w Window) Set(...)), so assignments to w.start/w.end only modify a copy and have no effect on the caller. Change the receiver to *Window (or return the updated Window) so the method actually updates the instance.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants