package types import ( "git.smesh.lol/moxie/pkg/syntax" "git.smesh.lol/moxie/pkg/token" ) // resolveTypeExpr resolves a type expression to a Type. // This is the core of the type system - translating AST type nodes to Type values. func (c *Checker) resolveTypeExpr(e syntax.Expr) (t Type) { if e == nil { return nil } switch ev := e.(type) { case *syntax.Name: return c.resolveTypeName(ev) case *syntax.SelectorExpr: return c.resolveQualifiedTypeName(ev) case *syntax.Operation: // *T (pointer) if ev.Y == nil && ev.Op == token.Mul { base := c.resolveTypeExpr(ev.X) if base == nil { return nil } return NewPointer(base) } // ~T (approximation element in type constraints) - treat as T for B1 if ev.Y == nil && ev.Op == token.Tilde { return c.resolveTypeExpr(ev.X) } // T | U (union element in type constraints) - return first operand for B1 if ev.Y != nil && ev.Op == token.Or { c.resolveTypeExpr(ev.Y) // resolve both sides to catch undefined errors return c.resolveTypeExpr(ev.X) } case *syntax.SliceType: elem := c.resolveTypeExpr(ev.Elem) if elem == nil { return nil } if b, ok := elem.(*Basic); ok && b.Kind == Uint8 { return Typ[TCString] } return NewSlice(elem) case *syntax.ArrayType: elem := c.resolveTypeExpr(ev.Elem) if elem == nil { return nil } if ev.Len == nil { return NewArray(elem, -1) } n := c.evalArrayLen(ev.Len) return NewArray(elem, n) case *syntax.MapType: key := c.resolveTypeExpr(ev.Key) val := c.resolveTypeExpr(ev.Value) if key == nil || val == nil { return nil } return NewTCMap(key, val) case *syntax.ChanType: elem := c.resolveTypeExpr(ev.Elem) if elem == nil { return nil } var dir TCChanDir switch ev.Dir { case syntax.SendOnly: dir = TCSendOnly case syntax.RecvOnly: dir = TCRecvOnly default: dir = TCSendRecv } return NewTCChan(dir, elem) case *syntax.StructType: return c.resolveStructType(ev) case *syntax.InterfaceType: return c.resolveInterfaceType(ev) case *syntax.FuncType: return c.resolveFuncType(ev, nil) case *syntax.IndexExpr: // generic instantiation T[A, B, ...] return c.resolveGenericInst(ev) case *syntax.DotsType: elem := c.resolveTypeExpr(ev.Elem) if elem == nil { return nil } if b, ok := elem.(*Basic); ok && b.Kind == Uint8 { return Typ[TCString] } return NewSlice(elem) } c.errorf(e.Pos(), "cannot resolve type expression") return nil } func (c *Checker) resolveTypeName(e *syntax.Name) (t Type) { _, obj := c.lookupType(e.Value) if obj == nil { c.errorf(e.Pos(), "undefined: " | e.Value) return nil } if tn, ok := obj.(*TypeName); ok { if c.info != nil { c.info.Uses[e] = tn } return tn.Typ } c.errorf(e.Pos(), e.Value | " is not a type") return nil } func (c *Checker) resolveQualifiedTypeName(e *syntax.SelectorExpr) (t Type) { pkgName, ok := e.X.(*syntax.Name) if !ok { c.errorf(e.Pos(), "invalid type expression") return nil } // lookupType walks localScope -> fileScope -> pkg scope so the file's // own import binding wins over a same-named import in another file. _, obj := c.lookupType(pkgName.Value) if obj == nil { c.errorf(e.Pos(), "undefined: " | pkgName.Value) return nil } pkgObj, ok2 := obj.(*PkgName) if !ok2 { c.errorf(e.Pos(), pkgName.Value | " is not a package") return nil } imported := pkgObj.Imported if imported == nil { return nil } typeObj := imported.Scope.Lookup(e.Sel.Value) if typeObj == nil { c.errorf(e.Sel.Pos(), "undefined: " | pkgName.Value | "." | e.Sel.Value) return nil } if tn, ok3 := typeObj.(*TypeName); ok3 { return tn.Typ } c.errorf(e.Sel.Pos(), pkgName.Value | "." | e.Sel.Value | " is not a type") return nil } func (c *Checker) resolveStructType(e *syntax.StructType) (t *TCStruct) { var fields []*TCVar var tags []string for i, f := range e.FieldList { typ := c.resolveTypeExpr(f.Type) if f.Name != nil { push(fields, NewTCField(c.pkg, f.Name.Value, typ, false)) } else { // anonymous/embedded name := TypeBaseName(typ) push(fields, NewTCField(c.pkg, name, typ, true)) } tag := "" if i < len(e.TagList) && e.TagList[i] != nil { tag = e.TagList[i].Value } push(tags, tag) } return NewTCStruct(fields, tags) } func (c *Checker) resolveInterfaceType(e *syntax.InterfaceType) (t *TCInterface) { var methods []*IfaceMethod var embeds []Type for _, f := range e.MethodList { if f.Name == nil { // embedded type typ := c.resolveTypeExpr(f.Type) if typ != nil { push(embeds, typ) } continue } ft, ok := f.Type.(*syntax.FuncType) if !ok { continue } sig := c.resolveFuncType(ft, nil) if sig != nil { push(methods, &IfaceMethod{Name: f.Name.Value, Sig: sig}) } } iface := NewTCInterface(methods, embeds) iface.Complete() return iface } // resolveFuncType converts a FuncType AST node to a Signature. // recv is optional (non-nil only for method declarations). func (c *Checker) resolveFuncType(ft *syntax.FuncType, recv *syntax.Field) (s *Signature) { if ft == nil { return nil } var recvVar *TCVar if recv != nil { recvTyp := c.resolveTypeExpr(recv.Type) recvName := "" if recv.Name != nil { recvName = recv.Name.Value } recvVar = NewTCVar(c.pkg, recvName, recvTyp) } params := c.resolveFieldList(ft.ParamList) results := c.resolveFieldList(ft.ResultList) variadic := false if len(ft.ParamList) > 0 { last := ft.ParamList[len(ft.ParamList)-1] if _, ok := last.Type.(*syntax.DotsType); ok { variadic = true // unwrap: the slice type is already resolved in resolveFieldList } } return NewSignature(recvVar, params, results, variadic) } func (c *Checker) resolveFieldList(fields []*syntax.Field) (t *Tuple) { if len(fields) == 0 { return nil } var vars []*TCVar for _, f := range fields { typ := c.resolveTypeExpr(f.Type) name := "" if f.Name != nil { name = f.Name.Value } push(vars, NewTCVar(c.pkg, name, typ)) } return NewTuple(vars...) } func (c *Checker) resolveGenericInst(e *syntax.IndexExpr) (t Type) { // TODO: full generic instantiation return c.resolveTypeExpr(e.X) } // TypeBaseName returns the name of the base type for an anonymous field. func TypeBaseName(t Type) (s string) { if t == nil { return "" } switch tt := t.(type) { case *Named: if tt.Obj != nil { return tt.Obj.Name } case *Pointer: return TypeBaseName(tt.Base) case *Basic: return tt.Name } return "" }