diff --git a/c_expr.go b/c_expr.go index 0d30e65..a0d69ff 100644 --- a/c_expr.go +++ b/c_expr.go @@ -330,7 +330,9 @@ func (g *translator) newCBinaryExpr(exp types.Type, x Expr, op BinaryOp, y Expr) Right: y, } } - typ := g.env.CommonType(x.CType(exp), y.CType(exp)) + xt := x.CType(exp) + yt := y.CType(exp) + typ := g.env.CommonType(xt, yt) x = g.cCast(typ, x) y = g.cCast(typ, y) return g.cCast(typ, &CBinaryExpr{ diff --git a/casts.go b/casts.go index bf5079b..060421b 100644 --- a/casts.go +++ b/casts.go @@ -7,20 +7,20 @@ import ( "github.com/gotranspile/cxgo/types" ) -func (g *translator) cCast(typ types.Type, x Expr) Expr { - if typ == nil { +func (g *translator) cCast(toType types.Type, x Expr) Expr { + if toType == nil { panic("no type") } - tk := typ.Kind() - xt := x.CType(typ) - xk := xt.Kind() - if xt == g.env.Go().Any() { - return &CCastExpr{Assert: true, Type: typ, Expr: x} + toKind := toType.Kind() + xType := x.CType(toType) + xKind := xType.Kind() + if xType == g.env.Go().Any() { + return &CCastExpr{Assert: true, Type: toType, Expr: x} } - if typ == g.env.Go().Any() { + if toType == g.env.Go().Any() { return x } - if at, ok := typ.(types.ArrayType); ok && at.IsSlice() { + if at, ok := toType.(types.ArrayType); ok && at.IsSlice() { switch x := x.(type) { case Nil: return g.Nil() @@ -29,7 +29,7 @@ func (g *translator) cCast(typ types.Type, x Expr) Expr { return g.Nil() } case *TakeAddr: - if ind, ok := x.X.(*CIndexExpr); ok && types.Same(ind.Expr.CType(nil), typ) { + if ind, ok := x.X.(*CIndexExpr); ok && types.Same(ind.Expr.CType(nil), toType) { if ind.IndexZero() { // special case: unwrap unnecessary cast to slice return ind.Expr @@ -42,30 +42,30 @@ func (g *translator) cCast(typ types.Type, x Expr) Expr { switch f.Identifier() { case gg.SliceFunc(), gg.AppendFunc(): - if types.Same(typ, fc.Args[0].CType(nil)) { + if types.Same(toType, fc.Args[0].CType(nil)) { return x } } } } } - if xk.Is(types.Array) && !tk.Is(types.Array) { + if xKind.Is(types.Array) && !toKind.Is(types.Array) { x = g.cAddr(x) - return g.cCast(typ, x) + return g.cCast(toType, x) } // equal or same type: no conversion - if types.Same(typ, xt) { + if types.Same(toType, xType) { return x } // unknown types: bypass - if tk.Is(types.Unknown) { + if toKind.Is(types.Unknown) { // special cases for well-known types - switch typ { + switch toType { case g.env.Go().String(): var fnc *types.Ident - if types.Same(xt, g.env.C().String()) { + if types.Same(xType, g.env.C().String()) { fnc = g.env.StringC2Go() - } else if types.Same(xt, g.env.C().WString()) { + } else if types.Same(xType, g.env.C().WString()) { fnc = g.env.WStringC2Go() } else { return g.cCast(g.env.C().String(), x) @@ -76,28 +76,28 @@ func (g *translator) cCast(typ types.Type, x Expr) Expr { } if c1, ok := cUnwrap(x).(*CCastExpr); ok { // casts A(A(x)) -> A(x) - if types.Same(c1.Type, typ) { + if types.Same(c1.Type, toType) { return c1 } } // conversions to bool - we have a specialized function for that - if tk.IsBool() { + if toKind.IsBool() { return g.ToBool(x) } // nil should be first, because it's an "untyped ptr" - if xk.Is(types.Nil) { - if tk.IsPtr() || tk.IsFunc() { + if xKind.Is(types.Nil) { + if toKind.IsPtr() || toKind.IsFunc() { return cUnwrap(x) } } // strings are immutable, so call a specialized function for conversion - if types.Same(xt, g.env.Go().String()) { + if types.Same(xType, g.env.Go().String()) { // string -> []byte - if at, ok := types.Unwrap(typ).(types.ArrayType); ok && at.IsSlice() && at.Elem() == g.env.Go().Byte() { + if at, ok := types.Unwrap(toType).(types.ArrayType); ok && at.IsSlice() && at.Elem() == g.env.Go().Byte() { return &CCastExpr{Type: at, Expr: x} } // [N]byte = "xyz" - if at, ok := types.Unwrap(typ).(types.ArrayType); ok && (types.Same(at.Elem(), g.env.Go().Byte()) || xk == types.Unknown) { + if at, ok := types.Unwrap(toType).(types.ArrayType); ok && (types.Same(at.Elem(), g.env.Go().Byte()) || xKind == types.Unknown) { if !at.IsSlice() { tmp := types.NewIdent("t", at) copyF := FuncIdent{g.env.Go().CopyFunc()} @@ -124,46 +124,46 @@ func (g *translator) cCast(typ types.Type, x Expr) Expr { return g.NewCCallExpr(lit, nil) } } - if types.Same(typ, g.env.C().WString()) { - return g.cCast(typ, g.NewCCallExpr(FuncIdent{g.env.WStringGo2C()}, []Expr{x})) + if types.Same(toType, g.env.C().WString()) { + return g.cCast(toType, g.NewCCallExpr(FuncIdent{g.env.WStringGo2C()}, []Expr{x})) } - return g.cCast(typ, g.NewCCallExpr(FuncIdent{g.env.StringGo2C()}, []Expr{x})) + return g.cCast(toType, g.NewCCallExpr(FuncIdent{g.env.StringGo2C()}, []Expr{x})) } - if xt == g.env.Go().String() { + if xType == g.env.Go().String() { var conv *types.Ident - if types.Same(typ, g.env.C().String()) { + if types.Same(toType, g.env.C().String()) { conv = g.env.StringGo2C() - } else if types.Same(typ, g.env.C().WString()) { + } else if types.Same(toType, g.env.C().WString()) { conv = g.env.WStringGo2C() } if conv != nil { - return g.cCast(typ, g.NewCCallExpr(FuncIdent{conv}, []Expr{x})) + return g.cCast(toType, g.NewCCallExpr(FuncIdent{conv}, []Expr{x})) } } // any casts from array to other types should go through pointer to an array - if xk.Is(types.Unknown) { + if xKind.Is(types.Unknown) { return &CCastExpr{ - Type: typ, + Type: toType, Expr: x, } } switch { - case tk.IsPtr(): - return g.cPtrToPtr(typ, g.ToPointer(x)) - case tk.IsInt(): + case toKind.IsPtr(): + return g.cPtrToPtr(toType, g.ToPointer(x)) + case toKind.IsInt(): if l, ok := cUnwrap(x).(IntLit); ok { - ti, ok := types.Unwrap(typ).(types.IntType) + ti, ok := types.Unwrap(toType).(types.IntType) if l.IsUint() && ok && ti.Signed() && !litCanStore(ti, l) { // try overflowing it return l.OverflowInt(ti.Sizeof()) } if l.IsUint() || (ok && ti.Signed()) { return &CCastExpr{ - Type: typ, + Type: toType, Expr: x, } } - sz := typ.Sizeof() + sz := toType.Sizeof() var uv uint64 switch sz { case 1: @@ -176,36 +176,36 @@ func (g *translator) cCast(typ types.Type, x Expr) Expr { uv = math.MaxUint64 default: return &CCastExpr{ - Type: typ, + Type: toType, Expr: x, } } uv -= uint64(-l.Int()) - 1 return cUintLit(uv, l.base) } - if xk.IsFunc() { + if xKind.IsFunc() { // func() -> int return &FuncToInt{ X: g.ToFunc(x, nil), - To: types.Unwrap(typ).(types.IntType), + To: types.Unwrap(toType).(types.IntType), } } - if xk.IsPtr() { + if xKind.IsPtr() { // *some -> int - return g.cPtrToInt(typ, g.ToPointer(x)) + return g.cPtrToInt(toType, g.ToPointer(x)) } - if x.IsConst() && xk.IsUntypedInt() { + if x.IsConst() && xKind.IsUntypedInt() { return x } - if xk.IsBool() { - return g.cCast(typ, &BoolToInt{X: g.ToBool(x)}) + if xKind.IsBool() { + return g.cCast(toType, &BoolToInt{X: g.ToBool(x)}) } - xi, ok1 := types.Unwrap(xt).(types.IntType) - ti, ok2 := types.Unwrap(typ).(types.IntType) + xi, ok1 := types.Unwrap(xType).(types.IntType) + ti, ok2 := types.Unwrap(toType).(types.IntType) if ok1 && ok2 && xi.Signed() != ti.Signed() { if ti.Sizeof() > xi.Sizeof() { return &CCastExpr{ - Type: typ, + Type: toType, Expr: x, } } else if ti.Sizeof() < xi.Sizeof() { @@ -216,16 +216,16 @@ func (g *translator) cCast(typ types.Type, x Expr) Expr { t2 = types.UintT(ti.Sizeof()) } return &CCastExpr{ - Type: typ, + Type: toType, Expr: g.cCast(t2, x), } } } return &CCastExpr{ - Type: typ, + Type: toType, Expr: x, } - case tk.IsFunc(): + case toKind.IsFunc(): switch x := cUnwrap(x).(type) { case Nil: return x @@ -234,11 +234,11 @@ func (g *translator) cCast(typ types.Type, x Expr) Expr { return g.Nil() } } - if !xk.IsFunc() { - x = g.ToFunc(x, types.Unwrap(typ).(*types.FuncType)) - return g.cCast(typ, x) + if !xKind.IsFunc() { + x = g.ToFunc(x, types.Unwrap(toType).(*types.FuncType)) + return g.cCast(toType, x) } - ft, fx := types.Unwrap(typ).(*types.FuncType), types.Unwrap(xt).(*types.FuncType) + ft, fx := types.Unwrap(toType).(*types.FuncType), types.Unwrap(xType).(*types.FuncType) if (ft.Variadic() == fx.Variadic() || !ft.Variadic()) && ft.ArgN() >= fx.ArgN() && ((ft.Return() != nil) == (fx.Return() != nil) || (ft.Return() == nil && fx.Return() != nil)) { // cannot cast directly, but can return lambda instead callArgs := make([]Expr, 0, ft.ArgN()) @@ -282,25 +282,25 @@ func (g *translator) cCast(typ types.Type, x Expr) Expr { } // incompatible function types - force error return x - case tk.IsFloat(): - if xk.IsUntypedFloat() { + case toKind.IsFloat(): + if xKind.IsUntypedFloat() { return x } - if xk.IsUntypedInt() { - if !tk.IsUntypedFloat() { - typ = types.AsUntypedFloatT(types.Unwrap(typ).(types.FloatType)) - tk = typ.Kind() + if xKind.IsUntypedInt() { + if !toKind.IsUntypedFloat() { + toType = types.AsUntypedFloatT(types.Unwrap(toType).(types.FloatType)) + toKind = toType.Kind() } } - if xk.IsBool() { - return g.cCast(typ, &BoolToInt{X: g.ToBool(x)}) + if xKind.IsBool() { + return g.cCast(toType, &BoolToInt{X: g.ToBool(x)}) } - case tk.Is(types.Array): - ta := types.Unwrap(typ).(types.ArrayType) - if xa, ok := types.Unwrap(xt).(types.ArrayType); ok && ta.Len() == 0 && xa.Len() != 0 { + case toKind.Is(types.Array): + ta := types.Unwrap(toType).(types.ArrayType) + if xa, ok := types.Unwrap(xType).(types.ArrayType); ok && ta.Len() == 0 && xa.Len() != 0 { return &SliceExpr{Expr: x} } - case tk.Is(types.Struct): + case toKind.Is(types.Struct): isZero := false switch x := cUnwrap(x).(type) { case Nil: @@ -311,11 +311,11 @@ func (g *translator) cCast(typ types.Type, x Expr) Expr { } } if isZero { - return g.NewCCompLitExpr(typ, nil) + return g.NewCCompLitExpr(toType, nil) } } return &CCastExpr{ - Type: typ, + Type: toType, Expr: x, } } diff --git a/nums_test.go b/nums_test.go index dbee651..e32196a 100644 --- a/nums_test.go +++ b/nums_test.go @@ -632,6 +632,21 @@ func foo(a float32, b float32, c int32, d float32) { d = float32(libc.BoolToInt(a < b) + c) d = float32(libc.BoolToInt(a < b)) + float32(c) } +`, + }, + { + name: "implicit and explicit bool const", + src: ` +void foo(float a) { + a = (4 < 4.5) + 3; + a = (4 < 4.5) + 3.5; +} +`, + exp: ` +func foo(a float32) { + a = float32(libc.BoolToInt(4 < 4.5) + 3) + a = float32(libc.BoolToInt(4 < 4.5)) + 3.5 +} `, }, { diff --git a/types/best.go b/types/best.go index e6f088e..64fc172 100644 --- a/types/best.go +++ b/types/best.go @@ -88,6 +88,23 @@ func (e *Env) CommonType(x, y Type) (otyp Type) { } return x } + case FloatType: + switch y := y.(type) { + case FloatType: + if x.Kind().IsUntypedFloat() { + x = AsTypedFloatT(x) + } + if y.Kind().IsUntypedFloat() { + y = AsTypedFloatT(y) + } + return y + case BoolType: + // float+bool = float + if x.Kind().IsUntyped() { + return e.DefFloatT() + } + return x + } case BoolType: switch y := y.(type) { case IntType: @@ -96,6 +113,12 @@ func (e *Env) CommonType(x, y Type) (otyp Type) { return e.DefIntT() } return y + case FloatType: + // bool+float = float + if y.Kind().IsUntyped() { + return e.DefFloatT() + } + return y } case ArrayType: switch y.(type) { diff --git a/types/env.go b/types/env.go index fd48c8c..1b9c756 100644 --- a/types/env.go +++ b/types/env.go @@ -148,6 +148,11 @@ func (e *Env) DefUintT() Type { return UintT(e.conf.IntSize) } +// DefFloatT returns a default float type. +func (e *Env) DefFloatT() Type { + return FloatT(e.conf.IntSize) +} + // FuncT returns a function type with a given return type and named arguments. // It's mostly useful for function declarations. See FuncTT for simplified version. func (e *Env) FuncT(ret Type, args ...*Field) *FuncType { diff --git a/types/types_test.go b/types/types_test.go index a392bb1..a281ae2 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -38,3 +38,26 @@ func TestSameInt(t *testing.T) { }) } } + +func TestCommonType(t *testing.T) { + cases := []struct { + name string + x Type + y Type + exp Type + }{ + {"bool and int", BoolT(), IntT(1), IntT(1)}, + {"bool and untyped int", BoolT(), AsUntypedIntT(IntT(1)), IntT(8)}, + {"bool and float", BoolT(), FloatT(8), FloatT(8)}, + {"bool and untyped float", BoolT(), AsUntypedFloatT(FloatT(8)), FloatT(8)}, + } + e := NewEnv(Default()) + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := e.CommonType(c.x, c.y) + require.Equal(t, c.exp, got) + got = e.CommonType(c.y, c.x) + require.Equal(t, c.exp, got) + }) + } +}