Proposal: `FuncDef` — add functions and methods to the generated Go package
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
調査の方向性
Start by reading the existing TypeDef, MethodCheck, and NewCheck configuration and generation paths, plus issue #1079 for the companion option. Then trace how functions are registered, mapped from C signatures, emitted into Go files, and checked for name collisions and receiver compatibility. Done means implementing and documenting FuncDef with validation and tests for the listed forms and interactions; the proposal leaves several semantics open for resolution.
索引モデルが issue の本文から書いたものです。
説明
Summary
Add a new llcppg.cfg option, FuncDef, that lets a binding author declare a Go function or method that should exist in the generated package but is not produced by llcppg's default conversion. The author names the file it goes into, the Go name of the function (a plain identifier for a package-level function, or (*Type).Method / (Type).Method for a method), and the C symbol it links to.
"FuncDef": {
"moduleobject.go": [
{
"Name": "(*ModuleObject).GetDict",
"Link": "PyModule_GetDict"
}
]
}
This means: in moduleobject.go, emit a method GetDict on *ModuleObject, bound to the C symbol PyModule_GetDict.
FuncDef is the companion of TypeDef (#1079). Together they form a family of options that add new symbols to the generated package, and this proposal also fixes the common syntax shared by that family.
Motivation
TypeDef makes it possible to declare a type that the public headers never expose, such as ModuleObject for the Python C API. Once the type exists, the natural next step is to make functions belong to it: PyModule_GetDict, PyModule_GetName and friends should become methods on *ModuleObject.
The existing options cannot always express this:
MethodCheckandNewCheckdecide automatically whether a C function belongs to a type, based on name matching rules. They are a good fit for regular naming schemes, but they cannot cover one-off cases: a function whose C name does not follow the pattern, a function that should be exposed under a different Go name, or a method whose receiver is aTypeDeftype that the C signature never mentions.- Hand-written Go files with
//go:linknamework, but they sit outside the generation flow. They are invisible to llcppg's name matching, and they must be kept in sync by hand when the package is regenerated.
FuncDef is the explicit, per-function escape hatch: the author states exactly which Go symbol should exist and which C symbol it links to, and llcppg emits it as part of normal generation.
Common syntax for xxxDef options
All options that add new symbols (currently TypeDef and FuncDef) use the same macro-level shape:
"xxxDef": {
"<file>.go": [
{
"Name": "<name>",
...
}
]
}
The rules below apply to every xxxDef option, now and in the future:
- Key — the target Go file name (for example
moduleobject.go). It must end in.go. The file is created in the output package directory, next to the other generated files. - Value — an ordered array of entries. Several symbols may share one file by listing several entries. The array order is the emission order, so output is deterministic.
Name— the Go-side name of the symbol being added. It is the final Go name, exactly as it should appear in the package. It is not run through prefix stripping or any other naming rule.- Other fields — specific to each option (
FieldsforTypeDef,LinkforFuncDef). - File processing order — files are processed in the order their keys appear in the config. Within a file, entries are processed in array order. If
TypeDefandFuncDeftarget the same file, the file collects declarations from both (see "Where the function is emitted"). - Name collisions — a
xxxDefentry only supplies missing symbols. If the name already exists in the package, from the headers or from another entry, it is a configuration error. It never silently overrides an existing symbol.
Keeping one structure means a binding author learns the shape once, and tooling (validation, formatting, documentation) can treat all xxxDef options uniformly.
Configuration
"FuncDef": {
"<file>.go": [
{ "Name": "<FuncName>", "Link": "<c_symbol>" },
{ "Name": "(*<Type>).<Method>", "Link": "<c_symbol>" },
{ "Name": "(<Type>).<Method>", "Link": "<c_symbol>" }
]
}
Name
Name takes one of three forms:
| Form | Meaning |
|---|---|
Ident |
A package-level function, for example NewModule. |
(*Type).Method |
A method with a pointer receiver, for example (*ModuleObject).GetDict. |
(Type).Method |
A method with a value receiver, for example (Object).IsNil. |
The receiver form follows ordinary Go method-expression notation, so it reads the same way it will look in the generated code. Type and Method are Go identifiers in their final form; Type refers to a type of the package being generated, either one produced from the headers or one defined by TypeDef. Because Type is always a type of the current package, it is written without a leading dot, unlike field types in TypeDef.
Link
Link is the name of the C symbol the Go function is linked to. It becomes the target of the //go:linkname directive in the generated code. It is currently the only field supported besides Name, and it is required.
The signature of the generated function is derived from the C declaration of the Link symbol, converted with the same type-mapping rules llcppg uses for every other function. The author does not restate parameters or results in the config.
Further fields (for example a doc comment, or an explicit signature) may be added later without changing the structure above.
Example
Combined with the TypeDef example, a binding author can add the ModuleObject type and attach functions to it:
"TypeDef": {
"moduleobject.go": [
{ "Name": "ModuleObject", "Fields": [".Object"] }
]
},
"FuncDef": {
"moduleobject.go": [
{ "Name": "(*ModuleObject).GetDict", "Link": "PyModule_GetDict" },
{ "Name": "(*ModuleObject).GetName", "Link": "PyModule_GetName" },
{ "Name": "NewModuleObject", "Link": "PyModule_New" }
]
}
generates a moduleobject.go along these lines:
package python
import (
"github.com/goplus/lib/c"
_ "unsafe"
)
type ModuleObject struct {
Object
}
//go:linkname (*ModuleObject).GetDict C.PyModule_GetDict
func (recv_ *ModuleObject) GetDict() *Object
//go:linkname (*ModuleObject).GetName C.PyModule_GetName
func (recv_ *ModuleObject) GetName() *c.Char
//go:linkname NewModuleObject C.PyModule_New
func NewModuleObject(name *c.Char) *ModuleObject
The exact spelling of the linkname directive and of the receiver variable follows what llcppg already generates for methods produced by MethodCheck, so FuncDef output is indistinguishable from automatically attributed functions.
Semantics
Where the function is emitted
The declaration is written to the file named by the key, in the order of that file's array. When a TypeDef and a FuncDef target the same file, type declarations come first, followed by function declarations, so a file reads naturally from types to the functions that use them. Files created only for FuncDef contain the package clause, the needed imports, and the function declarations. If the file name collides with a file llcppg generates for another reason, the declarations are appended to that file rather than overwriting it.
Signature and receiver
- For a package-level function, the signature is the converted C signature of the
Linksymbol. - For a method, the receiver is the type named in
Name. The C function's first parameter is taken as the receiver position, and the remaining parameters become the method's parameters. The first parameter's converted type does not have to be spelled identically to the receiver type. For example,PyModule_GetDicttakes aPyObject *, while the receiver is*ModuleObject. This is valid when the receiver type is layout-compatible with the converted first parameter type, as in the embedded-base case produced byTypeDef. llcppg checks that the receiver type is the first parameter type itself, or a type that embeds it as its first field. - Value receivers
(Type).Methodfollow the same rule with a non-pointer first parameter.
Interaction with other options
FuncDef entries are registered together with TypeDef entries, before the phases that consume names, so they behave like functions that came from the headers:
- A function that appears in
FuncDefis not also emitted by the default conversion,MethodCheckorNewCheck. The C symbol is claimed by theFuncDefentry, so there is no duplicate binding of the same symbol under two Go names. See open question 1 for the exact rule. - A method's receiver type may be a
TypeDeftype, which is the main motivating case. - The C symbol named by
Linkmust be resolvable, either from the parsed headers or, if it is not declared there, through the rules in open question 2.
Relation to llcppg.pub
A FuncDef entry adds no C-to-Go type mapping, so it is not written to llcppg.pub.
Validation and errors
llcppg reports a configuration error (and stops before generating) when:
- a file key is empty or does not end in
.go; - an entry has no
Name, or itsNameis none of the three accepted forms (Ident,(*Type).Method,(Type).Method), or an identifier in it is not a valid Go identifier; Linkis missing or empty;- the
Linksymbol cannot be found, or is not a function; - for a method, the receiver
Typedoes not exist in the generated package, or the receiver is not compatible with the first parameter of the C function (see "Signature and receiver"), or the C function has no parameters; - the resulting name already exists in the package: a function, a method of the same type, or a type.
FuncDefonly supplies missing symbols; it never overrides existing ones; - two
FuncDefentries produce the same Go name; - an unknown field appears in an entry.
Errors name the file, the entry's Name, and the problem.
Alternatives considered
- Hand-written files with
//go:linkname. Works today but sits outside generation: the symbols are not visible to llcppg, and regeneration needs manual care. It remains available for anythingFuncDefcannot express. - Extending
MethodCheckwith per-function exceptions.MethodCheckis a rule-based, pattern-driven mechanism. Adding explicit one-off entries to it mixes two intents (automatic rule vs. explicit declaration) and has no natural place for per-entry fields such asLink. - Writing the whole Go signature in
Name. This would let the author declare functions that have no C declaration at all, but it duplicates the type-mapping work llcppg already does and reintroduces mistakes that automatic conversion avoids. A signature field can still be added later as an additional option. - Keying entries by function name. Rejected for the same reason as in
TypeDef: JSON objects are unordered, so emission order would be undefined. An ordered array of entries is used for allxxxDefoptions.
Open questions
- Interaction with default emission. When the
Linksymbol is also a regular C function in the headers, should theFuncDefentry replace the default binding (as proposed above), or should both be emitted? Replacing avoids duplicate bindings; the cost is that usingFuncDefto merely rename or re-home a function also removes the original. A later option could keep the original if needed. - Symbols without a C declaration. Should
Linkbe allowed to name a symbol that is not declared in the parsed headers? That would need an explicit signature field, which is out of scope for the first version, so the first version requires the symbol to be declared. - Optional
Link. For a package-level function whose Go name already equals the C name,Linkcould be omitted and inferred. The first version requiresLinkto keep the config explicit. - Doc comments. As with
TypeDef, an optional"Doc"string could attach a doc comment to the generated function. Not required for the core feature. - Receiver compatibility check. The rule "same type, or embeds it as its first field" is deliberately strict. Whether it should be relaxed, for instance to allow any layout-compatible pointer type, can be decided after real-world use with the Python binding.
Compatibility
Purely additive. Configs without FuncDef behave exactly as before. The common xxxDef syntax described above matches TypeDef as proposed in #1079, so no change to TypeDef is required by this proposal.
- 主要言語
- Go
- スター
- 40
- フォーク
- 13
- 平均マージ
- 1時間 34分
- マージ済み PR(30日)
- 251
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
goplus/llcppg のほかの issue
-
Proposal: `Define` — inject constants into the generated package対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンconfiguration feature proposal
難易度 5/5 1週間以上 初心者へのやさしさ 25/100
goplus/llcppg#1067 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 5/5 1週間以上 初心者へのやさしさ 28/100
メンテナーはふだん 1 日以内に返信
-
bug
難易度 4/5 3〜5日 初心者へのやさしさ 55/100
メンテナーはふだん 1 日以内に返信
-
feature proposal
難易度 5/5 1週間以上 初心者へのやさしさ 18/100
メンテナーはふだん 1 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 25/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
OCIRepository: pick the Helm chart layer automatically when the first layer is a provenance fileオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
fluxcd/source-controller#2198 ·
メンテナーはふだん 1 日以内に返信
-
webhook: mutating admission silently allows objects when createPatch fails (error discarded)対応中かも @lemonzoey が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
volcano-sh/volcano#6138 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 1 日以内に返信
-
area/frameworks area/steve kind/bug team/frameworks
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信