diff --git a/cli/run.go b/cli/run.go index d124517de9..19df0871e8 100644 --- a/cli/run.go +++ b/cli/run.go @@ -152,7 +152,7 @@ func (obj *RunArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) { deploy, err := gapiObj.Cli(info) if err != nil { - return false, cliUtil.CliParseError(err) // consistent errors + return false, cliUtil.CliParseError(err) // TODO: it seems unlikely that parsing the CLI failed at this stage, and then the error will be misleading } if cmd := obj.RunLang; cmd != nil && cmd.OnlyUnify && deploy == nil { diff --git a/examples/lang-errors/simple_types.mcl b/examples/lang-errors/simple_types.mcl new file mode 100644 index 0000000000..0ed381d948 --- /dev/null +++ b/examples/lang-errors/simple_types.mcl @@ -0,0 +1,9 @@ + +$string = "hi" +$number = 7 + +if $string == $number { + test "faulty" { + anotherstr => "this should not run through type checking", + } +} diff --git a/lang/ast/structs.go b/lang/ast/structs.go index b32e0fe82e..8c025627fc 100644 --- a/lang/ast/structs.go +++ b/lang/ast/structs.go @@ -177,12 +177,53 @@ var ( orderingGraphSingleton = false ) +// TextArea stores the coordinates of a statement or expression in the form of +// starting line/column and ending line/column +type TextArea struct { + startLine int + startColumn int + endLine int + endColumn int + + // Bug5819 works around issue https://github.com/golang/go/issues/5819 + Bug5819 interface{} // XXX: workaround +} + +// Locate is used by the parser to store the token positions in AST nodes +// TODO: also note down the file name containing the statement/expression +// ...this is currently hard because the parser is blissfully unaware +func (a *TextArea) Locate(line int, col int, endline int, endcol int) { + a.startLine = line + a.startColumn = col + a.endLine = endline + a.endColumn = endcol +} + +// LocalNode is the interface implemented by AST nodes that store their code +// position. It is implemented by node types that embed TextArea. +type LocalNode interface { + Locate(int, int, int, int) + GetPosition() (int, int) + GetEndPosition() (int, int) +} + +// GetPosition returns the starting line/column of an AST node +func (a TextArea) GetPosition() (int, int) { + return a.startLine, a.startColumn +} + +// GetEndPosition returns the end line/column of an AST node +func (a TextArea) GetEndPosition() (int, int) { + return a.endLine, a.endColumn +} + // StmtBind is a representation of an assignment, which binds a variable to an // expression. type StmtBind struct { Ident string Value interfaces.Expr Type *types.Type + TextArea } // String returns a short representation of this statement. @@ -220,11 +261,9 @@ func (obj *StmtBind) Interpolate() (interfaces.Stmt, error) { if err != nil { return nil, err } - return &StmtBind{ - Ident: obj.Ident, - Value: interpolated, - Type: obj.Type, - }, nil + result := *obj + result.Value = interpolated + return &result, nil } // Copy returns a light copy of this struct. Anything static will not be copied. @@ -320,6 +359,7 @@ func (obj *StmtBind) TypeCheck() ([]*interfaces.UnificationInvariant, error) { invar := &interfaces.UnificationInvariant{ Expr: obj.Value, + Node: obj, Expect: typExpr, // obj.Type Actual: typ, } @@ -365,6 +405,7 @@ type StmtRes struct { Name interfaces.Expr // unique name for the res of this kind namePtr interfaces.Func // ptr for table lookup Contents []StmtResContents // list of fields/edges in parsed order + TextArea } // String returns a short representation of this statement. @@ -629,6 +670,7 @@ func (obj *StmtRes) TypeCheck() ([]*interfaces.UnificationInvariant, error) { invar := &interfaces.UnificationInvariant{ Expr: obj.Name, + Node: obj, Expect: typExpr, // the name Actual: typ, } @@ -1389,6 +1431,7 @@ func (obj *StmtResField) TypeCheck(kind string) ([]*interfaces.UnificationInvari // XXX: Is this needed? invar := &interfaces.UnificationInvariant{ Expr: obj.Condition, + Node: obj, Expect: types.TypeBool, Actual: typ, } @@ -1429,6 +1472,7 @@ func (obj *StmtResField) TypeCheck(kind string) ([]*interfaces.UnificationInvari // regular scenario invar := &interfaces.UnificationInvariant{ Expr: obj.Value, + Node: obj, Expect: typExpr, Actual: typ, } @@ -1664,6 +1708,7 @@ func (obj *StmtResEdge) TypeCheck(kind string) ([]*interfaces.UnificationInvaria // XXX: Is this needed? invar := &interfaces.UnificationInvariant{ Expr: obj.Condition, + Node: obj, Expect: types.TypeBool, Actual: typ, } @@ -1925,6 +1970,7 @@ func (obj *StmtResMeta) TypeCheck(kind string) ([]*interfaces.UnificationInvaria // XXX: Is this needed? invar := &interfaces.UnificationInvariant{ Expr: obj.Condition, + Node: obj, Expect: types.TypeBool, Actual: typ, } @@ -2003,6 +2049,7 @@ func (obj *StmtResMeta) TypeCheck(kind string) ([]*interfaces.UnificationInvaria invar := &interfaces.UnificationInvariant{ Expr: obj.MetaExpr, + Node: obj, Expect: typExpr, Actual: typ, } @@ -2059,6 +2106,7 @@ type StmtEdge struct { // TODO: should notify be an Expr? Notify bool // specifies that this edge sends a notification as well + TextArea } // String returns a short representation of this statement. @@ -2524,6 +2572,7 @@ func (obj *StmtEdgeHalf) TypeCheck() ([]*interfaces.UnificationInvariant, error) invar := &interfaces.UnificationInvariant{ Expr: obj.Name, + Node: obj, Expect: typExpr, // the name Actual: typ, } @@ -2561,6 +2610,7 @@ type StmtIf struct { conditionPtr interfaces.Func // ptr for table lookup ThenBranch interfaces.Stmt // optional, but usually present ElseBranch interfaces.Stmt // optional + TextArea } // String returns a short representation of this statement. @@ -2642,11 +2692,11 @@ func (obj *StmtIf) Interpolate() (interfaces.Stmt, error) { return nil, errwrap.Wrapf(err, "could not interpolate ElseBranch") } } - return &StmtIf{ - Condition: condition, - ThenBranch: thenBranch, - ElseBranch: elseBranch, - }, nil + result := *obj + result.Condition = condition + result.ThenBranch = thenBranch + result.ElseBranch = elseBranch + return &result, nil } // Copy returns a light copy of this struct. Anything static will not be copied. @@ -2808,6 +2858,7 @@ func (obj *StmtIf) TypeCheck() ([]*interfaces.UnificationInvariant, error) { typExpr := types.TypeBool // default invar := &interfaces.UnificationInvariant{ Expr: obj.Condition, + Node: obj, Expect: typExpr, // the condition Actual: typ, } @@ -2923,6 +2974,7 @@ type StmtProg struct { importFiles []string // list of files seen during the SetScope import Body []interfaces.Stmt + TextArea } // String returns a short representation of this statement. @@ -4325,6 +4377,7 @@ type StmtFunc struct { Name string Func interfaces.Expr Type *types.Type + TextArea } // String returns a short representation of this statement. @@ -4484,6 +4537,7 @@ func (obj *StmtFunc) TypeCheck() ([]*interfaces.UnificationInvariant, error) { invar := &interfaces.UnificationInvariant{ Expr: obj.Func, + Node: obj, Expect: typExpr, // obj.Type Actual: typ, } @@ -4531,6 +4585,7 @@ type StmtClass struct { Name string Args []*interfaces.Arg Body interfaces.Stmt // probably a *StmtProg + TextArea } // String returns a short representation of this statement. @@ -4733,6 +4788,7 @@ type StmtInclude struct { Name string Args []interfaces.Expr Alias string + TextArea } // String returns a short representation of this statement. @@ -5048,6 +5104,7 @@ func (obj *StmtInclude) TypeCheck() ([]*interfaces.UnificationInvariant, error) if typExpr := obj.class.Args[i].Type; typExpr != nil { invar := &interfaces.UnificationInvariant{ Expr: x, + Node: obj, Expect: typExpr, // type of arg Actual: typ, } @@ -5098,6 +5155,7 @@ func (obj *StmtInclude) Output(table map[interfaces.Func]types.Value) (*interfac type StmtImport struct { Name string Alias string + TextArea } // String returns a short representation of this statement. @@ -5279,6 +5337,7 @@ type ExprBool struct { scope *interfaces.Scope // store for referencing this later V bool + TextArea } // String returns a short representation of this expression. @@ -5356,6 +5415,7 @@ func (obj *ExprBool) Infer() (*types.Type, []*interfaces.UnificationInvariant, e return types.TypeBool, []*interfaces.UnificationInvariant{ { Expr: obj, + Node: obj, Expect: types.TypeBool, Actual: types.TypeBool, }, @@ -5424,6 +5484,7 @@ type ExprStr struct { scope *interfaces.Scope // store for referencing this later V string // value of this string + TextArea } // String returns a short representation of this expression. @@ -5553,6 +5614,7 @@ func (obj *ExprStr) Infer() (*types.Type, []*interfaces.UnificationInvariant, er return types.TypeStr, []*interfaces.UnificationInvariant{ { Expr: obj, + Node: obj, Expect: types.TypeStr, Actual: types.TypeStr, }, @@ -5619,6 +5681,7 @@ type ExprInt struct { scope *interfaces.Scope // store for referencing this later V int64 + TextArea } // String returns a short representation of this expression. @@ -5696,6 +5759,7 @@ func (obj *ExprInt) Infer() (*types.Type, []*interfaces.UnificationInvariant, er return types.TypeInt, []*interfaces.UnificationInvariant{ { Expr: obj, + Node: obj, Expect: types.TypeInt, Actual: types.TypeInt, }, @@ -5762,6 +5826,7 @@ type ExprFloat struct { scope *interfaces.Scope // store for referencing this later V float64 + TextArea } // String returns a short representation of this expression. @@ -5841,6 +5906,7 @@ func (obj *ExprFloat) Infer() (*types.Type, []*interfaces.UnificationInvariant, return types.TypeFloat, []*interfaces.UnificationInvariant{ { Expr: obj, + Node: obj, Expect: types.TypeFloat, Actual: types.TypeFloat, }, @@ -5909,6 +5975,7 @@ type ExprList struct { //Elements []*ExprListElement Elements []interfaces.Expr + TextArea } // String returns a short representation of this expression. @@ -6133,6 +6200,7 @@ func (obj *ExprList) Infer() (*types.Type, []*interfaces.UnificationInvariant, e // This must be added even if redundant, so that we collect the obj ptr. invar := &interfaces.UnificationInvariant{ Expr: obj, + Node: obj, Expect: typExpr, // This is the type that we return. Actual: typType, } @@ -6262,6 +6330,7 @@ type ExprMap struct { typ *types.Type KVs []*ExprMapKV + TextArea } // String returns a short representation of this expression. @@ -6574,6 +6643,7 @@ func (obj *ExprMap) Infer() (*types.Type, []*interfaces.UnificationInvariant, er // This must be added even if redundant, so that we collect the obj ptr. invar := &interfaces.UnificationInvariant{ Expr: obj, + Node: obj, Expect: typExpr, // This is the type that we return. Actual: typType, } @@ -6747,6 +6817,7 @@ type ExprStruct struct { typ *types.Type Fields []*ExprStructField // the list (fields) are intentionally ordered! + TextArea } // String returns a short representation of this expression. @@ -6998,6 +7069,7 @@ func (obj *ExprStruct) Infer() (*types.Type, []*interfaces.UnificationInvariant, // This must be added even if redundant, so that we collect the obj ptr. invar := &interfaces.UnificationInvariant{ Expr: obj, + Node: obj, Expect: typExpr, // This is the type that we return. Actual: typType, } @@ -7179,6 +7251,7 @@ type ExprFunc struct { // XXX: is this necessary? //V func(interfaces.Txn, []pgraph.Vertex) (pgraph.Vertex, error) + TextArea } // String returns a short representation of this expression. @@ -7746,6 +7819,7 @@ func (obj *ExprFunc) Infer() (*types.Type, []*interfaces.UnificationInvariant, e // This must be added even if redundant, so that we collect the obj ptr. invar := &interfaces.UnificationInvariant{ Expr: obj, + Node: obj, Expect: typExpr, // This is the type that we return. Actual: typType, } @@ -7923,6 +7997,7 @@ type ExprCall struct { Args []interfaces.Expr // list of args in parsed order // Var specifies whether the function being called is a lambda in a var. Var bool + TextArea } // String returns a short representation of this expression. @@ -7978,19 +8053,11 @@ func (obj *ExprCall) Interpolate() (interfaces.Expr, error) { orig = obj.orig } - return &ExprCall{ - data: obj.data, - scope: obj.scope, - typ: obj.typ, - // XXX: Copy copies this, do we want to here as well? (or maybe - // we want to do it here, but not in Copy?) - expr: obj.expr, - orig: orig, - V: obj.V, - Name: obj.Name, - Args: args, - Var: obj.Var, - }, nil + result := *obj + result.orig = orig + result.Args = args + + return &result, nil } // Copy returns a light copy of this struct. Anything static will not be copied. @@ -8444,6 +8511,7 @@ func (obj *ExprCall) Infer() (*types.Type, []*interfaces.UnificationInvariant, e // This must be added even if redundant, so that we collect the obj ptr. invar := &interfaces.UnificationInvariant{ Expr: obj, + Node: obj, Expect: typExpr, // This is the type that we return. Actual: typType, } @@ -8514,7 +8582,8 @@ func (obj *ExprCall) Infer() (*types.Type, []*interfaces.UnificationInvariant, e invar := &interfaces.UnificationInvariant{ Expr: obj.expr, // this should NOT be obj - Expect: typFunc, // TODO: are these two reversed here? + Node: obj, + Expect: typFunc, // TODO: are these two reversed here? Actual: typFn, } invariants = append(invariants, invar) @@ -8654,6 +8723,7 @@ type ExprVar struct { typ *types.Type Name string // name of the variable + TextArea } // String returns a short representation of this expression. @@ -8833,6 +8903,7 @@ func (obj *ExprVar) Infer() (*types.Type, []*interfaces.UnificationInvariant, er // This adds the obj ptr, so it's seen as an expr that we need to solve. invar := &interfaces.UnificationInvariant{ Expr: obj, + Node: obj, Expect: typ, Actual: typ, } @@ -8917,6 +8988,7 @@ type ExprParam struct { typ *types.Type Name string // name of the parameter + TextArea } // String returns a short representation of this expression. @@ -9060,6 +9132,7 @@ func (obj *ExprParam) Infer() (*types.Type, []*interfaces.UnificationInvariant, // This adds the obj ptr, so it's seen as an expr that we need to solve. invar := &interfaces.UnificationInvariant{ Expr: obj, + Node: obj, Expect: typ, Actual: typ, } @@ -9367,6 +9440,7 @@ func (obj *ExprTopLevel) Infer() (*types.Type, []*interfaces.UnificationInvarian // This adds the obj ptr, so it's seen as an expr that we need to solve. invar := &interfaces.UnificationInvariant{ Expr: obj, + Node: obj, Expect: typ, Actual: typ, } @@ -9561,6 +9635,7 @@ func (obj *ExprSingleton) Infer() (*types.Type, []*interfaces.UnificationInvaria // to solve. invar := &interfaces.UnificationInvariant{ Expr: obj, + Node: obj, Expect: typ, Actual: typ, } @@ -9629,6 +9704,7 @@ type ExprIf struct { Condition interfaces.Expr ThenBranch interfaces.Expr // could be an ExprBranch ElseBranch interfaces.Expr // could be an ExprBranch + TextArea } // String returns a short representation of this expression. @@ -9928,6 +10004,7 @@ func (obj *ExprIf) Infer() (*types.Type, []*interfaces.UnificationInvariant, err // This must be added even if redundant, so that we collect the obj ptr. invar := &interfaces.UnificationInvariant{ Expr: obj, + Node: obj, Expect: typExpr, // This is the type that we return. Actual: typType, } diff --git a/lang/ast/util.go b/lang/ast/util.go index 4c4e71237a..c951c2402d 100644 --- a/lang/ast/util.go +++ b/lang/ast/util.go @@ -393,3 +393,23 @@ func lambdaScopeFeedback(scope *interfaces.Scope, logf func(format string, v ... logf("$%s(...)", name) } } + +func AreaParentOf(needle, haystack interfaces.Node) LocalNode { + var LastArea LocalNode + + err := haystack.Apply(func(n interfaces.Node) error { + ln, ok := n.(LocalNode) + if ok { + LastArea = ln + } + if n == needle { + return fmt.Errorf("found") + } + return nil + }) + + if err != nil && err.Error() == "found" { + return LastArea + } + return haystack.(LocalNode) +} diff --git a/lang/gapi/gapi.go b/lang/gapi/gapi.go index 1013f140fc..8cb62ae739 100644 --- a/lang/gapi/gapi.go +++ b/lang/gapi/gapi.go @@ -318,6 +318,10 @@ func (obj *GAPI) Cli(info *gapi.Info) (*gapi.Deploy, error) { if args.OnlyUnify { logf("type unification failed after %s", formatted) } + cause := unifyErr.(*interfaces.UnificationInvariant).Node + parent := ast.AreaParentOf(cause, iast) + line, col := parent.GetPosition() + logf("possible type issue found at line %d column %d", line, col) return nil, errwrap.Wrapf(unifyErr, "could not unify types") } diff --git a/lang/interfaces/unification.go b/lang/interfaces/unification.go index 59bd854a13..1484314cdd 100644 --- a/lang/interfaces/unification.go +++ b/lang/interfaces/unification.go @@ -43,11 +43,22 @@ type UnificationInvariant struct { // formerly the SamInvariant // our error messages. Expr Expr + // Node is the AST node holding that expression. This improves our error + // messages more. + Node Node + // Expect is one of the two types to unify. Expect *types.Type // Actual is one of the two types to unify. Actual *types.Type + + // An error string to pass along with this + Err string +} + +func (obj *UnificationInvariant) Error() string { + return obj.Err } // GenericCheck is the generic implementation of the Check Expr interface call. diff --git a/lang/parser/parser.y b/lang/parser/parser.y index f497b7997a..442828bc91 100644 --- a/lang/parser/parser.y +++ b/lang/parser/parser.y @@ -149,14 +149,13 @@ top: prog: /* end of list */ { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtProg{ Body: []interfaces.Stmt{}, } + //locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } | prog stmt { - posLast(yylex, yyDollar) // our pos // TODO: should we just skip comments for now? //if _, ok := $2.stmt.(*ast.StmtComment); !ok { //} @@ -166,49 +165,50 @@ prog: $$.stmt = &ast.StmtProg{ Body: stmts, } + //locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } } ; stmt: COMMENT { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtComment{ Value: $1.str, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } | bind { - posLast(yylex, yyDollar) // our pos $$.stmt = $1.stmt + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } | panic { - posLast(yylex, yyDollar) // our pos $$.stmt = $1.stmt + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } | resource { - posLast(yylex, yyDollar) // our pos $$.stmt = $1.stmt + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } | edge { - posLast(yylex, yyDollar) // our pos $$.stmt = $1.stmt + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } | IF expr OPEN_CURLY prog CLOSE_CURLY { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtIf{ Condition: $2.expr, ThenBranch: $4.stmt, //ElseBranch: nil, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } | IF expr OPEN_CURLY prog CLOSE_CURLY ELSE OPEN_CURLY prog CLOSE_CURLY { - posLast(yylex, yyDollar) // our pos + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) $$.stmt = &ast.StmtIf{ Condition: $2.expr, ThenBranch: $4.stmt, @@ -221,7 +221,7 @@ stmt: // `func name(, ) { }` | FUNC_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY expr CLOSE_CURLY { - posLast(yylex, yyDollar) // our pos + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) $$.stmt = &ast.StmtFunc{ Name: $2.str, Func: &ast.ExprFunc{ @@ -234,7 +234,6 @@ stmt: // `func name(...) { }` | FUNC_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN type OPEN_CURLY expr CLOSE_CURLY { - posLast(yylex, yyDollar) // our pos fn := &ast.ExprFunc{ Args: $4.args, Return: $6.typ, // return type is known @@ -271,191 +270,192 @@ stmt: Func: fn, Type: typ, // sam says add the type here instead... } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } // `class name { }` | CLASS_IDENTIFIER colon_identifier OPEN_CURLY prog CLOSE_CURLY { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtClass{ Name: $2.str, Args: nil, Body: $4.stmt, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } // `class name() { }` // `class name(, ) { }` | CLASS_IDENTIFIER colon_identifier OPEN_PAREN args CLOSE_PAREN OPEN_CURLY prog CLOSE_CURLY { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtClass{ Name: $2.str, Args: $4.args, Body: $7.stmt, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } // `include name` | INCLUDE_IDENTIFIER dotted_identifier { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtInclude{ Name: $2.str, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } // `include name(...)` | INCLUDE_IDENTIFIER dotted_identifier OPEN_PAREN call_args CLOSE_PAREN { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtInclude{ Name: $2.str, Args: $4.exprs, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } // `include name as foo` // TODO: should we support: `include name as *` | INCLUDE_IDENTIFIER dotted_identifier AS_IDENTIFIER IDENTIFIER { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtInclude{ Name: $2.str, Alias: $4.str, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } // `include name(...) as foo` // TODO: should we support: `include name(...) as *` | INCLUDE_IDENTIFIER dotted_identifier OPEN_PAREN call_args CLOSE_PAREN AS_IDENTIFIER IDENTIFIER { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtInclude{ Name: $2.str, Args: $4.exprs, Alias: $7.str, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } // `import "name"` | IMPORT_IDENTIFIER STRING { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtImport{ Name: $2.str, //Alias: "", } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } // `import "name" as alias` | IMPORT_IDENTIFIER STRING AS_IDENTIFIER IDENTIFIER { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtImport{ Name: $2.str, Alias: $4.str, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } // `import "name" as *` | IMPORT_IDENTIFIER STRING AS_IDENTIFIER MULTIPLY { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtImport{ Name: $2.str, Alias: $4.str, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } /* // resource bind | rbind { - posLast(yylex, yyDollar) // our pos $$.stmt = $1.stmt + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } */ ; expr: BOOL { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprBool{ V: $1.bool, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | STRING { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprStr{ V: $1.str, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | INTEGER { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprInt{ V: $1.int, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | FLOAT { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprFloat{ V: $1.float, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | list { - posLast(yylex, yyDollar) // our pos // TODO: list could be squashed in here directly... $$.expr = $1.expr + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | map { - posLast(yylex, yyDollar) // our pos // TODO: map could be squashed in here directly... $$.expr = $1.expr + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | struct { - posLast(yylex, yyDollar) // our pos // TODO: struct could be squashed in here directly... $$.expr = $1.expr + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | call { - posLast(yylex, yyDollar) // our pos // TODO: call could be squashed in here directly... $$.expr = $1.expr + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | var { - posLast(yylex, yyDollar) // our pos // TODO: var could be squashed in here directly... $$.expr = $1.expr + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | func { - posLast(yylex, yyDollar) // our pos // TODO: var could be squashed in here directly... $$.expr = $1.expr + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | IF expr OPEN_CURLY expr CLOSE_CURLY ELSE OPEN_CURLY expr CLOSE_CURLY { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprIf{ Condition: $2.expr, ThenBranch: $4.expr, ElseBranch: $8.expr, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } // parenthesis wrap an expression for precedence | OPEN_PAREN expr CLOSE_PAREN { - posLast(yylex, yyDollar) // our pos $$.expr = $2.expr + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } ; list: // `[42, 0, -13]` OPEN_BRACK list_elements CLOSE_BRACK { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprList{ Elements: $2.exprs, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } ; list_elements: @@ -473,18 +473,18 @@ list_elements: list_element: expr COMMA { - posLast(yylex, yyDollar) // our pos $$.expr = $1.expr + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } ; map: // `{"hello" => "there", "world" => "big",}` OPEN_CURLY map_kvs CLOSE_CURLY { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprMap{ KVs: $2.mapKVs, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } ; map_kvs: @@ -513,10 +513,10 @@ struct: // `struct{answer => 0, truth => false, hello => "world",}` STRUCT_IDENTIFIER OPEN_CURLY struct_fields CLOSE_CURLY { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprStruct{ Fields: $3.structFields, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } ; struct_fields: @@ -546,18 +546,17 @@ call: // iter.map(...) dotted_identifier OPEN_PAREN call_args CLOSE_PAREN { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: $1.str, Args: $3.exprs, //Var: false, // default } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } // calling a function that's stored in a variable (a lambda) // `$foo(4, "hey")` # call function value | dotted_var_identifier OPEN_PAREN call_args CLOSE_PAREN { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: $1.str, Args: $3.exprs, @@ -565,10 +564,10 @@ call: // prefix to the Name, but I felt this was more elegant. Var: true, // lambda } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr PLUS expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -579,10 +578,10 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr MINUS expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -593,10 +592,10 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr MULTIPLY expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -607,10 +606,10 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr DIVIDE expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -621,10 +620,10 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr EQ expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -635,10 +634,10 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr NEQ expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -649,10 +648,10 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr LT expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -663,10 +662,10 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr GT expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -677,10 +676,10 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr LTE expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -691,10 +690,10 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr GTE expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -705,10 +704,10 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr AND expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -719,10 +718,10 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr OR expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -733,10 +732,10 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | NOT expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: operators.OperatorFuncName, Args: []interfaces.Expr{ @@ -746,13 +745,13 @@ call: $2.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } // lookup an index in a list or a key in a map // lookup($foo, $key) // `$foo[$key]` // no default specifier | expr OPEN_BRACK expr CLOSE_BRACK { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: funcs.LookupFuncName, Args: []interfaces.Expr{ @@ -761,13 +760,13 @@ call: //$6.expr, // the default }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } // lookup an index in a list or a key in a map with a default // lookup_default($foo, $key, $default) // `$foo[$key] || "default"` | expr OPEN_BRACK expr CLOSE_BRACK DEFAULT expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: funcs.LookupDefaultFuncName, Args: []interfaces.Expr{ @@ -776,13 +775,13 @@ call: $6.expr, // the default }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } // lookup a field in a struct // _struct_lookup($foo, "field") // $foo->field | expr ARROW IDENTIFIER { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: funcs.StructLookupFuncName, Args: []interfaces.Expr{ @@ -793,13 +792,13 @@ call: //$5.expr, // the default }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } // lookup a field in a struct with a default // _struct_lookup_optional($foo, "field", "default") // $foo->field || "default" | expr ARROW IDENTIFIER DEFAULT expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: funcs.StructLookupOptionalFuncName, Args: []interfaces.Expr{ @@ -810,10 +809,10 @@ call: $5.expr, // the default }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } | expr IN expr { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprCall{ Name: funcs.ContainsFuncName, Args: []interfaces.Expr{ @@ -821,6 +820,7 @@ call: $3.expr, }, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } ; // list order gets us the position of the arg, but named params would work too! @@ -846,10 +846,10 @@ call_args: var: dotted_var_identifier { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprVar{ Name: $1.str, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } ; func: @@ -859,22 +859,22 @@ func: // `func(, ) { }` FUNC_IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY expr CLOSE_CURLY { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprFunc{ Args: $3.args, //Return: nil, Body: $6.expr, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } // `func(...) { }` | FUNC_IDENTIFIER OPEN_PAREN args CLOSE_PAREN type OPEN_CURLY expr CLOSE_CURLY { - posLast(yylex, yyDollar) // our pos $$.expr = &ast.ExprFunc{ Args: $3.args, Return: $5.typ, // return type is known Body: $7.expr, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) isFullyTyped := $5.typ != nil // true if set m := make(map[string]*types.Type) ord := []string{} @@ -939,17 +939,16 @@ bind: // `$s = "hey"` var_identifier EQUALS expr { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtBind{ Ident: $1.str, Value: $3.expr, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } // `$x bool = true` // `$x int = if true { 42 } else { 13 }` | var_identifier type EQUALS expr { - posLast(yylex, yyDollar) // our pos var expr interfaces.Expr = $4.expr // XXX: We still need to do this for now it seems... if err := expr.SetType($2.typ); err != nil { @@ -961,6 +960,7 @@ bind: Value: expr, Type: $2.typ, // sam says add the type here instead... } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } ; panic: @@ -971,7 +971,6 @@ panic: //} PANIC_IDENTIFIER OPEN_PAREN call_args CLOSE_PAREN { - posLast(yylex, yyDollar) // our pos call := &ast.ExprCall{ Name: $1.str, // the function name Args: $3.exprs, @@ -990,6 +989,7 @@ panic: ThenBranch: res, //ElseBranch: nil, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } ; /* TODO: do we want to include this? @@ -997,7 +997,6 @@ panic: rbind: var_identifier EQUALS resource { - posLast(yylex, yyDollar) // our pos // XXX: this kind of bind is different than the others, because // it can only really be used for send->recv stuff, eg: // foo.SomeString -> bar.SomeOtherString @@ -1005,6 +1004,7 @@ rbind: Ident: $1.str, Value: $3.stmt, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.expr) } ; */ @@ -1012,12 +1012,12 @@ resource: // `file "/tmp/hello" { ... }` or `aws:ec2 "/tmp/hello" { ... }` colon_identifier expr OPEN_CURLY resource_body CLOSE_CURLY { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtRes{ Kind: $1.str, Name: $2.expr, Contents: $4.resContents, } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } ; resource_body: @@ -1180,16 +1180,15 @@ edge: // Test["t1"] -> Test["t2"] -> Test["t3"] # chain or pair edge_half_list { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtEdge{ EdgeHalfList: $1.edgeHalfList, //Notify: false, // unused here } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } // Test["t1"].foo_send -> Test["t2"].blah_recv # send/recv | edge_half_sendrecv ARROW edge_half_sendrecv { - posLast(yylex, yyDollar) // our pos $$.stmt = &ast.StmtEdge{ EdgeHalfList: []*ast.StmtEdgeHalf{ $1.edgeHalf, @@ -1197,6 +1196,7 @@ edge: }, //Notify: false, // unused here, it is implied (i think) } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } ; edge_half_list: @@ -1486,10 +1486,22 @@ func cast(y yyLexer) *lexParseAST { return x.(*lexParseAST) } -// postLast pulls out the "last token" and does a pos with that. This is a hack! +// The posLast variant that specifies a node will store the coordinates in the +// node. +func locate(y yyLexer, first yySymType, last yySymType, node interfaces.Node) { + posLast(y, []yySymType{last}) // TODO: is it really useful to store this in the Lexer? the values are erratic and likely unhelpful + if ln, ok := node.(ast.LocalNode) ; !ok { + return + // only run Locate on nodes that look like they have not received locations yet + // otherwise the parser will come back and overwrite with faux end positions + } else if row, col := ln.GetPosition() ; row == 0 && col == 0 { + ln.Locate(first.row, first.col, last.row, last.col) + } +} + +// postLast runs pos on the last token of the current stmt/expr. func posLast(y yyLexer, dollars []yySymType) { - // pick the last token in the set matched by the parser - pos(y, dollars[len(dollars)-1]) // our pos + pos(y, dollars[len(dollars)-1]) } // cast is used to pull out the parser run-specific struct we store our AST in. diff --git a/lang/unification/fastsolver/fastsolver.go b/lang/unification/fastsolver/fastsolver.go index a2ebd0b8f9..958d5493b1 100644 --- a/lang/unification/fastsolver/fastsolver.go +++ b/lang/unification/fastsolver/fastsolver.go @@ -135,7 +135,8 @@ func (obj *FastInvariantSolver) Solve(ctx context.Context, data *unification.Dat // Storing the Expr with this invariant is so that we // can generate this more helpful error message here. // TODO: Improve this error message! - return nil, errwrap.Wrapf(err, "unify error with: %s", x.Expr) + x.Err = errwrap.Wrapf(err, "unify error with: %s", x.Expr).Error() + return nil, x } if obj.Debug { e1, e2 := unificationUtil.Extract(x.Expect), unificationUtil.Extract(x.Actual)