diff --git a/ast/node.go b/ast/node.go index 68c9e7be..03e8cf62 100644 --- a/ast/node.go +++ b/ast/node.go @@ -133,6 +133,7 @@ type MemberNode struct { Node Node // Node of the member access. Like "foo" in "foo.bar". Property Node // Property of the member access. For property access it is a StringNode. Optional bool // If true then the member access is optional. Like "foo?.bar". + Method bool } // SliceNode represents access to a slice of an array. diff --git a/checker/checker.go b/checker/checker.go index 0b5a0227..af6991c7 100644 --- a/checker/checker.go +++ b/checker/checker.go @@ -54,7 +54,6 @@ type checker struct { config *conf.Config predicateScopes []predicateScope varScopes []varScope - parents []ast.Node err *file.Error } @@ -83,7 +82,6 @@ type info struct { func (v *checker) visit(node ast.Node) (reflect.Type, info) { var t reflect.Type var i info - v.parents = append(v.parents, node) switch n := node.(type) { case *ast.NilNode: t, i = v.NilNode(n) @@ -130,7 +128,6 @@ func (v *checker) visit(node ast.Node) (reflect.Type, info) { default: panic(fmt.Sprintf("undefined node type (%T)", node)) } - v.parents = v.parents[:len(v.parents)-1] node.SetType(t) return t, i } @@ -431,9 +428,6 @@ func (v *checker) ChainNode(node *ast.ChainNode) (reflect.Type, info) { } func (v *checker) MemberNode(node *ast.MemberNode) (reflect.Type, info) { - base, _ := v.visit(node.Node) - prop, _ := v.visit(node.Property) - // $env variable if an, ok := node.Node.(*ast.IdentifierNode); ok && an.Value == "$env" { if name, ok := node.Property.(*ast.StringNode); ok { @@ -450,6 +444,9 @@ func (v *checker) MemberNode(node *ast.MemberNode) (reflect.Type, info) { return anyType, info{} } + base, _ := v.visit(node.Node) + prop, _ := v.visit(node.Property) + if name, ok := node.Property.(*ast.StringNode); ok { if base == nil { return v.error(node, "type %v has no field %v", base, name.Value) @@ -498,10 +495,8 @@ func (v *checker) MemberNode(node *ast.MemberNode) (reflect.Type, info) { if field, ok := fetchField(base, propertyName); ok { return field.Type, info{} } - if len(v.parents) > 1 { - if _, ok := v.parents[len(v.parents)-2].(*ast.CallNode); ok { - return v.error(node, "type %v has no method %v", base, propertyName) - } + if node.Method { + return v.error(node, "type %v has no method %v", base, propertyName) } return v.error(node, "type %v has no field %v", base, propertyName) } diff --git a/parser/parser.go b/parser/parser.go index 1ce1cda0..caab130d 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -572,6 +572,7 @@ func (p *parser) parsePostfixExpression(node Node) Node { memberNode.SetLocation(propertyToken.Location) if p.current.Is(Bracket, "(") { + memberNode.Method = true node = &CallNode{ Callee: memberNode, Arguments: p.parseArguments(), diff --git a/parser/parser_test.go b/parser/parser_test.go index 0e7a2383..362c41fe 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -178,13 +178,13 @@ world`}, { "foo.bar()", &CallNode{Callee: &MemberNode{Node: &IdentifierNode{Value: "foo"}, - Property: &StringNode{Value: "bar"}}, + Property: &StringNode{Value: "bar"}, Method: true}, Arguments: []Node{}}, }, { `foo.bar("arg1", 2, true)`, &CallNode{Callee: &MemberNode{Node: &IdentifierNode{Value: "foo"}, - Property: &StringNode{Value: "bar"}}, + Property: &StringNode{Value: "bar"}, Method: true}, Arguments: []Node{&StringNode{Value: "arg1"}, &IntegerNode{Value: 2}, &BoolNode{Value: true}}}, @@ -220,12 +220,14 @@ world`}, Property: &StringNode{ Value: "b", }, + Method: true, }, Arguments: []Node{}, }, Property: &StringNode{ Value: "c", }, + Method: true, }, Arguments: []Node{}, },