Proposal: `GoFiles` — mixing hand-written and generated Go code

Cerrado
#1,100 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
25/100
Tipo de issue
Nueva funcionalidad
Claridad
Necesita aclaración
Estado de actividad
Activo
Stack tecnológico
go
Área
tooling

Línea de trabajo

Start from the cl design doc and issue #929 to learn the preload/load/compile phases. The proposal requires parsing GoFiles with go/parser in the preload phase and validating dotted TypeAlias entries against the manual symbol table. Done means llcppg skips emitting types marked manual and records them in llcppg.pub without overwriting hand-written files.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Architecture-Design configuration feature proposal

Summary

llcppg.cfg already accepts a GoFiles field: a list of hand-written Go files that live in the target directory next to the generated ones. This proposal pins down what that field means, so that manual and automatic conversion can reference each other in both directions:

  • manual → auto: hand-written files use types, functions and methods produced by llcppg.
  • auto → manual: generated code uses types that the user translated by hand. The user says so explicitly through TypeAlias, whose right-hand side may now point at a symbol declared in GoFiles.

Nothing is inferred. A C type is treated as manually translated only when the configuration says so.

Motivation

Automatic conversion cannot cover everything. Typical cases:

  • A C struct that is better represented by a hand-written Go type (raylib Color as a packed uint32 with helper methods; a C++ template instance that needs a custom wrapper).
  • Convenience helpers layered on the generated API (constructors, String() methods, iterators, error-returning wrappers).
  • Types llcppg cannot yet express (see the union, bit-field and virtual-method proposals).

Today users work around this by post-editing generated output, which is lost on the next run, or by encoding everything in config options. GoFiles gives a stable, regeneration-safe place for manual code.

TypeAlias is the existing escape hatch for the auto → manual direction, but it only says "this C type is that Go symbol". It cannot say "this C type is translated by hand, and the translation lives in this package". The raylib Color case (Color redeclared in this block) is exactly where that gap shows.

Configuration

{
  ...
  "GoFiles": ["color.go", "helpers.go"],
  "TypeAlias": {
    "Vector2": ".Vector2",
    "Color": ".Color",
    "Rectangle": ".Rect"
  }
}
Rules for GoFiles
  • Entries are plain file names (no directories, no globs in the first version) relative to the target directory.
  • Every file must exist, must be a .go file, and must declare the same package name as the generated package.
  • llcppg never overwrites, reformats, renames or deletes a GoFiles entry.
  • A generated output file whose name equals a GoFiles entry is an error.
  • Hand-written files are ordinary Go files. They may have any imports and build constraints; llcppg only needs their top-level declarations.

Semantics

1. Manual symbol table

During the preload phase llcppg parses every GoFiles entry with go/parser and collects the top-level declarations by name: types, functions, methods (keyed by receiver type and name), constants and variables.

Only syntax is needed here. Manual files may reference generated symbols that do not exist yet, so llcppg must not type-check them at this stage and must not fail on undefined names.

2. Marking a type as manually translated

TypeAlias keeps its shape: the key is the C/C++ type name, the value says which Go symbol it maps to. A value that starts with a dot refers to a symbol of the current package, so .Vec2 means Vec2 in the package being generated.

"TypeAlias": {
  "Vector2": ".Vector2",
  "Rectangle": ".Rect"
}
  • "Vector2": ".Vector2" — the manual type keeps the C name.
  • "Rectangle": ".Rect" — the manual type has a different name.

When the value is a dotted name and it resolves to a type declared in the manual symbol table, the C type is manually translated:

  • llcppg emits no declaration for it;
  • every reference to it in generated code (struct fields, parameters, return types, pointer targets) resolves to the manual type;
  • it is recorded in llcppg.pub (see 5).

Values without a leading dot keep today's meaning (an alias to an existing Go symbol, a builtin type, a generic or an instance). The leading dot is the only marker; there is no second field and no special value for "same name".

There is no implicit override. Declaring type Vector2 ... in a GoFiles entry does not by itself stop llcppg from generating Vector2. Without a TypeAlias entry the generator emits its own Vector2, and the duplicate declaration is reported (see Errors).

3. Generated code that depends on manual types

Generated code only needs the name of a manual type, so it can be emitted without type-checking the manual files. llcppg does not look inside the manual type, and the author is responsible for layout compatibility (see 6).

A generated method may be attached to a manual receiver type when the manual files do not define a method of the same name. This is how functions that belong to a manual type (for example, MethodCheck or NewCheck results on a hand-written ModuleObject) still get generated.

4. Manual files that depend on generated symbols

Manual files can use generated symbols freely, because both sets of files compile as one Go package, and Go has no declaration-order requirement. llcppg only has to guarantee that the generated files exist and that the names are the stable, documented ones.

After generation, llcppg runs an optional verification step (go/types over the whole package, equivalent to go vet). Errors in manual files that come from an unresolved or changed generated symbol are reported with the manual file and line, plus a hint that a generated name may have changed.

5. Public symbol table (llcppg.pub)

Manually translated types are written to llcppg.pub like generated ones, under their C name with the Go name from the TypeAlias entry. Dependent packages then resolve cross-package references to the manual type exactly as they would to a generated one. Only exported manual types can be recorded; mapping a C type to an unexported manual type is an error.

6. ABI responsibility

llcppg cannot verify that a hand-written type matches the C layout. That responsibility belongs to the author of the manual file. A later enhancement could emit a compile-time size assertion for manual types that stand in for C structs (see open questions).

Processing order

This fits into the three phases described in the cl design doc:

  1. Preload: parse GoFiles, build the manual symbol table, validate the TypeAlias entries that start with a dot.
  2. Load: read C/C++ declarations as today; apply TypeAlias to decide which declarations are suppressed and which names they resolve to.
  3. Compile: emit the remaining declarations; references to manually translated types resolve to the manual names; write llcppg.pub; optionally verify the whole package.

Errors and diagnostics

llcppg reports an error for:

  • a missing GoFiles entry, a non-.go entry, or an entry containing a path separator;
  • a package-name mismatch between a manual file and the generated package;
  • a GoFiles name that collides with a generated file name;
  • a TypeAlias value starting with a dot whose target is not declared in GoFiles (the message names the entry, so a typo or a file missing from the list is easy to spot);
  • a dotted target that is not a type, or is unexported;
  • a generated function, method, constant or variable whose name collides with a manual declaration (the message lists both locations);
  • a generated type that collides with a manual type because no TypeAlias entry marks it as manually translated;
  • a manual file that fails to parse.

Example

color.go (hand-written):

package raylib

type Color uint32

func RGBA(r, g, b, a uint8) Color {
	return Color(uint32(r) | uint32(g)<<8 | uint32(b)<<16 | uint32(a)<<24)
}

func (c Color) R() uint8 { return uint8(c) }

With

"GoFiles": ["color.go"],
"TypeAlias": { "Color": ".Color" }

llcppg skips the C struct Color, and generated functions such as ClearBackground(color Color) take the manual Color. The Color redeclared in this block failure in the raylib test case goes away because the user states which of the two declarations is meant to exist.

Alternatives considered

  • Implicit override: a manual declaration with the same name silently replaces the generated one. Rejected. The same name in two places may be an accident, and silently dropping generated code is hard to notice. An explicit entry also lets llcppg validate that the manual type really exists.
  • A separate field such as ManualTypes (a list of names, or a map from C name to Go name). Clearer wording, but it overlaps with TypeAlias, so the mapping would be configured in two places. A list also cannot express renaming without extra syntax.
  • Grouping types under each GoFiles entry ({"File": "color.go", "Types": [...]}). Binds a type to its file, but turns GoFiles from a string list into an object list and makes the configuration longer.
  • Directive comments in manual files (for example //llcppg:type Vector2). Keeps the mapping next to the code, but spreads type mapping over config and source. Not chosen for now; it could be added later on top of the same symbol table.
  • Reusing TypeDef. TypeDef asks llcppg to produce a type; here llcppg must not produce it, so the semantics point in opposite directions.
  • Treat every .go file in the target directory as manual unless generated. Convenient, but regeneration then cannot tell stale generated files from manual ones. An explicit list is safer.

Open questions

  1. Should GoFiles accept glob patterns (*.go) in a later version?
  2. Should llcppg emit a size/alignment assertion for manual types that replace C structs?
  3. Functions, constants and variables currently have no explicit "I translated this by hand" marker; a collision is an error. Should a similar dotted syntax, or a separate skip option, be added for them?
  4. How should the manual symbol table interact with -doc? The proposed default is to show manual and generated symbols together in the same package documentation.

Related

Lenguaje dominante
Go
Estrellas
40
Forks
13
Merge medio
2 h
PR fusionados (30 d)
150

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de goplus/llcppg

Todos los issues de goplus/llcppg

Issues similares

Más issues de Go

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.