Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions codebuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ type CodeBuilder struct {
loadNamed LoadNamedFunc
handleErr func(err error)
closureParamInsts
vFieldsMgr
iotav int
commentOnce bool
noSkipConst bool
Expand Down Expand Up @@ -1572,6 +1573,10 @@ func (p *CodeBuilder) fieldRef(x ast.Expr, o *types.Struct, name string, src ast
embed = append(embed, fld)
}
}
if p.refVField(o, name, x, src) {
return true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Virtual field placement issue: Virtual fields are checked after direct fields but before embedded fields. This breaks Go's field resolution semantics where embedded fields are promoted to the same level as direct fields.

If a tuple with virtual field "x" embeds a struct that also has field "x", the virtual field will incorrectly shadow the embedded field.

Recommendation: Move this virtual field check to after embedded field recursion (after the loop at lines 1587-1599) to match Go's semantics.

}

if visited == nil {
visited = make(map[*types.Struct]none)
} else if _, ok := visited[o]; ok {
Expand Down Expand Up @@ -1934,6 +1939,9 @@ func (p *CodeBuilder) normalField(
return MemberField
}
}
if p.findVField(o, name, arg, src) {
return MemberField
}
return MemberInvalid
}

Expand Down
127 changes: 127 additions & 0 deletions type_tuple.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
Copyright 2026 The XGo Authors (xgo.dev)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gogen

import (
"go/ast"
"go/types"
"strconv"
)

// ----------------------------------------------------------------------------

/*
// vFields defines the interface for virtual fields of a struct type.
type vFields interface { // virtual fields
FindField(cb *CodeBuilder, t *types.Struct, name string, arg *Element, src ast.Node) bool
FieldRef(cb *CodeBuilder, t *types.Struct, name string, x ast.Expr, src ast.Node) bool
}
*/

type vFields = *tupleFields
Comment on lines +24 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The vFields interface is commented out and a type alias is used instead. This limits the extensibility of the virtual fields mechanism. It's recommended to use the interface to allow for different kinds of virtual fields in the future. This would make the design more robust and open for extension.

// vFields defines the interface for virtual fields of a struct type.
type vFields interface { // virtual fields
	FindField(cb *CodeBuilder, t *types.Struct, name string, arg *Element, src ast.Node) bool
	FieldRef(cb *CodeBuilder, t *types.Struct, name string, x ast.Expr, src ast.Node) bool
}


type vFieldsMgr struct {
vfts map[*types.Struct]vFields
}

func (p *CodeBuilder) refVField(t *types.Struct, name string, x ast.Expr, src ast.Node) bool {
if vft, ok := p.vfts[t]; ok {
return vft.FieldRef(p, t, name, x, src)
}
return false
}

func (p *CodeBuilder) findVField(t *types.Struct, name string, arg *Element, src ast.Node) bool {
if vft, ok := p.vfts[t]; ok {
return vft.FindField(p, t, name, arg, src)
}
return false
}

func (p *Package) setVFields(t *types.Struct, vft vFields) {
if p.cb.vfts == nil {
p.cb.vfts = make(map[*types.Struct]vFields)
}
p.cb.vfts[t] = vft
}

/*
func (p *Package) vFields(t *types.Struct) (vft vFields, ok bool) {
vft, ok = p.cb.vfts[t]
return
}
*/
Comment on lines +59 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This commented-out vFields function appears to be dead code. It should be removed to keep the codebase clean.


// ----------------------------------------------------------------------------

type tupleFields struct {
fields []*types.Var
}

func (p *tupleFields) FindField(cb *CodeBuilder, t *types.Struct, name string, arg *Element, src ast.Node) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The t parameter is unused in this function. It's good practice to rename it to _ to make it clear that it's intentionally not used.

func (p *tupleFields) FindField(cb *CodeBuilder, _ *types.Struct, name string, arg *Element, src ast.Node) bool {

for i, fld := range p.fields {
if fld.Name() == name {
cb.stk.Ret(1, &Element{
Val: selector(arg, tupleFieldName(i)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing nil safety check: The selector() function will panic if arg is nil. Consider adding a nil check:

if arg == nil {
    return false
}

This defensive check would prevent potential nil pointer dereferences.

Type: fld.Type(),
Src: src,
})
if cb.rec != nil {
cb.rec.Member(src, fld)
}
return true
}
}
return false
}

func (p *tupleFields) FieldRef(cb *CodeBuilder, t *types.Struct, name string, x ast.Expr, src ast.Node) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The t parameter is unused in this function. It's good practice to rename it to _ to make it clear that it's intentionally not used.

func (p *tupleFields) FieldRef(cb *CodeBuilder, _ *types.Struct, name string, x ast.Expr, src ast.Node) bool {

for i, fld := range p.fields {
if fld.Name() == name {
if cb.rec != nil {
cb.rec.Member(src, fld)
}
ordName := tupleFieldName(i)
cb.stk.Ret(1, &Element{
Val: &ast.SelectorExpr{X: x, Sel: ident(ordName)},
Type: &refType{typ: fld.Type()},
})
return true
}
}
return false
}

// NewTuple creates a tuple type with the given fields.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation clarity: The Godoc should clarify that with withName=true, fields can be accessed by BOTH their original names (e.g., "x", "y") AND ordinal names ("_0", "_1"). The current comment doesn't make this dual-access capability clear.

Suggested addition:

// NewTuple creates a tuple type with the given fields.
// The fields are always accessible by ordinal names: _0, _1, ...
// If withName is true, the fields are ALSO accessible by their original names
// through a virtual fields mechanism.

// The fields are named as _0, _1, ...
// If withName is true, the original fields can also be accessed through
// virtual fields mechanism.
func (p *Package) NewTuple(withName bool, fields ...*types.Var) *types.Struct {
ordinals := make([]*types.Var, len(fields))
for i, fld := range fields {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Input validation: Consider adding validation for nil fields to make debugging easier:

for i, fld := range fields {
    if fld == nil {
        panic("NewTuple: nil field at index " + strconv.Itoa(i))
    }
    // ...
}

Also consider documenting the behavior when multiple fields have the same name (first match wins).

name := tupleFieldName(i)
ordinals[i] = types.NewVar(fld.Pos(), fld.Pkg(), name, fld.Type())
}
ret := types.NewStruct(ordinals, nil)
if withName {
p.setVFields(ret, &tupleFields{fields})
}
return ret
}

func tupleFieldName(i int) string {
return "_" + strconv.Itoa(i)
}

// ----------------------------------------------------------------------------
53 changes: 53 additions & 0 deletions type_tuple_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
Copyright 2026 The XGo Authors (xgo.dev)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gogen_test

import (
"go/token"
"go/types"
"testing"

"github.com/goplus/gogen"
)

func TestTupleMember(t *testing.T) {
pkg := newMainPackage()
x := types.NewField(token.NoPos, pkg.Types, "x", types.Typ[types.Int], false)
y := types.NewField(token.NoPos, pkg.Types, "y", types.Typ[types.Int], false)
typ := pkg.NewTuple(true, x, y)
a := types.NewParam(token.NoPos, pkg.Types, "a", typ)
pkg.NewFunc(nil, "foo", types.NewTuple(a), nil, false).BodyStart(pkg).
Val(ctxRef(pkg, "a")).
MemberRef("x").
Val(ctxRef(pkg, "a")).
MemberVal("y").
Assign(1).
EndStmt().
Debug(func(cb *gogen.CodeBuilder) {
cb.Val(ctxRef(pkg, "a"))
cb.Member("unknown", gogen.MemberFlagRef)
cb.Member("unknown", gogen.MemberFlagVal)
cb.ResetStmt()
}).
End()
domTest(t, pkg, `package main

func foo(a struct {
_0 int
_1 int
}) {
a._0 = a._1
}
`)
}
Loading