Skip to content

Commit

Permalink
Saner enum type, record pointers; union support
Browse files Browse the repository at this point in the history
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
diamondburned committed Oct 23, 2021
1 parent 12f81c3 commit d16c0d1
Show file tree
Hide file tree
Showing 298 changed files with 3,143 additions and 4,402 deletions.
10 changes: 9 additions & 1 deletion gir/girgen/generators/bitfield.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

var bitfieldTmpl = gotmpl.NewGoTemplate(`
{{ GoDoc . 0 (OverrideSelfName .GoName) }}
type {{ .GoName }} int
type {{ .GoName }} C.guint
const (
{{ range .Members -}}
Expand Down Expand Up @@ -65,6 +65,7 @@ type bitfieldData struct {
GoName string
StrLen int // length of all enum strings concatenated
Marshaler bool
Members []gir.Member

gen FileGenerator
}
Expand Down Expand Up @@ -108,6 +109,7 @@ func GenerateBitfield(gen FileGeneratorWriter, bitfield *gir.Bitfield) bool {
data := bitfieldData{
Bitfield: bitfield,
GoName: goName,
Members: make([]gir.Member, 0, len(bitfield.Members)),
gen: gen,
}

Expand All @@ -122,6 +124,12 @@ func GenerateBitfield(gen FileGeneratorWriter, bitfield *gir.Bitfield) bool {
writer.Header().Import("fmt")

for i, member := range bitfield.Members {
if v, _ := strconv.ParseInt(member.Value, 10, 64); v < 0 {
// Ignore negative values that are bodged by GIR.
continue
}

data.Members = append(data.Members, member)
data.StrLen += len(data.FormatMember(member))
if i > 0 {
data.StrLen++ // account for '|'
Expand Down
2 changes: 1 addition & 1 deletion gir/girgen/generators/enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

var enumTmpl = gotmpl.NewGoTemplate(`
{{ GoDoc . 0 (OverrideSelfName .GoName) }}
type {{ .GoName }} int
type {{ .GoName }} C.gint
{{ if .IsIota }}
const (
Expand Down
14 changes: 7 additions & 7 deletions gir/girgen/generators/generators.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package generators

import (
"github.com/diamondburned/gotk4/gir/girgen/pen"
"github.com/diamondburned/gotk4/gir/girgen/cmt"
"github.com/diamondburned/gotk4/gir/girgen/file"
"github.com/diamondburned/gotk4/gir/girgen/pen"
"github.com/diamondburned/gotk4/gir/girgen/types"
)

Expand Down Expand Up @@ -35,12 +35,12 @@ type FileWriter interface {
Pen() *pen.Pen
}

// headeredFileGenerator is used to overried a Header to be used inside
// callable.FileGenerator.
type headeredFileGenerator struct {
types.FileGenerator
file.Headerer
}
// // headeredFileGenerator is used to overried a Header to be used inside
// // callable.FileGenerator.
// type headeredFileGenerator struct {
// types.FileGenerator
// file.Headerer
// }

// StubFileGeneratorWriterWriter wraps an existing FileGenerator around a stub
// file writer. This is useful for using existing functions that expect to write
Expand Down
229 changes: 229 additions & 0 deletions gir/girgen/generators/union.go
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)...)
}
9 changes: 9 additions & 0 deletions gir/girgen/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ func (n *NamespaceGenerator) CanGenerate(r *types.Resolved) bool {
canResolve = generators.GenerateCallback(generators.StubFileGeneratorWriter(ngen), v)
case *gir.Function:
canResolve = generators.GenerateFunction(generators.StubFileGeneratorWriter(ngen), v)
case *gir.Union:
canResolve = generators.GenerateUnion(generators.StubFileGeneratorWriter(ngen), v)
}

// Actually store the correct value once we're done.
Expand Down Expand Up @@ -254,6 +256,13 @@ func (n *NamespaceGenerator) Generate() (map[string][]byte, error) {
}
generateFunctions(v.Name, v.Functions)
}
for _, v := range n.current.Namespace.Unions {
if !generators.GenerateUnion(n, &v) {
n.logIfSkipped(false, "union "+v.Name)
continue
}
generateFunctions(v.Name, v.Functions)
}

// Ensure that all files explicitly import runtime/cgo to not trigger an
// error in a compiler complaining about implicitly importing runtime/cgo.
Expand Down
Loading

0 comments on commit d16c0d1

Please sign in to comment.