Skip to content

Commit

Permalink
feat: named and unnamed type assignment 2 of 3 (#1246)
Browse files Browse the repository at this point in the history
This is part 2 of 3 of the solution for issue #1141. The part 1 of 3 of
the solution can be found in issue #1143.

In this part of the solution, we have made several improvements:

- Support both named and unnamed type assignments in assignment
statements and function return values.
- Resolved the issue related to incorrect method selectors that is
caused by mixing named and unnamed assignments.
- Added 62 file tests to ensure the correctness of the code.
- Included 2 realm tests to further validate the cross realm assignment
and method selector.
- Enhanced the support for GNO file tests in nested directories. This
allows us to organize tests in intuitively named folders.

To achieve the above improvements in the preprocessing phase, we made
the following changes:

- Introduced an isNamed() function on the Type Interface and marked
named types with isNamed() returning true. This helps distinguish
between named and unnamed types.
- Followed the specifications to convert the right-hand side type into a
constant function type.
- As for determining the package associated with a test file, we've
maintained the original convention. We keeps relying on the comment
directive "//PKGPATH: gno.land/r/xyz" in the test file itself to
identify the package it belongs to. We do not using the local folder
structure to derive the package for file tests. Therefore the tests in
tests/files folder can be stored in any intuitively named sub
directories.

**NOTE:** The named and unnamed type conversions that involve the
decomposition of function calls returning multiple values in the
preprocess have not yet been included in this pull request. This
functionality will be addressed in part 3 of 3 of the entire solution.

<!-- please provide a detailed description of the changes made in this
pull request. -->

<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>

---------

Co-authored-by: deelawn <dboltz03@gmail.com>
Co-authored-by: Morgan Bazalgette <morgan@morganbaz.com>
  • Loading branch information
3 people committed May 31, 2024
1 parent 86f5624 commit b4a07ed
Show file tree
Hide file tree
Showing 75 changed files with 2,434 additions and 89 deletions.
22 changes: 22 additions & 0 deletions examples/gno.land/r/demo/tests/realm_compositelit.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package tests

type (
Word uint
nat []Word
)

var zero = &Int{
neg: true,
abs: []Word{0},
}

// structLit
type Int struct {
neg bool
abs nat
}

func GetZeroType() nat {
a := zero.abs
return a
}
19 changes: 19 additions & 0 deletions examples/gno.land/r/demo/tests/realm_method38d.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tests

var abs nat

func (n nat) Add() nat {
return []Word{0}
}

func GetAbs() nat {
abs = []Word{0}

return abs
}

func AbsAdd() nat {
rt := GetAbs().Add()

return rt
}
74 changes: 65 additions & 9 deletions gnovm/pkg/gnolang/preprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -1621,8 +1621,12 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
lhs0 := n.Lhs[0].(*NameExpr).Name
lhs1 := n.Lhs[1].(*NameExpr).Name

var mt *MapType
dt := evalStaticTypeOf(store, last, cx.X)
mt := baseOf(dt).(*MapType)
mt, ok := baseOf(dt).(*MapType)
if !ok {
panic(fmt.Sprintf("invalid index expression on %T", dt))
}
// re-definitions
last.Define(lhs0, anyValue(mt.Value))
last.Define(lhs1, anyValue(BoolType))
Expand Down Expand Up @@ -2263,12 +2267,12 @@ func getResultTypedValues(cx *CallExpr) []TypedValue {
func evalConst(store Store, last BlockNode, x Expr) *ConstExpr {
// TODO: some check or verification for ensuring x
// is constant? From the machine?
cv := NewMachine(".dontcare", store)
tv := cv.EvalStatic(last, x)
cv.Release()
m := NewMachine(".dontcare", store)
cv := m.EvalStatic(last, x)
m.Release()
cx := &ConstExpr{
Source: x,
TypedValue: tv,
TypedValue: cv,
}
cx.SetAttribute(ATTR_PREPROCESSED, true)
setConstAttrs(cx)
Expand Down Expand Up @@ -2465,11 +2469,13 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative
// "push" expected type into shift binary's left operand.
checkOrConvertType(store, last, &bx.Left, t, autoNative)
} else if *x != nil { // XXX if x != nil && t != nil {
// check type
xt := evalStaticTypeOf(store, last, *x)
if t != nil {
checkType(xt, t, autoNative)
}
if isUntyped(xt) {
// convert type
if isUntyped(xt) { // convert if x is untyped literal
if t == nil {
t = defaultTypeOf(xt)
}
Expand All @@ -2490,11 +2496,61 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative
// default:
}
}
cx := Expr(Call(constType(nil, t), *x))
cx = Preprocess(store, last, cx).(Expr)
*x = cx
// convert x to destination type t
convertType(store, last, x, t)
} else {
// if one side is declared name type and the other side is unnamed type
if isNamedConversion(xt, t) {
// covert right (xt) to the type of the left (t)
convertType(store, last, x, t)
}
}
}
}

// convert x to destination type t
func convertType(store Store, last BlockNode, x *Expr, t Type) {
cx := Expr(Call(constType(nil, t), *x))
cx = Preprocess(store, last, cx).(Expr)
*x = cx
}

// isNamedConversion returns true if assigning a value of type
// xt (rhs) into a value of type t (lhs) entails an implicit type conversion.
// xt is the result of an expression type.
//
// In a few special cases, we should not perform the conversion:
//
// case 1: the LHS is an interface, which is unnamed, so we should not
// convert to that even if right is a named type.
// case 2: isNamedConversion is called within evaluating make() or new()
// (uverse functions). It returns TypType (generic) which does have IsNamed appropriate
func isNamedConversion(xt, t Type) bool {
if t == nil {
t = xt
}

// no conversion case 1: the LHS is an interface

_, c1 := t.(*InterfaceType)

// no conversion case2: isNamedConversion is called within evaluating make() or new()
// (uverse functions)

_, oktt := t.(*TypeType)
_, oktt2 := xt.(*TypeType)
c2 := oktt || oktt2

//
if !c1 && !c2 { // carve out above two cases
// covert right to the type of left if one side is unnamed type and the other side is not

if t.IsNamed() && !xt.IsNamed() ||
!t.IsNamed() && xt.IsNamed() {
return true
}
}
return false
}

// like checkOrConvertType(last, x, nil)
Expand Down
81 changes: 79 additions & 2 deletions gnovm/pkg/gnolang/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Type interface {
String() string // for dev/debugging
Elem() Type // for TODO... types
GetPkgPath() string
IsNamed() bool // named vs unname type. property as a method
}

type TypeID string
Expand Down Expand Up @@ -323,6 +324,10 @@ func (pt PrimitiveType) GetPkgPath() string {
return ""
}

func (pt PrimitiveType) IsNamed() bool {
return true
}

// ----------------------------------------
// Field type (partial)

Expand Down Expand Up @@ -369,6 +374,10 @@ func (ft FieldType) GetPkgPath() string {
panic("FieldType is a pseudotype with no package path")
}

func (ft FieldType) IsNamed() bool {
panic("FieldType is a pseudotype with no property called named")
}

// ----------------------------------------
// FieldTypeList

Expand Down Expand Up @@ -528,6 +537,10 @@ func (at *ArrayType) GetPkgPath() string {
return ""
}

func (at *ArrayType) IsNamed() bool {
return false
}

// ----------------------------------------
// Slice type

Expand Down Expand Up @@ -574,6 +587,10 @@ func (st *SliceType) GetPkgPath() string {
return ""
}

func (st *SliceType) IsNamed() bool {
return false
}

// ----------------------------------------
// Pointer type

Expand Down Expand Up @@ -612,6 +629,10 @@ func (pt *PointerType) GetPkgPath() string {
return pt.Elt.GetPkgPath()
}

func (pt *PointerType) IsNamed() bool {
return false
}

func (pt *PointerType) FindEmbeddedFieldType(callerPath string, n Name, m map[Type]struct{}) (
trail []ValuePath, hasPtr bool, rcvr Type, field Type, accessError bool,
) {
Expand Down Expand Up @@ -747,6 +768,10 @@ func (st *StructType) GetPkgPath() string {
return st.PkgPath
}

func (st *StructType) IsNamed() bool {
return false
}

// NOTE only works for exposed non-embedded fields.
func (st *StructType) GetPathForName(n Name) ValuePath {
for i := 0; i < len(st.Fields); i++ {
Expand Down Expand Up @@ -867,6 +892,10 @@ func (pt *PackageType) GetPkgPath() string {
panic("package types has no package path (unlike package values)")
}

func (pt *PackageType) IsNamed() bool {
panic("package types have no property called named")
}

// ----------------------------------------
// Interface type

Expand Down Expand Up @@ -926,6 +955,10 @@ func (it *InterfaceType) GetPkgPath() string {
return it.PkgPath
}

func (it *InterfaceType) IsNamed() bool {
return false
}

func (it *InterfaceType) FindEmbeddedFieldType(callerPath string, n Name, m map[Type]struct{}) (
trail []ValuePath, hasPtr bool, rcvr Type, ft Type, accessError bool,
) {
Expand Down Expand Up @@ -1073,6 +1106,10 @@ func (ct *ChanType) GetPkgPath() string {
return ""
}

func (ct *ChanType) IsNamed() bool {
return false
}

// ----------------------------------------
// Function type

Expand Down Expand Up @@ -1280,6 +1317,10 @@ func (ft *FuncType) GetPkgPath() string {
panic("function types have no package path")
}

func (ft *FuncType) IsNamed() bool {
return false
}

func (ft *FuncType) HasVarg() bool {
if numParams := len(ft.Params); numParams == 0 {
return false
Expand Down Expand Up @@ -1338,6 +1379,10 @@ func (mt *MapType) GetPkgPath() string {
return ""
}

func (mt *MapType) IsNamed() bool {
return false
}

// ----------------------------------------
// Type (typeval) type

Expand Down Expand Up @@ -1366,6 +1411,10 @@ func (tt *TypeType) GetPkgPath() string {
panic("typeval types have no package path")
}

func (tt *TypeType) IsNamed() bool {
panic("typeval types have no property called 'named'")
}

// ----------------------------------------
// Declared type
// Declared types have a name, base (underlying) type,
Expand Down Expand Up @@ -1450,6 +1499,10 @@ func (dt *DeclaredType) GetPkgPath() string {
return dt.PkgPath
}

func (dt *DeclaredType) IsNamed() bool {
return true
}

func (dt *DeclaredType) DefineMethod(fv *FuncValue) {
if !dt.TryDefineMethod(fv) {
panic(fmt.Sprintf("redeclaration of method %s.%s",
Expand Down Expand Up @@ -1767,6 +1820,14 @@ func (nt *NativeType) GetPkgPath() string {
return "go:" + nt.Type.PkgPath()
}

func (nt *NativeType) IsNamed() bool {
if nt.Type.Name() != "" {
return true
} else {
return false
}
}

func (nt *NativeType) GnoType(store Store) Type {
if nt.gnoType == nil {
nt.gnoType = store.Go2GnoType(nt.Type)
Expand Down Expand Up @@ -1895,6 +1956,10 @@ func (bt blockType) GetPkgPath() string {
panic("blockType has no package path")
}

func (bt blockType) IsNamed() bool {
panic("blockType has no property called named")
}

// ----------------------------------------
// tupleType

Expand Down Expand Up @@ -1945,6 +2010,10 @@ func (tt *tupleType) GetPkgPath() string {
panic("typleType has no package path")
}

func (tt *tupleType) IsNamed() bool {
panic("typleType has no property called named")
}

// ----------------------------------------
// RefType

Expand All @@ -1965,11 +2034,15 @@ func (rt RefType) String() string {
}

func (rt RefType) Elem() Type {
panic("should not happen")
panic("RefType has no elem type")
}

func (rt RefType) GetPkgPath() string {
panic("should not happen")
panic("RefType has no package path")
}

func (rt RefType) IsNamed() bool {
panic("RefType has no property called named")
}

// ----------------------------------------
Expand Down Expand Up @@ -2002,6 +2075,10 @@ func (mn MaybeNativeType) GetPkgPath() string {
return mn.Type.GetPkgPath()
}

func (mn MaybeNativeType) IsNamed() bool {
return mn.Type.IsNamed()
}

// ----------------------------------------
// Kind

Expand Down
4 changes: 2 additions & 2 deletions gnovm/pkg/gnolang/uverse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestIssue1337PrintNilSliceAsUndefined(t *testing.T) {
var a []string
println(a)
}`,
expected: "nil []string\n",
expected: "(nil []string)\n",
},
{
name: "print non-empty slice",
Expand All @@ -57,7 +57,7 @@ func TestIssue1337PrintNilSliceAsUndefined(t *testing.T) {
var a map[string]string
println(a)
}`,
expected: "nil map[string]string\n",
expected: "(nil map[string]string)\n",
},
{
name: "print non-empty map",
Expand Down
2 changes: 1 addition & 1 deletion gnovm/pkg/gnolang/values_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo
default:
// The remaining types may have a nil value.
if tv.V == nil {
return nilStr + " " + tv.T.String()
return "(" + nilStr + " " + tv.T.String() + ")"
}

// *ArrayType, *SliceType, *StructType, *MapType
Expand Down
Loading

0 comments on commit b4a07ed

Please sign in to comment.