Skip to content
86 changes: 69 additions & 17 deletions internal/ssa2ast/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,15 @@ func isVoidType(typ types.Type) bool {
}

func isStringType(typ types.Type) bool {
typ = typ.Underlying()
return types.Identical(typ, types.Typ[types.String]) || types.Identical(typ, types.Typ[types.UntypedString])
}

func getFieldName(tp types.Type, index int) (string, error) {
if pt, ok := tp.(*types.Pointer); ok {
tp = pt.Elem()
}
tp = types.Unalias(tp)
if named, ok := tp.(*types.Named); ok {
tp = named.Underlying()
}
Expand Down Expand Up @@ -564,6 +566,12 @@ func (fc *funcConverter) convertBlock(astFunc *AstFunc, ssaBlock *ssa.BasicBlock
return err
}
stmt = defineVar(instr, castExpr)
case *ssa.MultiConvert:
castExpr, err := fc.castCallExpr(instr.Type(), instr.X)
if err != nil {
return err
}
stmt = defineVar(instr, castExpr)
case *ssa.Defer:
callExpr, err := fc.convertCall(instr.Call)
if err != nil {
Expand Down Expand Up @@ -594,6 +602,19 @@ func (fc *funcConverter) convertBlock(astFunc *AstFunc, ssaBlock *ssa.BasicBlock
if err != nil {
return err
}

if fieldName == "_" {
// Blank fields cannot be referenced; the store to them is a
// no-op, but its value expression may still have side effects.
fieldType := instr.Type().Underlying().(*types.Pointer).Elem()
fieldTypeExpr, err := fc.tc.Convert(fieldType)
if err != nil {
return err
}
stmt = defineVar(instr, ah.CallExprByName("new", fieldTypeExpr))
break
}

stmt = defineVar(instr, &ast.UnaryExpr{
Op: token.AND,
X: ah.SelectExpr(xExpr, ast.NewIdent(fieldName)),
Expand Down Expand Up @@ -732,12 +753,13 @@ func (fc *funcConverter) convertBlock(astFunc *AstFunc, ssaBlock *ssa.BasicBlock
}

if instr.IsString {
idxName := fc.tupleVarName(instr.Iter, 0)
runeIdxName := fc.tupleVarName(instr.Iter, 0)
iterValName := fc.tupleVarName(instr.Iter, 1)
offsetsName := fc.tupleVarName(instr.Iter, 2)

stmt = ah.BlockStmt(
ah.AssignStmt(ast.NewIdent(okName), &ast.BinaryExpr{
X: ast.NewIdent(idxName),
X: ast.NewIdent(runeIdxName),
Op: token.LSS,
Y: ah.CallExprByName("len", ast.NewIdent(iterValName)),
}),
Expand All @@ -747,9 +769,9 @@ func (fc *funcConverter) convertBlock(astFunc *AstFunc, ssaBlock *ssa.BasicBlock
&ast.AssignStmt{
Lhs: []ast.Expr{ast.NewIdent(keyName), ast.NewIdent(valName)},
Tok: token.ASSIGN,
Rhs: []ast.Expr{ast.NewIdent(idxName), ah.IndexExprByExpr(ast.NewIdent(iterValName), ast.NewIdent(idxName))},
Rhs: []ast.Expr{ah.IndexExprByExpr(ast.NewIdent(offsetsName), ast.NewIdent(runeIdxName)), ah.IndexExprByExpr(ast.NewIdent(iterValName), ast.NewIdent(runeIdxName))},
},
&ast.IncDecStmt{X: ast.NewIdent(idxName), Tok: token.INC},
&ast.IncDecStmt{X: ast.NewIdent(runeIdxName), Tok: token.INC},
),
},
)
Expand Down Expand Up @@ -779,22 +801,32 @@ func (fc *funcConverter) convertBlock(astFunc *AstFunc, ssaBlock *ssa.BasicBlock
return err
}
if isStringType(instr.X.Type()) {
idxName := fc.tupleVarName(instr, 0)
valName := fc.tupleVarName(instr, 1)
runeIdxName := fc.tupleVarName(instr, 0)
iterValName := fc.tupleVarName(instr, 1)
offsetsName := fc.tupleVarName(instr, 2)

astFunc.Vars[idxName] = types.Typ[types.Int]
astFunc.Vars[valName] = types.NewSlice(types.Typ[types.Rune])
astFunc.Vars[runeIdxName] = types.Typ[types.Int]
astFunc.Vars[iterValName] = types.NewSlice(types.Typ[types.Rune])
astFunc.Vars[offsetsName] = types.NewSlice(types.Typ[types.Int])

stmt = &ast.AssignStmt{
Lhs: []ast.Expr{ast.NewIdent(idxName), ast.NewIdent(valName)},
Tok: token.ASSIGN,
Rhs: []ast.Expr{
ah.IntLit(0),
ah.CallExpr(&ast.ArrayType{Elt: ast.NewIdent("rune")}, xExpr),
idxName := fc.tupleVarName(instr, 3)
runeName := fc.tupleVarName(instr, 4)

stmt = ah.BlockStmt(
&ast.RangeStmt{
Key: ast.NewIdent(idxName),
Value: ast.NewIdent(runeName),
Tok: token.DEFINE,
X: xExpr,
Body: ah.BlockStmt(
ah.AssignStmt(ast.NewIdent(iterValName), ah.CallExprByName("append", ast.NewIdent(iterValName), ast.NewIdent(runeName))),
ah.AssignStmt(ast.NewIdent(offsetsName), ah.CallExprByName("append", ast.NewIdent(offsetsName), ast.NewIdent(idxName))),
),
},
}
ah.AssignStmt(ast.NewIdent(runeIdxName), ah.IntLit(0)),
)
} else {
makeIterExpr, nextType, err := makeMapIteratorPolyfill(fc.tc, instr.X.Type().(*types.Map))
makeIterExpr, nextType, err := makeMapIteratorPolyfill(fc.tc, instr.X.Type().Underlying().(*types.Map))
if err != nil {
return err
}
Expand Down Expand Up @@ -835,7 +867,16 @@ func (fc *funcConverter) convertBlock(astFunc *AstFunc, ssaBlock *ssa.BasicBlock
if valHasRefs {
astFunc.Vars[valName] = valType
}
commStmt = ah.AssignStmt(ast.NewIdent(valName), &ast.UnaryExpr{Op: token.ARROW, X: chanExpr})
recvExpr := &ast.UnaryExpr{Op: token.ARROW, X: chanExpr}
if okHasRefs {
commStmt = &ast.AssignStmt{
Lhs: []ast.Expr{ast.NewIdent(valName), ast.NewIdent(okName)},
Tok: token.ASSIGN,
Rhs: []ast.Expr{recvExpr},
}
} else {
commStmt = ah.AssignStmt(ast.NewIdent(valName), recvExpr)
}
recvIndex++
default:
return fmt.Errorf("not supported select chan dir %d: %w", state.Dir, ErrUnsupported)
Expand Down Expand Up @@ -975,6 +1016,17 @@ func (fc *funcConverter) convertBlock(astFunc *AstFunc, ssaBlock *ssa.BasicBlock
}
case *ssa.MakeClosure:
anonFunc := instr.Fn.(*ssa.Function)
if strings.HasSuffix(anonFunc.Name(), "$bound") {
recvExpr, err := fc.convertSsaValue(instr.Bindings[0])
if err != nil {
return err
}
methodName := strings.TrimSuffix(anonFunc.Name(), "$bound")
methodName, _, _ = strings.Cut(methodName, "[")
stmt = defineVar(instr, ah.SelectExpr(recvExpr, ast.NewIdent(methodName)))
break
}

anonFuncName, err := fc.getAnonFunctionName(anonFunc)
if err != nil {
return err
Expand Down
79 changes: 79 additions & 0 deletions internal/ssa2ast/func_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ func slicesOps() {
return
}

type namedMap map[string]int

type namedString string

func iterAndMapsOps() {
sprintf := makeSprintf("iterAndMapsOps")

Expand Down Expand Up @@ -163,7 +167,28 @@ func iterAndMapsOps() {
sprintf(idx, s)
}

for i, r := range "héllo" {
sprintf(i, r)
}

for i, r := range "\xff\x41" {
sprintf(i, r)
}

sprintf(mmap["April"].String())

nm := namedMap{"a": 1, "b": 2}
var nmkeys []string
for k := range nm {
nmkeys = append(nmkeys, k)
}
sort.Strings(nmkeys)
sprintf(nmkeys)

ns := namedString("hello")
for i, r := range ns {
sprintf(i, r)
}
return
}

Expand Down Expand Up @@ -234,6 +259,12 @@ func methodOps() {
thunkMethod2 := (*structCalls).Return2
sprintf(thunkMethod2(&strct))

boundMethod := strct.Return1
sprintf(boundMethod())

boundMethod2 := strct.Return2
sprintf(boundMethod2())

closureVar := "c " + s
anonFnc := func(n func(structCalls) string) string {
return n(structCalls{}) + "anon" + closureVar
Expand Down Expand Up @@ -272,6 +303,22 @@ func chanOps() {
val, ok := <-a

sprintf(val, ok)

f := make(chan string, 1)
f <- "x"
select {
case r, ok := <-f:
sprintf("sel-ok", r, ok)
default:
sprintf("sel-default")
}
close(f)
select {
case r, ok := <-f:
sprintf("sel-closed", r, ok)
default:
sprintf("sel-default2")
}
return
}

Expand Down Expand Up @@ -305,6 +352,13 @@ type testStruct struct {
A, B int
}

type blankFieldStruct struct {
_ int
X int
}

type aliasStruct = struct{ X int }

func typeOps() {
sprintf := makeSprintf("typeOps")

Expand All @@ -331,6 +385,12 @@ func typeOps() {
strc.B += strc.A
sprintf(strc)

bs := blankFieldStruct{return42(), 7}
sprintf(bs.X)

as := aliasStruct{return42()}
sprintf(as.X)

// Access to unexported structure
discard := io.Discard
if return42() == 0 {
Expand All @@ -347,6 +407,14 @@ func sumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
return s
}

type genericBox[T any] struct {
val T
}

func (b genericBox[T]) get() T {
return b.val
}

func genericFunc() {
sprintf := makeSprintf("genericFunc")

Expand All @@ -361,6 +429,17 @@ func genericFunc() {
"second": 12.1,
}
sprintf(sumIntsOrFloats(floats))

sprintf(genericConvert(int64(34)))
sprintf(genericConvert(float64(12.5)))

box := genericBox[int]{val: 42}
getVal := box.get
sprintf(getVal())
}

func genericConvert[T ~int64 | ~float64](x T) float64 {
return float64(x)
}
`

Expand Down
11 changes: 4 additions & 7 deletions internal/ssa2ast/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"go/ast"
"go/token"
"go/types"
"reflect"
"strconv"
)

Expand Down Expand Up @@ -99,12 +98,10 @@ func (tc *TypeConverter) Convert(typ types.Type) (ast.Expr, error) {
case *types.Named:
obj := typ.Obj()

// TODO: rewrite struct inlining without reflection hack
if parent := obj.Parent(); parent != nil {
isFuncScope := reflect.ValueOf(parent).Elem().FieldByName("isFunc")
if isFuncScope.Bool() {
return tc.Convert(obj.Type().Underlying())
}
// A named type declared in a local (function or block) scope is not
// part of the converted output, so reference its underlying type.
if obj.Pkg() != nil && obj.Parent() != obj.Pkg().Scope() {
return tc.Convert(typ.Underlying())
}

var namedExpr ast.Expr
Expand Down
Loading
Loading