Proposal: `FieldAlias` — override the Go name and type of a C/C++ field
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 55/100
Línea de trabajo
Start by reading the configuration parsing and field conversion paths in llcppg, then inspect nearby tests for TypeAlias and C++ field translation. Add FieldAlias handling so a matching qualified field uses the configured Go name and type in its original position, and unmatched keys produce a warning. Done when the example translates as specified and the configuration rules are covered by tests.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
Add a new llcppg.cfg option, FieldAlias, that lets a user replace both the Go name and the Go type of a specific C/C++ field when libclang cannot provide enough information to convert it automatically.
The motivating case is libc++'s std::shared_ptr machinery (__shared_ptr_emplace), where a field is declared as a raw byte buffer whose size depends on a template parameter. libclang reports such a field as CXType_DependentSizedArray, and the array length is not available, so llcppg cannot produce a correct Go field.
Motivation
Consider this (simplified) code from libc++:
class __shared_weak_count {};
template <class _Tp, class _Alloc>
struct __shared_ptr_emplace : __shared_weak_count {
struct _Storage {
struct _Data {
[[no_unique_address]] _Alloc __alloc_;
[[no_unique_address]] _Tp __elem_;
};
alignas(_Data) char __buffer_[sizeof(_Data)];
};
_Storage __storage_;
};
The field __shared_ptr_emplace::_Storage::__buffer_ has the type char[sizeof(_Data)]. Because _Data depends on the template parameters _Tp and _Alloc, libclang classifies the type as CXType_DependentSizedArray. For this kind of type libclang only gives us the element type (char); it does not expose the length expression or its value. As a result:
- llcppg cannot emit
[N]byte, sinceNis unknown. - Emitting a zero-length or slice-like placeholder would silently produce a wrong struct layout, and
__storage_(and therefore__shared_ptr_emplace) would be unusable from Go.
The intent of the declaration is well known to a human reader, though: the buffer is raw storage for exactly one _Data object, with _Data's alignment. In Go that is simply an array of length 1 whose element type is the translated _Data struct. What is missing is a way to tell llcppg this.
This is not specific to libc++. Any library that uses sizeof(T)-sized, alignas(T)-aligned raw buffers inside templates (small-buffer optimizations, std::function storage, std::variant/std::optional storage, and so on) hits the same limitation.
Proposal
Add an optional FieldAlias object to llcppg.cfg. Each entry maps a field, identified by its qualified C++ path, to a replacement Go field declaration.
{
"FieldAlias": {
"__shared_ptr_emplace::_Storage::__buffer_": "X__bufferRaw_ [1].X__SharedPtrEmplace_Storage_Data[_Tp, _Alloc]"
}
}
Key: which field
The key is the field's full C++ name (its cName), consistent with every other name in llcppg.cfg: enclosing namespaces (if any), then the enclosing class or classes, then the field name, all separated by ::. Because the path is always fully qualified, two classes with the same name in different namespaces never collide.
In the example, __shared_ptr_emplace::_Storage::__buffer_ means: the field __buffer_ of the class _Storage that is nested inside __shared_ptr_emplace.
Template parameters are not written in the key. The entry applies to the class template as a whole.
Value: the replacement declaration
The value has the shape of a Go struct field declaration, <name> <type>:
- The first word is the Go field name to use in the generated struct. It may differ from the C++ name. In the example,
__buffer_becomesX__bufferRaw_, which also makes it obvious in the Go API that this is raw storage and not a typed member. - The rest of the string is the Go type expression. Its syntax and semantics are exactly the same as the type definition on the right-hand side of a
TypeAliasentry, so users only have to learn one notation. Array lengths, pointers, slices, builtin types and generic instantiations are all accepted.
Referring to types in the current package
As in TypeAlias, a name that starts with a dot refers to a type generated in the current package. In the example, .X__SharedPtrEmplace_Storage_Data is the Go type that llcppg produces for __shared_ptr_emplace::_Storage::_Data. The leading dot is only a marker for llcppg and is dropped in the output; it lets llcppg tell a local generated name from a builtin Go type (int, byte, ...) or an external package reference (unsafe.Pointer) without guessing.
Generic arguments
If the enclosing C++ class is a template, the Go struct is generated as a generic type, and the template parameters (_Tp, _Alloc) are in scope in the type expression. They can be passed to generic types in the usual Go way, as in .X__SharedPtrEmplace_Storage_Data[_Tp, _Alloc].
Expected result
With the entry above, the class __shared_ptr_emplace::_Storage is translated to:
type X__SharedPtrEmplace_Storage[_Tp any, _Alloc any] struct {
X__bufferRaw_ [1]X__SharedPtrEmplace_Storage_Data[_Tp, _Alloc]
}
Without the entry, llcppg has no valid translation for this field.
Because _Data is translated like any other nested struct, it is emitted as its own generic type:
type X__SharedPtrEmplace_Storage_Data[_Tp any, _Alloc any] struct {
__alloc_ _Alloc
__elem_ _Tp
}
Behavior and rules
- Scope of the override.
FieldAliasonly changes the single field it names. All other fields of the same struct are converted as usual. - Field position is preserved. The replacement field takes the place of the original in declaration order, so the surrounding layout stays consistent.
- Name and type are always replaced together. An entry always provides a Go name and a Go type. If the user only wants to keep the C++ name, they repeat it as the first word.
- No semantic validation of the Go type. llcppg does not verify that the replacement type has the same size or alignment as the original C++ field. The user who writes the entry is responsible for that. llcppg only checks that the expression parses, that
.-prefixed names resolve to generated types, and that generic arguments name in-scope template parameters. - Unmatched keys are reported. If a key in
FieldAliasmatches no field in the parsed headers, llcppg emits a warning, so typos and stale entries after a header upgrade are caught. - Applies wherever the field would be converted, whether or not the type is a template, and whether the field was directly convertible or not. This makes
FieldAliasusable as a general escape hatch and not only for dependent-sized arrays.
Known limitations
-
Layout equivalence is by convention. The
[1]Datareplacement foralignas(Data) char buf[sizeof(Data)]has the same size and alignment as the C++ original only as long as the Go translation ofDatahas the same layout as the C++Data. llcppg does not verify this; the user who writes the entry is responsible for checking it when the buffer is accessed across the C/Go boundary.In most cases the layouts do agree, including the
[[no_unique_address]]members in the libc++ example. An empty class occupies 1 byte in C++ by default, but a member declared[[no_unique_address]]may occupy no space, and a zero-size Go field (struct{}) behaves the same way when it is not the last field. For the_Dataabove, an empty_Allocplaced before__elem_therefore produces the same layout in C++ and in Go. There are two edge cases where the layouts can still differ:- Empty member as the last field. Go adds trailing padding after a zero-size final field, so that a pointer to that field cannot point past the end of the object. C++ adds the same padding for a plain empty member, but not for a
[[no_unique_address]]one. For example,struct { int x; [[no_unique_address]] E e; }is 4 bytes in C++, while the corresponding Go struct{ x int32; e E }is 8 bytes. If the empty member is last in theDatabeing aliased, the Go struct may be larger than the C++ one. - A struct containing only empty members. C++ never gives a class size 0, so it is at least 1 byte. The Go equivalent has size 0.
- Empty member as the last field. Go adds trailing padding after a zero-size final field, so that a pointer to that field cannot point past the end of the object. C++ adds the same padding for a plain empty member, but not for a
-
Not a substitute for fixing libclang gaps in general. Where the array length is a plain constant, llcppg should keep resolving it automatically.
FieldAliasis for cases that cannot be resolved from the AST.
Alternatives considered
- Extending
TypeAlias.TypeAliasmaps whole C/C++ types to Go symbols. The problem here is specific to one field whose type has no usable identity (a dependent-sized array ofchar), so there is nothing meaningful to key on at the type level. Keying on the field is the natural choice, and it also allows renaming. - Computing the length from the template. Evaluating
sizeof(_Data)for a dependent type would require instantiating the template or implementing a C++ constant evaluator, which is far outside llcppg's scope. - Emitting
[0]byteor an opaque blob. This loses the type information and the alignment, and silently produces a wrong layout, which is worse than requiring an explicit configuration.
Non-goals
- Dropping a field.
FieldAliashas no "remove this field" form. It is only for the case where the field needs a different name and a concrete replacement type. - A new type notation. The type expression reuses the
TypeAliasnotation (including the.prefix for same-package types), and keys use the same fully qualifiedcNameform as the rest of the config.FieldAliasintroduces no new syntax rules beyond<name> <type>.
- Lenguaje dominante
- Go
- Estrellas
- 39
- Forks
- 13
- Merge medio
- 1 h 28 min
- PR fusionados (30 d)
- 256
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
-
bug
Dificultad 5/5 Más de una semana Aptitud para principiantes 28/100
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 4/5 3-5 días Aptitud para principiantes 55/100
Los mantenedores suelen responder en 1 día
-
feature proposal
Dificultad 5/5 Más de una semana Aptitud para principiantes 18/100
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
Los mantenedores suelen responder en 1 día
-
Architecture-Design feature to be confirm
Dificultad 5/5 Más de una semana Aptitud para principiantes 35/100
Los mantenedores suelen responder en 1 día
Todos los issues de goplus/llcppg
Issues similares
-
perf(translator): Claude→Responses stream re-validates the whole request body on every SSE chunkAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
router-for-me/CLIProxyAPI#6665 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
superplanehq/superplane#8557 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
copilot-api domain:platform go
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
docs: clarify gh api -f vs -F and @-file expansion in the gh skillPosiblemente ocupada @MatiasOlivera la tomó hoy. Abiertoenhancement
Dificultad 2/5 Medio día Aptitud para principiantes 62/100
cli/cli#14701 · 2 comentarios ·
Los mantenedores suelen responder en 1 día