Proposal: `GoFiles` — mixing hand-written and generated Go code
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
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
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 inGoFiles.
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
Coloras a packeduint32with 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
.gofile, and must declare the same package name as the generated package. - llcppg never overwrites, reformats, renames or deletes a
GoFilesentry. - A generated output file whose name equals a
GoFilesentry 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:
- Preload: parse
GoFiles, build the manual symbol table, validate theTypeAliasentries that start with a dot. - Load: read C/C++ declarations as today; apply
TypeAliasto decide which declarations are suppressed and which names they resolve to. - 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
GoFilesentry, a non-.goentry, or an entry containing a path separator; - a package-name mismatch between a manual file and the generated package;
- a
GoFilesname that collides with a generated file name; - a
TypeAliasvalue starting with a dot whose target is not declared inGoFiles(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
TypeAliasentry 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 withTypeAlias, so the mapping would be configured in two places. A list also cannot express renaming without extra syntax. - Grouping types under each
GoFilesentry ({"File": "color.go", "Types": [...]}). Binds a type to its file, but turnsGoFilesfrom 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.TypeDefasks llcppg to produce a type; here llcppg must not produce it, so the semantics point in opposite directions. - Treat every
.gofile 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
- Should
GoFilesaccept glob patterns (*.go) in a later version? - Should llcppg emit a size/alignment assertion for manual types that replace C structs?
- 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?
- 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
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de goplus/llcppg
-
Dificultad 3/5 1-2 días Aptitud para principiantes 64/100
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 1-2 días Aptitud para principiantes 70/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 38/100
goplus/llcppg#1136 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 1-2 días Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 1-2 días Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
Todos los issues de goplus/llcppg
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
router-for-me/CLIProxyAPI#7209 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
Azure/azure-storage-azcopy#3674 ·
Los mantenedores suelen responder en 1 día
-
date_time, url_encode, html_escape, to_unix_time, contains_all and contains_any panic when called without argumentsPosiblemente ocupada @antcybersec la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
projectdiscovery/dsl#372 ·
Los mantenedores suelen responder en 1 día
-
blocked-needs-validation
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
projectcapsule/capsule#2265 ·
Los mantenedores suelen responder en 1 día
-
Remove "Model catalog refresh unavailable" warning from OpenRouter agent runsPosiblemente ocupada @superplanehq-integration la tomó hoy. Abiertosuperplane
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
superplanehq/superplane#9164 ·
Los mantenedores suelen responder en 1 día