-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Saner enum type, record pointers; union support
This commit changes most record return values to return a pointer instead of value, to both reflect how it'll always pass-by-reference and how most record methods need a pointer receiver. This commit also changes the underlying types for enums and bitfields to be C.int and C.uint, respectively, to allow for direct casting of enums/bitfields lists. The record pointer change is expected to be followed up with a similar change for classes. This will make signal generation slightly more accurate as well, since both of those types are always passed by pointers. This commit closes issue #25.
- Loading branch information
1 parent
12f81c3
commit d16c0d1
Showing
298 changed files
with
3,143 additions
and
4,402 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
package generators | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/diamondburned/gotk4/gir" | ||
"github.com/diamondburned/gotk4/gir/girgen/file" | ||
"github.com/diamondburned/gotk4/gir/girgen/gotmpl" | ||
"github.com/diamondburned/gotk4/gir/girgen/logger" | ||
"github.com/diamondburned/gotk4/gir/girgen/pen" | ||
"github.com/diamondburned/gotk4/gir/girgen/strcases" | ||
"github.com/diamondburned/gotk4/gir/girgen/types" | ||
"github.com/diamondburned/gotk4/gir/girgen/types/typeconv" | ||
) | ||
|
||
var unionTmpl = gotmpl.NewGoTemplate(` | ||
{{ GoDoc . 0 }} | ||
type {{ .GoName }} struct { | ||
*{{ .ImplName }} | ||
} | ||
// {{ .ImplName }} has the finalizer attached to it. | ||
type {{ .ImplName }} struct { | ||
native *C.{{ .CType }} | ||
} | ||
{{ if .Marshaler }} | ||
func marshal{{ .GoName }}(p uintptr) (interface{}, error) { | ||
b := externglib.ValueFromNative(unsafe.Pointer(p)).Boxed() | ||
return &{{.GoName}}{&{{.ImplName}}{(*C.{{.CType}})(b)}}, nil | ||
} | ||
{{ end }} | ||
{{ range .Fields }} | ||
// As{{ .GoName }} returns a copy of {{ $.Recv }} as the struct {{ .GoType }}. | ||
// It does this without any knowledge on the actual type of the value, so | ||
// the caller must take care of type-checking beforehand. | ||
func ({{ $.Recv }} *{{ $.GoName }}) As{{ .GoName }}() {{ .GoType }} {{ .Block }} | ||
{{ end }} | ||
`) | ||
|
||
type UnionGenerator struct { | ||
*gir.Union | ||
GoName string | ||
ImplName string | ||
Marshaler bool | ||
|
||
Marshalers []UnionFieldMarshaler | ||
Fields []UnionField | ||
|
||
gen FileGenerator | ||
hdr file.Header | ||
} | ||
|
||
type UnionFieldMarshaler struct { | ||
*gir.Field | ||
GLibGetType string | ||
} | ||
|
||
type UnionField struct { | ||
*gir.Field | ||
GoName string | ||
GoType string | ||
Block string | ||
} | ||
|
||
// GenerateUnion generates the union. | ||
func GenerateUnion(gen FileGeneratorWriter, union *gir.Union) bool { | ||
unionGen := NewUnionGenerator(gen) | ||
if !unionGen.Use(union) { | ||
return false | ||
} | ||
|
||
writer := FileWriterFromType(gen, union) | ||
|
||
if union.GLibGetType != "" && !types.FilterCType(gen, union.GLibGetType) { | ||
unionGen.Marshaler = true | ||
writer.Header().NeedsExternGLib() | ||
writer.Header().AddMarshaler(union.GLibGetType, unionGen.GoName) | ||
} | ||
|
||
writer.Pen().WriteTmpl(unionTmpl, &unionGen) | ||
// Write the header after using the template to ensure that UseConstructor | ||
// registers everything. | ||
file.ApplyHeader(writer, &unionGen) | ||
|
||
return true | ||
} | ||
|
||
func NewUnionGenerator(gen FileGenerator) UnionGenerator { | ||
return UnionGenerator{ | ||
gen: gen, | ||
} | ||
} | ||
|
||
// Header returns the union generator's headers. | ||
func (ug *UnionGenerator) Header() *file.Header { | ||
return &ug.hdr | ||
} | ||
|
||
// Recv returns the method receiver. | ||
func (ug *UnionGenerator) Recv() string { | ||
return strcases.FirstLetter(ug.GoName) | ||
} | ||
|
||
func (ug *UnionGenerator) Use(union *gir.Union) bool { | ||
ug.hdr.Reset() | ||
ug.Marshaler = false | ||
ug.Fields = ug.Fields[:0] | ||
|
||
// If there's no C type or the name has an underscore, then we probably | ||
// shouldn't touch it. | ||
if union.CType == "" || strings.HasPrefix(union.Name, "_") { | ||
return false | ||
} | ||
|
||
ug.Union = union | ||
ug.GoName = strcases.PascalToGo(union.Name) | ||
ug.ImplName = strcases.UnexportPascal(ug.GoName) | ||
|
||
typ := gir.TypeFindResult{ | ||
NamespaceFindResult: ug.gen.Namespace(), | ||
Type: union, | ||
} | ||
|
||
// Can we copy? Exit if not. We want the finalizer to work properly. | ||
copyMethod := types.FindMethodName(union.Methods, "copy") | ||
if copyMethod == nil { | ||
ug.Logln(logger.Skip, "no copy method") | ||
return false | ||
} | ||
|
||
// We can optionally have a freeMethod. | ||
freeMethod := types.RecordHasFree(&gir.Record{Methods: union.Methods}) | ||
|
||
for i, field := range union.Fields { | ||
if field.Type == nil || !field.IsReadable() { | ||
continue | ||
} | ||
|
||
srcVal := typeconv.ConversionValue{ | ||
InName: "cpy", | ||
OutName: "dst", | ||
Direction: typeconv.ConvertCToGo, | ||
InContainer: true, | ||
// ParameterIndex = 0 lets the type converter treat the field value | ||
// as a pointer rather than a field value. | ||
ParameterIndex: 0, | ||
ParameterAttrs: gir.ParameterAttrs{ | ||
Name: field.Name, | ||
AnyType: field.AnyType, | ||
TransferOwnership: gir.TransferOwnership{ | ||
// Full prevents the type converter from adding a finalizer. | ||
// We'll write our own. | ||
TransferOwnership: "full", | ||
}, | ||
}, | ||
} | ||
|
||
converter := typeconv.NewConverter(ug.gen, &typ, []typeconv.ConversionValue{srcVal}) | ||
converter.UseLogger(ug) | ||
|
||
srcRes := converter.Convert(0) | ||
if srcRes == nil { | ||
ug.Logln(logger.Skip, "field", field.Name) | ||
continue | ||
} | ||
|
||
// Only allow records or enums or bitfields for now. | ||
if !srcRes.Resolved.IsRecord() && !srcRes.Resolved.IsEnumOrBitfield() { | ||
ug.Logln(logger.Skip, "field", field.Name, "is unsupported type") | ||
continue | ||
} | ||
|
||
p := pen.NewBlock() | ||
ug.hdr.Import("runtime") | ||
|
||
// We only need to copy if this is a record, because a record is passed | ||
// by reference. Since enums/bitfields are copied, we don't need to copy | ||
// the original value. | ||
if srcRes.Resolved.IsRecord() { | ||
p.Linef( | ||
// The type conversion helps us ensure that copy() actually returns | ||
// the type that we expect it to. Otherwise, unsafe.Pointer is | ||
// potentialy dangerous. | ||
"cpy := (*C.%s)(C.%s(%s.%s.native))", | ||
ug.CType, copyMethod.CIdentifier, ug.Recv(), ug.ImplName, | ||
) | ||
} else { | ||
p.Linef("cpy := %s.%s.native", ug.Recv(), ug.ImplName) | ||
} | ||
|
||
p.Linef("var dst %s", srcRes.Out.Type) | ||
p.Linef(srcRes.Conversion) | ||
|
||
// We should free the copy when we're done if this is a record, since we | ||
// copied it earlier. | ||
if srcRes.Resolved.IsRecord() { | ||
ug.hdr.Import("unsafe") | ||
ug.hdr.ImportCore("gextras") | ||
|
||
p.Linef("runtime.SetFinalizer(") | ||
// dst is ASSUMED TO BE A POINTER. | ||
p.Linef(" gextras.StructIntern(unsafe.Pointer(dst)),") | ||
p.Linef(" func(intern *struct{ C unsafe.Pointer }) {") | ||
p.Linef(types.RecordPrintFreeMethod(freeMethod, "intern.C")) | ||
p.Linef("},") | ||
p.Linef(")") | ||
} | ||
|
||
p.Linef("runtime.KeepAlive(%s.%s)", ug.Recv(), ug.ImplName) | ||
p.Linef("return dst") | ||
|
||
ug.Fields = append(ug.Fields, UnionField{ | ||
Field: &union.Fields[i], | ||
GoName: strcases.SnakeToGo(true, field.Name), | ||
GoType: srcRes.Out.Type, | ||
Block: p.String(), | ||
}) | ||
} | ||
|
||
return true | ||
} | ||
|
||
func (ug *UnionGenerator) Logln(lvl logger.Level, v ...interface{}) { | ||
p := fmt.Sprintf("union %s (C.%s):", ug.GoName, ug.CType) | ||
ug.gen.Logln(lvl, logger.Prefix(v, p)...) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.