Skip to content

Commit

Permalink
Implemented interface duck type casting
Browse files Browse the repository at this point in the history
  • Loading branch information
ritchiecarroll committed Oct 12, 2024
1 parent 1173385 commit 96949f1
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/Tests/Behavioral/ArrayPassByValue/ArrayPassByValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ private static void Main() {
test3(a[..]);
fmt.Println(a[0], a[1]);
fmt.Println();
var primes = new nint[] { 2, 3, 5, 7, 11, 13 };
var primes = new nint[] {2, 3, 5, 7, 11, 13 };
fmt.Println(primes);
fmt.Println(a[0]);
stest(p);
Expand Down
14 changes: 9 additions & 5 deletions src/Tests/Behavioral/InterfaceCasting/InterfaceCasting.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
namespace go;

using fmt = fmt_package;

public static partial class main_package {

[GoType("struct")]
public partial struct MyError {
public @string description;
}
Expand All @@ -12,15 +15,16 @@ public static @string Error(this MyError err) {

// error is an interface - MyError is cast to error interface upon return
private static error f() {
return error.As(new MyError("foo"))!;
return error.As(new MyError("foo"u8));
}

private static void Main() {
error err = default!;

err = error.As(new MyError("bar"))!;
error err;

fmt.Printf("%v %v\n"u8, f(), err); // error: foo
err = error.As(new MyError("bar"u8));
fmt.Printf("%v %v\n"u8, f(), err);
}

// error: foo

} // end main_package
23 changes: 16 additions & 7 deletions src/Tests/Behavioral/InterfaceCasting/InterfaceCasting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
<PackageProjectUrl>https://github.com/GridProtectionAlliance/go2cs</PackageProjectUrl>
<RepositoryUrl>https://github.com/GridProtectionAlliance/go2cs</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<ApplicationIcon>$(GOPATH)\src\go2cs\go2cs.ico</ApplicationIcon>
<Nullable>enable</Nullable>
<NoWarn>660;661;IDE1006</NoWarn>
<Version>0.1.0</Version>
Expand All @@ -21,6 +20,14 @@
<OutDir>bin\$(Configuration)\$(TargetFramework)\</OutDir>
</PropertyGroup>

<ItemGroup>
<Compile Remove="InterfaceCasting_MyErrorStruct.cs" />
</ItemGroup>

<ItemGroup>
<None Remove="InterfaceCasting.metadata" />
</ItemGroup>

<ItemGroup>
<Using Include="go.builtin" Static="True" />
<Using Include="System.Byte" Alias="uint8" />
Expand All @@ -36,13 +43,15 @@
<Using Include="System.Numerics.Complex" Alias="complex128" />
<Using Include="System.Int32" Alias="rune" />
<Using Include="System.UIntPtr" Alias="uintptr" />
<Using Include="System.Numerics.BigInteger" Alias="GoUntyped" />
<Using Include="System.ComponentModel.DescriptionAttribute" Alias="GoTag" />

<ProjectReference Include="..\..\..\gocore\golib\golib.csproj" />
<ProjectReference Include="..\..\..\gocore\fmt\fmt.csproj" />
</ItemGroup>

<Reference Include="golib">
<HintPath>$(GOPATH)\src\go2cs\golib\$(OutDir)golib.dll</HintPath>
</Reference>
<Reference Include="fmt">
<HintPath>$(GOPATH)\src\go2cs\fmt\$(OutDir)fmt_package.dll</HintPath>
</Reference>
<ItemGroup>
<ProjectReference Include="..\..\..\go2cs.CodeGenerators\go2cs.CodeGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/Tests/Behavioral/SortArrayType/SortArrayType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ private static void Main() {
fmt.Println(x);
// Where am I?
// Person slice
var people = new Person[] { // EOL Comment
var people = new Person[] { // EOL Comment
new(
Name: "Person1"u8, // Name
Age: 26, // Age
Expand Down
7 changes: 4 additions & 3 deletions src/go2cs2/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
"args": [
//"-var=false",
"-tree",
//"H:\\Projects\\go2cs\\src\\Tests\\Behavioral\\SortArrayType\\SortArrayType.go",
//"H:\\Projects\\go2cs\\src\\Tests\\Behavioral\\ArrayPassByValue\\ArrayPassByValue.go",
"H:\\Projects\\go2cs\\src\\Tests\\Behavioral\\ExprSwitch\\ExprSwitch.go",
//"..\\Tests\\Behavioral\\SortArrayType\\SortArrayType.go",
//"..\\Tests\\Behavioral\\ArrayPassByValue\\ArrayPassByValue.go",
//"..\\Tests\\Behavioral\\ExprSwitch\\ExprSwitch.go",
"..\\Tests\\Behavioral\\InterfaceCasting\\InterfaceCasting.go",
]
}
]
Expand Down
25 changes: 20 additions & 5 deletions src/go2cs2/convCompositeLit.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ func (v *Visitor) convCompositeLit(compositeLit *ast.CompositeLit) string {
result := &strings.Builder{}

if compositeLit.Type == nil {

rparenSuffix := ""

if len(compositeLit.Elts) > 0 && v.isLineFeedBetween(compositeLit.Elts[len(compositeLit.Elts)-1].End(), compositeLit.Rbrace) {
Expand All @@ -24,29 +23,45 @@ func (v *Visitor) convCompositeLit(compositeLit *ast.CompositeLit) string {
return result.String()
}

isArrayType := false
sliceSuffix := ""

// TODO: Handle map type construction
if arrayType, ok := compositeLit.Type.(*ast.ArrayType); ok {
isArrayType = true

if arrayType.Len == nil {
sliceSuffix = ".slice()"
}
}

rbracePrefix := " "
lbracePrefix := ""
rbracePrefix := ""

if isArrayType {
lbracePrefix = " "
rbracePrefix = " "
}

if len(compositeLit.Elts) > 0 && v.isLineFeedBetween(compositeLit.Elts[len(compositeLit.Elts)-1].Pos(), compositeLit.Rbrace) {
rbracePrefix = fmt.Sprintf("%s%s", v.newline, v.indent(v.indentLevel))
}

result.WriteString(fmt.Sprintf("new %s { ", v.convExpr(compositeLit.Type, nil)))
lbraceChar := "("
rbraceChar := ")"

if isArrayType {
lbraceChar = "{"
rbraceChar = "}"
}

lbraceSuffix := ""
result.WriteString(fmt.Sprintf("new %s%s%s", v.convExpr(compositeLit.Type, nil), lbracePrefix, lbraceChar))

if len(compositeLit.Elts) > 0 {
v.writeStandAloneCommentString(result, compositeLit.Elts[0].Pos(), nil, " ")
}

result.WriteString(fmt.Sprintf("%s%s%s}%s", lbraceSuffix, v.convExprList(compositeLit.Elts, compositeLit.Lbrace, nil), rbracePrefix, sliceSuffix))
result.WriteString(fmt.Sprintf("%s%s%s%s", v.convExprList(compositeLit.Elts, compositeLit.Lbrace, nil), rbracePrefix, rbraceChar, sliceSuffix))

return result.String()
}
82 changes: 72 additions & 10 deletions src/go2cs2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,22 +434,54 @@ func isComparisonOperator(op string) bool {
}
}

func (v *Visitor) getTypeName(expr ast.Expr, underlying bool) string {
exprType := v.info.TypeOf(expr)
func (v *Visitor) isInterface(ident *ast.Ident) bool {
obj := v.info.ObjectOf(ident)

if underlying {
exprType = exprType.Underlying()
if obj == nil {
return false
}

return getTypeName(exprType)
return isInterface(obj.Type())
}

func getTypeName(t types.Type) string {
if named, ok := t.(*types.Named); ok {
return named.Obj().Name()
func isInterface(t types.Type) bool {
exprType := t.Underlying()

_, isInterface := exprType.(*types.Interface)

return isInterface
}

func paramsAreInterfaces(paramTypes *types.Tuple) []bool {
if paramTypes == nil {
return nil
}

return t.String()
paramIsInterface := make([]bool, paramTypes.Len())

for i := 0; i < paramTypes.Len(); i++ {
param := paramTypes.At(i)
paramIsInterface[i] = isInterface(param.Type())
}

return paramIsInterface
}

func (v *Visitor) convertToInterfaceType(interfaceExpr ast.Expr, targetExpr string) string {
return convertToInterfaceType(v.getType(interfaceExpr, false), targetExpr)
}

func convertToInterfaceType(interfaceType types.Type, targetExpr string) string {
result := &strings.Builder{}

// Convert to interface type using Go converted interface ".As" method,
// this handles duck typed Go interface implementations
result.WriteString(convertToCSTypeName(getTypeName(interfaceType)))
result.WriteString(".As(")
result.WriteString(targetExpr)
result.WriteRune(')')

return result.String()
}

func getIdentifier(node ast.Node) *ast.Ident {
Expand All @@ -468,6 +500,32 @@ func getIdentifier(node ast.Node) *ast.Ident {
return ident
}

func (v *Visitor) getType(expr ast.Expr, underlying bool) types.Type {
exprType := v.info.TypeOf(expr)

if exprType == nil {
return nil
}

if underlying {
return exprType.Underlying()
}

return exprType
}

func (v *Visitor) getTypeName(expr ast.Expr, underlying bool) string {
return getTypeName(v.getType(expr, underlying))
}

func getTypeName(t types.Type) string {
if named, ok := t.(*types.Named); ok {
return named.Obj().Name()
}

return t.String()
}

func getCSTypeName(t types.Type) string {
return convertToCSTypeName(getTypeName(t))
}
Expand Down Expand Up @@ -516,6 +574,10 @@ func convertToCSFullTypeName(typeName string) string {
switch typeName {
case "int":
return "nint"
case "uint":
return "nuint"
case "bool":
return "bool"
case "float":
return "float64"
case "complex64":
Expand All @@ -525,7 +587,7 @@ func convertToCSFullTypeName(typeName string) string {
case "interface{}":
return "object"
default:
return typeName
return getSanitizedIdentifier(typeName)
}
}

Expand Down
34 changes: 31 additions & 3 deletions src/go2cs2/visitAssignStmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ func (v *Visitor) visitAssignStmt(assignStmt *ast.AssignStmt, parentBlock *ast.B
reassignedCount := 0
declaredCount := 0

// Check for interface types in LHS as RHS will need to be casted to the interface type
lhsTypeIsInterface := make([]bool, lhsLen)

// Check for string types in LHS, u8 readonly spans are not supported in value tuple
lhsTypeIsString := make([]bool, lhsLen)
anyTypeIsString := false
Expand All @@ -45,6 +48,8 @@ func (v *Visitor) visitAssignStmt(assignStmt *ast.AssignStmt, parentBlock *ast.B
}
}

lhsTypeIsInterface[i] = v.isInterface(ident)

typeName := v.getTypeName(ident, true)

lhsTypeIsString[i] = typeName == "string"
Expand Down Expand Up @@ -101,7 +106,14 @@ func (v *Visitor) visitAssignStmt(assignStmt *ast.AssignStmt, parentBlock *ast.B
if i > 0 {
result.WriteString(", ")
}
result.WriteString(v.convExpr(rhs, nil))

rhsExpr := v.convExpr(rhs, nil)

if lhsTypeIsInterface[i] {
result.WriteString(v.convertToInterfaceType(assignStmt.Lhs[i], rhsExpr))
} else {
result.WriteString(rhsExpr)
}
}

if rhsLen > 1 {
Expand Down Expand Up @@ -130,7 +142,15 @@ func (v *Visitor) visitAssignStmt(assignStmt *ast.AssignStmt, parentBlock *ast.B
if v.isReassignment(ident) {
result.WriteString(v.convExpr(lhs, nil))
result.WriteString(" = ")
result.WriteString(v.convExpr(rhs, nil))

rhsExpr := v.convExpr(rhs, nil)

if lhsTypeIsInterface[i] {
result.WriteString(v.convertToInterfaceType(assignStmt.Lhs[i], rhsExpr))
} else {
result.WriteString(rhsExpr)
}

result.WriteString(";")
} else if lhsTypeIsString[i] {
// Handle string variables
Expand Down Expand Up @@ -159,7 +179,15 @@ func (v *Visitor) visitAssignStmt(assignStmt *ast.AssignStmt, parentBlock *ast.B

result.WriteString(v.convExpr(lhs, nil))
result.WriteString(" = ")
result.WriteString(v.convExpr(rhs, nil))

rhsExpr := v.convExpr(rhs, nil)

if lhsTypeIsInterface[i] {
result.WriteString(v.convertToInterfaceType(assignStmt.Lhs[i], rhsExpr))
} else {
result.WriteString(rhsExpr)
}

result.WriteString(";")
}
}
Expand Down
1 change: 1 addition & 0 deletions src/go2cs2/visitFuncDecl.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func (v *Visitor) visitFuncDecl(funcDecl *ast.FuncDecl) {
paramName := v.getIdentName(ident)

resultParameters.WriteString(v.newline)

v.writeString(resultParameters, fmt.Sprintf("%s%s %s = default;", v.indent(v.indentLevel+1), getCSTypeName(param.Type()), paramName))

paramIndex++
Expand Down
14 changes: 12 additions & 2 deletions src/go2cs2/visitReturnStmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ func (v *Visitor) visitReturnStmt(returnStmt *ast.ReturnStmt) {
v.writeOutput("return")

var context *BasicLitContext
signature := v.currentFunction.Signature()

if returnStmt.Results == nil {
// Check if result signature has named return values
if v.currentFunction != nil {
signature := v.currentFunction.Signature()

results := &strings.Builder{}

Expand Down Expand Up @@ -57,12 +57,22 @@ func (v *Visitor) visitReturnStmt(returnStmt *ast.ReturnStmt) {
context = &BasicLitContext{u8StringOK: false}
}

resultParams := signature.Results()
resultParamIsInterface := paramsAreInterfaces(resultParams)

for i, expr := range returnStmt.Results {
if i > 0 {
v.targetFile.WriteString(", ")
}

v.targetFile.WriteString(v.convExpr(expr, context))
resultExpr := v.convExpr(expr, context)

if resultParamIsInterface[i] {
resultParamType := resultParams.At(i).Type()
v.targetFile.WriteString(convertToInterfaceType(resultParamType, resultExpr))
} else {
v.targetFile.WriteString(resultExpr)
}
}

if len(returnStmt.Results) > 1 {
Expand Down
Loading

0 comments on commit 96949f1

Please sign in to comment.