Location, error formatting and stack trace improvements (#59)

* Location, error formatting and stack trace improvements

* Static context for AST nodes
* Thunks no longer need `name`
* Prototype for showing snippets in error messages (old format still
available)
* Use ast.Function to represent methods and local function sugar.
* Change tests so that the error output is pretty
This commit is contained in:
Stanisław Barzowski 2017-10-03 14:27:44 -04:00 committed by Dave Cunningham
parent a1b8248e84
commit c3459153df
145 changed files with 4663 additions and 175 deletions

View File

@ -30,10 +30,14 @@ type Identifiers []Identifier
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
type Context *string
type Node interface { type Node interface {
Context() Context
Loc() *LocationRange Loc() *LocationRange
FreeVariables() Identifiers FreeVariables() Identifiers
SetFreeVariables(Identifiers) SetFreeVariables(Identifiers)
SetContext(Context)
} }
type Nodes []Node type Nodes []Node
@ -41,6 +45,7 @@ type Nodes []Node
type NodeBase struct { type NodeBase struct {
loc LocationRange loc LocationRange
context Context
freeVariables Identifiers freeVariables Identifiers
} }
@ -70,6 +75,14 @@ func (n *NodeBase) SetFreeVariables(idents Identifiers) {
n.freeVariables = idents n.freeVariables = idents
} }
func (n *NodeBase) Context() Context {
return n.context
}
func (n *NodeBase) SetContext(context Context) {
n.context = context
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
type IfSpec struct { type IfSpec struct {
@ -358,9 +371,7 @@ type Slice struct {
type LocalBind struct { type LocalBind struct {
Variable Identifier Variable Identifier
Body Node Body Node
FunctionSugar bool Fun *Function
Params *Parameters // if functionSugar is true
TrailingComma bool
} }
type LocalBinds []LocalBind type LocalBinds []LocalBind
@ -439,6 +450,7 @@ type ObjectField struct {
Hide ObjectFieldHide // (ignore if kind != astObjectField*) Hide ObjectFieldHide // (ignore if kind != astObjectField*)
SuperSugar bool // +: (ignore if kind != astObjectField*) SuperSugar bool // +: (ignore if kind != astObjectField*)
MethodSugar bool // f(x, y, z): ... (ignore if kind == astObjectAssert) MethodSugar bool // f(x, y, z): ... (ignore if kind == astObjectAssert)
Method *Function
Expr1 Node // Not in scope of the object Expr1 Node // Not in scope of the object
Id *Identifier Id *Identifier
Params *Parameters // If methodSugar == true then holds the params. Params *Parameters // If methodSugar == true then holds the params.
@ -448,12 +460,8 @@ type ObjectField struct {
// TODO(jbeda): Add the remaining constructor helpers here // TODO(jbeda): Add the remaining constructor helpers here
func ObjectFieldLocal(methodSugar bool, id *Identifier, params *Parameters, trailingComma bool, body Node) ObjectField {
return ObjectField{ObjectLocal, ObjectFieldVisible, false, methodSugar, nil, id, params, trailingComma, body, nil}
}
func ObjectFieldLocalNoMethod(id *Identifier, body Node) ObjectField { func ObjectFieldLocalNoMethod(id *Identifier, body Node) ObjectField {
return ObjectField{ObjectLocal, ObjectFieldVisible, false, false, nil, id, nil, false, body, nil} return ObjectField{ObjectLocal, ObjectFieldVisible, false, false, nil, nil, id, nil, false, body, nil}
} }
type ObjectFields []ObjectField type ObjectFields []ObjectField

View File

@ -16,7 +16,14 @@ limitations under the License.
package ast package ast
import "fmt" import (
"bytes"
"fmt"
)
type Source struct {
lines []string
}
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// Location // Location
@ -24,6 +31,7 @@ import "fmt"
// Location represents a single location in an (unspecified) file. // Location represents a single location in an (unspecified) file.
type Location struct { type Location struct {
Line int Line int
// Column is a byte offset from the beginning of the line
Column int Column int
} }
@ -36,6 +44,13 @@ func (l *Location) String() string {
return fmt.Sprintf("%v:%v", l.Line, l.Column) return fmt.Sprintf("%v:%v", l.Line, l.Column)
} }
func locationBefore(a Location, b Location) bool {
if a.Line != b.Line {
return a.Line < b.Line
}
return a.Column < b.Column
}
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// LocationRange // LocationRange
@ -43,7 +58,15 @@ func (l *Location) String() string {
type LocationRange struct { type LocationRange struct {
FileName string FileName string
Begin Location Begin Location
End Location End Location // TODO(sbarzowski) inclusive? exclusive? a gap?
file *Source
}
func LocationRangeBetween(a, b *LocationRange) LocationRange {
if a.file != b.file {
panic("Cannot create a LocationRange between different files")
}
return MakeLocationRange(a.FileName, a.file, a.Begin, b.End)
} }
// IsSet returns if this LocationRange has been set. // IsSet returns if this LocationRange has been set.
@ -70,11 +93,101 @@ func (lr *LocationRange) String() string {
return fmt.Sprintf("%s(%v)-(%v)", filePrefix, lr.Begin.String(), lr.End.String()) return fmt.Sprintf("%s(%v)-(%v)", filePrefix, lr.Begin.String(), lr.End.String())
} }
func (l *LocationRange) WithCode() bool {
return l.Begin.Line != 0
}
// This is useful for special locations, e.g. manifestation entry point. // This is useful for special locations, e.g. manifestation entry point.
func MakeLocationRangeMessage(msg string) LocationRange { func MakeLocationRangeMessage(msg string) LocationRange {
return LocationRange{FileName: msg} return LocationRange{FileName: msg}
} }
func MakeLocationRange(fn string, begin Location, end Location) LocationRange { func MakeLocationRange(fn string, fc *Source, begin Location, end Location) LocationRange {
return LocationRange{FileName: fn, Begin: begin, End: end} return LocationRange{FileName: fn, file: fc, Begin: begin, End: end}
}
type SourceProvider struct {
}
func (sp *SourceProvider) GetSnippet(loc LocationRange) string {
var result bytes.Buffer
if loc.Begin.Line == 0 {
return ""
}
for i := loc.Begin.Line; i <= loc.End.Line; i++ {
inLineRange := trimToLine(loc, i)
for j := inLineRange.Begin.Column; j < inLineRange.End.Column; j++ {
result.WriteByte(loc.file.lines[i-1][j-1])
}
if i != loc.End.Line {
result.WriteByte('\n')
}
}
return result.String()
}
func BuildSource(s string) *Source {
var result []string
var lineBuf bytes.Buffer
for _, runeValue := range s {
lineBuf.WriteRune(runeValue)
if runeValue == '\n' {
result = append(result, lineBuf.String())
lineBuf.Reset()
}
}
rest := lineBuf.String()
// Stuff after last end-of-line (EOF or some more code)
result = append(result, rest+"\n")
return &Source{result}
}
func trimToLine(loc LocationRange, line int) LocationRange {
if loc.Begin.Line > line {
panic("invalid")
}
if loc.Begin.Line != line {
loc.Begin.Column = 1
}
loc.Begin.Line = line
if loc.End.Line < line {
panic("invalid")
}
if loc.End.Line != line {
loc.End.Column = len(loc.file.lines[line-1])
}
loc.End.Line = line
return loc
}
// lineBeginning returns a part of the line directly before LocationRange
// for example:
// local x = foo()
// ^^^^^ <- LocationRange loc
// then
// local x = foo()
// ^^^^^^^^^^ <- lineBeginning(loc)
func LineBeginning(loc *LocationRange) LocationRange {
return LocationRange{
Begin: Location{Line: loc.Begin.Line, Column: 1},
End: loc.Begin,
FileName: loc.FileName,
file: loc.file,
}
}
// lineEnding returns a part of the line directly after LocationRange
// for example:
// local x = foo() + test
// ^^^^^ <- LocationRange loc
// then
// local x = foo() + test
// ^^^^^^^ <- lineEnding(loc)
func LineEnding(loc *LocationRange) LocationRange {
return LocationRange{
Begin: loc.End,
End: Location{Line: loc.End.Line, Column: len(loc.file.lines[loc.End.Line-1])},
FileName: loc.FileName,
file: loc.file,
}
} }

View File

@ -608,7 +608,7 @@ type UnaryBuiltin struct {
func getBuiltinEvaluator(e *evaluator, name ast.Identifier) *evaluator { func getBuiltinEvaluator(e *evaluator, name ast.Identifier) *evaluator {
loc := ast.MakeLocationRangeMessage("<builtin>") loc := ast.MakeLocationRangeMessage("<builtin>")
context := TraceContext{Name: "builtin function <" + string(name) + ">"} context := "builtin function <" + string(name) + ">"
trace := TraceElement{loc: &loc, context: &context} trace := TraceElement{loc: &loc, context: &context}
return &evaluator{i: e.i, trace: &trace} return &evaluator{i: e.i, trace: &trace}
} }

View File

@ -128,22 +128,14 @@ func desugarFields(location ast.LocationRange, fields *ast.ObjectFields, objLeve
field.Expr2 = assertion field.Expr2 = assertion
} }
// Remove methods
for i := range *fields { for i := range *fields {
field := &((*fields)[i]) field := &((*fields)[i])
if !field.MethodSugar { if field.Method == nil {
continue continue
} }
origBody := field.Expr2 field.Expr2 = field.Method
function := &ast.Function{ field.Method = nil
// TODO(sbarzowski) better location // Body of the function already desugared through expr2
NodeBase: ast.NewNodeBaseLoc(*origBody.Loc()),
Parameters: *field.Params,
Body: origBody,
}
field.MethodSugar = false
field.Params = nil
field.Expr2 = function
} }
// Remove object-level locals // Remove object-level locals
@ -483,19 +475,11 @@ func desugar(astPtr *ast.Node, objLevel int) (err error) {
case *ast.Local: case *ast.Local:
for i := range node.Binds { for i := range node.Binds {
if node.Binds[i].FunctionSugar { if node.Binds[i].Fun != nil {
origBody := node.Binds[i].Body
function := &ast.Function{
// TODO(sbarzowski) better location
NodeBase: ast.NewNodeBaseLoc(*origBody.Loc()),
Parameters: *node.Binds[i].Params,
Body: origBody,
}
node.Binds[i] = ast.LocalBind{ node.Binds[i] = ast.LocalBind{
Variable: node.Binds[i].Variable, Variable: node.Binds[i].Variable,
Body: function, Body: node.Binds[i].Fun,
FunctionSugar: false, Fun: nil,
Params: nil,
} }
} }
err = desugar(&node.Binds[i].Body, objLevel) err = desugar(&node.Binds[i].Body, objLevel)

View File

@ -19,6 +19,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/fatih/color"
"github.com/google/go-jsonnet/ast" "github.com/google/go-jsonnet/ast"
"github.com/google/go-jsonnet/parser" "github.com/google/go-jsonnet/parser"
) )
@ -27,10 +28,13 @@ type ErrorFormatter struct {
// TODO(sbarzowski) use this // TODO(sbarzowski) use this
// MaxStackTraceSize is the maximum length of stack trace before cropping // MaxStackTraceSize is the maximum length of stack trace before cropping
MaxStackTraceSize int MaxStackTraceSize int
// TODO(sbarzowski) use these
// Examples of current state of the art.
// http://elm-lang.org/blog/compiler-errors-for-humans
// https://clang.llvm.org/diagnostics.html
pretty bool pretty bool
colorful bool colorful bool
SP SourceProvider SP *ast.SourceProvider
} }
func (ef *ErrorFormatter) format(err error) string { func (ef *ErrorFormatter) format(err error) string {
@ -46,12 +50,13 @@ func (ef *ErrorFormatter) format(err error) string {
func (ef *ErrorFormatter) formatRuntime(err *RuntimeError) string { func (ef *ErrorFormatter) formatRuntime(err *RuntimeError) string {
return err.Error() + "\n" + ef.buildStackTrace(err.StackTrace) return err.Error() + "\n" + ef.buildStackTrace(err.StackTrace)
// TODO(sbarzowski) pretty stuff
} }
func (ef *ErrorFormatter) formatStatic(err *parser.StaticError) string { func (ef *ErrorFormatter) formatStatic(err *parser.StaticError) string {
return err.Error() + "\n" var buf bytes.Buffer
// TODO(sbarzowski) pretty stuff buf.WriteString(err.Error() + "\n")
ef.showCode(&buf, err.Loc)
return buf.String()
} }
const bugURL = "https://github.com/google/go-jsonnet/issues" const bugURL = "https://github.com/google/go-jsonnet/issues"
@ -61,19 +66,42 @@ func (ef *ErrorFormatter) formatInternal(err error) string {
"Please report a bug here: " + bugURL + "\n" "Please report a bug here: " + bugURL + "\n"
} }
func (ef *ErrorFormatter) showCode(buf *bytes.Buffer, loc ast.LocationRange) {
errFprintf := fmt.Fprintf
if ef.colorful {
errFprintf = color.New(color.FgRed).Fprintf
}
if loc.WithCode() {
// TODO(sbarzowski) include line numbers
// TODO(sbarzowski) underline errors instead of depending only on color
fmt.Fprintf(buf, "\n")
beginning := ast.LineBeginning(&loc)
ending := ast.LineEnding(&loc)
fmt.Fprintf(buf, "%v", ef.SP.GetSnippet(beginning))
errFprintf(buf, "%v", ef.SP.GetSnippet(loc))
fmt.Fprintf(buf, "%v", ef.SP.GetSnippet(ending))
buf.WriteByte('\n')
}
fmt.Fprintf(buf, "\n")
}
func (ef *ErrorFormatter) buildStackTrace(frames []TraceFrame) string { func (ef *ErrorFormatter) buildStackTrace(frames []TraceFrame) string {
// https://github.com/google/jsonnet/blob/master/core/libjsonnet.cpp#L594 // https://github.com/google/jsonnet/blob/master/core/libjsonnet.cpp#L594
var buf bytes.Buffer var buf bytes.Buffer
for _, f := range frames { for i := len(frames) - 1; i >= 0; i-- {
f := frames[i]
// TODO(sbarzowski) make pretty format more readable (it's already useful)
if ef.pretty {
fmt.Fprintf(&buf, "-------------------------------------------------\n")
}
// TODO(sbarzowski) tabs are probably a bad idea
fmt.Fprintf(&buf, "\t%v\t%v\n", &f.Loc, f.Name) fmt.Fprintf(&buf, "\t%v\t%v\n", &f.Loc, f.Name)
if ef.pretty {
ef.showCode(&buf, f.Loc)
}
// TODO(sbarzowski) handle max stack trace size // TODO(sbarzowski) handle max stack trace size
// TODO(sbarzowski) I think the order of frames is reversed // TODO(sbarzowski) I think the order of frames is reversed
} }
return buf.String() return buf.String()
} }
type SourceProvider interface {
// TODO(sbarzowski) problem: locationRange.FileName may not necessarily
// uniquely identify a file. But this is the interface we want to have here.
getCode(ast.LocationRange) string
}

View File

@ -162,11 +162,11 @@ func (e *evaluator) evaluateObject(pv potentialValue) (valueObject, error) {
} }
func (e *evaluator) evalInCurrentContext(a ast.Node) (value, error) { func (e *evaluator) evalInCurrentContext(a ast.Node) (value, error) {
return e.i.evaluate(a, e.trace.context) return e.i.evaluate(a)
} }
func (e *evaluator) evalInCleanEnv(newContext *TraceContext, env *environment, ast ast.Node) (value, error) { func (e *evaluator) evalInCleanEnv(env *environment, ast ast.Node) (value, error) {
return e.i.EvalInCleanEnv(e.trace, newContext, env, ast) return e.i.EvalInCleanEnv(e.trace, env, ast)
} }
func (e *evaluator) lookUpVar(ident ast.Identifier) potentialValue { func (e *evaluator) lookUpVar(ident ast.Identifier) potentialValue {

View File

@ -86,7 +86,7 @@ func codeToPV(e *evaluator, filename string, code string) potentialValue {
// The same thinking applies to external variables. // The same thinking applies to external variables.
return makeErrorThunk(err) return makeErrorThunk(err)
} }
return makeThunk("_", e.i.initialEnv, node) return makeThunk(e.i.initialEnv, node)
} }
func (cache *ImportCache) ImportCode(codeDir, importedPath string, e *evaluator) (value, error) { func (cache *ImportCache) ImportCode(codeDir, importedPath string, e *evaluator) (value, error) {

View File

@ -71,7 +71,7 @@ type callFrame struct {
// This makes callFrame a misnomer as it is technically not always a call... // This makes callFrame a misnomer as it is technically not always a call...
isCall bool isCall bool
// Tracing information about the place where (TODO) // Tracing information about the place where it was called from.
trace *TraceElement trace *TraceElement
/** Reuse this stack frame for the purpose of tail call optimization. */ /** Reuse this stack frame for the purpose of tail call optimization. */
@ -242,11 +242,11 @@ func (i *interpreter) getCurrentEnv(ast ast.Node) environment {
) )
} }
func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error) { func (i *interpreter) evaluate(a ast.Node) (value, error) {
e := &evaluator{ e := &evaluator{
trace: &TraceElement{ trace: &TraceElement{
loc: a.Loc(), loc: a.Loc(),
context: context, context: a.Context(),
}, },
i: i, i: i,
} }
@ -257,7 +257,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
var elements []potentialValue var elements []potentialValue
for _, el := range ast.Elements { for _, el := range ast.Elements {
env := makeEnvironment(i.capture(el.FreeVariables()), sb) env := makeEnvironment(i.capture(el.FreeVariables()), sb)
elThunk := makeThunk("array_element", env, el) elThunk := makeThunk(env, el)
elements = append(elements, elThunk) elements = append(elements, elThunk)
} }
return makeValueArray(elements), nil return makeValueArray(elements), nil
@ -269,8 +269,8 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
// TODO(sbarzowski) it may make sense not to show a line in stack trace for operators // TODO(sbarzowski) it may make sense not to show a line in stack trace for operators
// at all in many cases. 1 + 2 + 3 + 4 + error "x" will show 5 lines // at all in many cases. 1 + 2 + 3 + 4 + error "x" will show 5 lines
// of stack trace now, and it's not that nice. // of stack trace now, and it's not that nice.
left := makeThunk("x", env, ast.Left) left := makeThunk(env, ast.Left)
right := makeThunk("y", env, ast.Right) right := makeThunk(env, ast.Right)
builtin := bopBuiltins[ast.Op] builtin := bopBuiltins[ast.Op]
@ -282,7 +282,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
case *ast.Unary: case *ast.Unary:
env := i.getCurrentEnv(ast) env := i.getCurrentEnv(ast)
arg := makeThunk("x", env, ast.Expr) arg := makeThunk(env, ast.Expr)
builtin := uopBuiltins[ast.Op] builtin := uopBuiltins[ast.Op]
@ -401,7 +401,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
vars := make(bindingFrame) vars := make(bindingFrame)
bindEnv := i.getCurrentEnv(a) bindEnv := i.getCurrentEnv(a)
for _, bind := range ast.Binds { for _, bind := range ast.Binds {
th := makeThunk(bind.Variable, bindEnv, bind.Body) th := makeThunk(bindEnv, bind.Body)
// recursive locals // recursive locals
vars[bind.Variable] = th vars[bind.Variable] = th
@ -467,8 +467,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
positional: make([]potentialValue, len(ast.Arguments.Positional)), positional: make([]potentialValue, len(ast.Arguments.Positional)),
} }
for i, arg := range ast.Arguments.Positional { for i, arg := range ast.Arguments.Positional {
// TODO(sbarzowski) better thunk name arguments.positional[i] = makeThunk(argEnv, arg)
arguments.positional[i] = makeThunk("arg", argEnv, arg)
} }
return e.evaluate(function.call(arguments)) return e.evaluate(function.call(arguments))
@ -700,13 +699,12 @@ func (i *interpreter) manifestAndSerializeJSON(trace *TraceElement, v value, mul
return buf.String(), nil return buf.String(), nil
} }
func (i *interpreter) EvalInCleanEnv(fromWhere *TraceElement, newContext *TraceContext, func (i *interpreter) EvalInCleanEnv(fromWhere *TraceElement, env *environment, ast ast.Node) (value, error) {
env *environment, ast ast.Node) (value, error) {
err := i.newCall(fromWhere, *env) err := i.newCall(fromWhere, *env)
if err != nil { if err != nil {
return nil, err return nil, err
} }
val, err := i.evaluate(ast, newContext) val, err := i.evaluate(ast)
i.stack.pop() i.stack.pop()
return val, err return val, err
} }
@ -736,12 +734,11 @@ func evaluateStd(i *interpreter) (value, error) {
) )
evalLoc := ast.MakeLocationRangeMessage("During evaluation of std") evalLoc := ast.MakeLocationRangeMessage("During evaluation of std")
evalTrace := &TraceElement{loc: &evalLoc} evalTrace := &TraceElement{loc: &evalLoc}
node, err := snippetToAST("std.jsonnet", getStdCode()) node, err := snippetToAST("<std>", getStdCode())
if err != nil { if err != nil {
return nil, err return nil, err
} }
context := TraceContext{Name: "<stdlib>"} return i.EvalInCleanEnv(evalTrace, &beforeStdEnv, node)
return i.EvalInCleanEnv(evalTrace, &context, &beforeStdEnv, node)
} }
func prepareExtVars(i *interpreter, ext vmExtMap) map[ast.Identifier]potentialValue { func prepareExtVars(i *interpreter, ext vmExtMap) map[ast.Identifier]potentialValue {
@ -796,8 +793,7 @@ func evaluate(node ast.Node, ext vmExtMap, maxStack int, importer Importer) (str
evalTrace := &TraceElement{ evalTrace := &TraceElement{
loc: &evalLoc, loc: &evalLoc,
} }
context := TraceContext{Name: "<main>"} result, err := i.EvalInCleanEnv(evalTrace, &i.initialEnv, node)
result, err := i.EvalInCleanEnv(evalTrace, &context, &i.initialEnv, node)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -92,6 +92,7 @@ func TestMain(t *testing.T) {
} }
mainTests = append(mainTests, mainTest{name: name, input: input, golden: golden}) mainTests = append(mainTests, mainTest{name: name, input: input, golden: golden})
} }
errFormatter := ErrorFormatter{pretty: true}
for _, test := range mainTests { for _, test := range mainTests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
vm := MakeVM() vm := MakeVM()
@ -109,7 +110,7 @@ func TestMain(t *testing.T) {
if err != nil { if err != nil {
// TODO(sbarzowski) perhaps somehow mark that we are processing // TODO(sbarzowski) perhaps somehow mark that we are processing
// an error. But for now we can treat them the same. // an error. But for now we can treat them the same.
output = err.Error() output = errFormatter.format(err)
} }
output += "\n" output += "\n"
if *update { if *update {
@ -188,19 +189,20 @@ func TestOneLineError(t *testing.T) {
// TODO(sbarzowski) checking if the whitespace is right is quite unpleasant, what can we do about it? // TODO(sbarzowski) checking if the whitespace is right is quite unpleasant, what can we do about it?
var minimalErrorTests = []errorFormattingTest{ var minimalErrorTests = []errorFormattingTest{
{"error", `error "x"`, "RUNTIME ERROR: x\n" + {"error", `error "x"`, "RUNTIME ERROR: x\n" +
" error:1:1-9 $\n" + // TODO(sbarzowski) if seems we have off-by-one in location
" During evaluation \n" + " During evaluation \n" +
" error:1:1-9 <main>\n"}, // TODO(sbarzowski) if seems we have off-by-one in location ""},
{"error_in_func", `local x(n) = if n == 0 then error "x" else x(n - 1); x(3)`, "RUNTIME ERROR: x\n" + {"error_in_func", `local x(n) = if n == 0 then error "x" else x(n - 1); x(3)`, "RUNTIME ERROR: x\n" +
" error_in_func:1:29-37 function <x>\n" +
" error_in_func:1:44-52 function <x>\n" +
" error_in_func:1:44-52 function <x>\n" +
" error_in_func:1:44-52 function <x>\n" +
" error_in_func:1:54-58 $\n" +
" During evaluation \n" + " During evaluation \n" +
" error_in_func:1:54-58 <main>\n" +
" error_in_func:1:44-52 function <anonymous>\n" +
" error_in_func:1:44-52 function <anonymous>\n" +
" error_in_func:1:44-52 function <anonymous>\n" +
" error_in_func:1:29-37 function <anonymous>\n" +
""}, ""},
{"error_in_error", `error (error "x")`, "RUNTIME ERROR: x\n" + {"error_in_error", `error (error "x")`, "RUNTIME ERROR: x\n" +
" error_in_error:1:8-16 $\n" +
" During evaluation \n" + " During evaluation \n" +
" error_in_error:1:8-16 <main>\n" +
""}, ""},
} }
@ -210,3 +212,6 @@ func TestMinimalError(t *testing.T) {
return formatter.format(r) return formatter.format(r)
}) })
} }
// TODO(sbarzowski) test pretty errors once they are stable-ish
// probably "golden" pattern is the right one for that

371
parser/context.go Normal file
View File

@ -0,0 +1,371 @@
/*
Copyright 2017 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package parser
import (
"fmt"
"github.com/google/go-jsonnet/ast"
)
var topLevelContext = "$"
const anonymous = "anonymous"
// TODO(sbarzowski) polish children functions and consider moving to AST
// and exporting
// directChildren are children of AST node that are executed in the same context
// and environment as their parent
//
// They must satisfy the following rules:
// * (no-delayed-evaluation) They are evaluated when their parent is evaluated or never.
// * (no-indirect-evaluation) They cannot be evaluated during evaluation of any non-direct children
// * (same-environment) They must be evaluated in the same environment as their parent
func directChildren(node ast.Node) []ast.Node {
switch node := node.(type) {
case *ast.Apply:
return []ast.Node{node.Target}
// TODO(sbarzowski) tailstrict call arguments (once we have tailstrict)
case *ast.ApplyBrace:
return []ast.Node{node.Left, node.Right}
case *ast.Array:
return nil
case *ast.Assert:
return []ast.Node{node.Cond, node.Message, node.Rest}
case *ast.Binary:
return []ast.Node{node.Left, node.Right}
case *ast.Conditional:
return []ast.Node{node.Cond, node.BranchTrue, node.BranchFalse}
case *ast.Dollar:
return nil
case *ast.Error:
return []ast.Node{node.Expr}
case *ast.Function:
return nil
case *ast.Import:
return nil
case *ast.ImportStr:
return nil
case *ast.Index:
return []ast.Node{node.Target, node.Index}
case *ast.Slice:
return []ast.Node{node.Target, node.BeginIndex, node.EndIndex, node.Step}
case *ast.Local:
return []ast.Node{node.Body}
case *ast.LiteralBoolean:
return nil
case *ast.LiteralNull:
return nil
case *ast.LiteralNumber:
return nil
case *ast.LiteralString:
return nil
case *ast.Object:
return objectFieldsDirectChildren(node.Fields)
case *ast.ArrayComp:
result := []ast.Node{}
spec := &node.Spec
for spec != nil {
result = append(result, spec.Expr)
for _, ifspec := range spec.Conditions {
result = append(result, ifspec.Expr)
}
spec = spec.Outer
}
return result
case *ast.ObjectComp:
result := objectFieldsDirectChildren(node.Fields)
spec := &node.Spec
for spec != nil {
result = append(result, spec.Expr)
for _, ifspec := range spec.Conditions {
result = append(result, ifspec.Expr)
}
spec = spec.Outer
}
return result
case *ast.Self:
return nil
case *ast.SuperIndex:
return []ast.Node{node.Index}
case *ast.InSuper:
return []ast.Node{node.Index}
case *ast.Unary:
return []ast.Node{node.Expr}
case *ast.Var:
return nil
}
panic(fmt.Sprintf("directChildren: Unknown node %#v", node))
}
// thunkChildren are children of AST node that are executed in a new context
// and capture environment from parent (thunked)
// TODO(sbarzowski) Make sure it works well with boundary cases like tailstrict arguments,
// make it more precise.
// Rules:
// * (same-environment) They must be evaluated in the same environment as their parent
// * (not-direct) If they can be direct children, they should (and cannot be thunked).
func thunkChildren(node ast.Node) []ast.Node {
switch node := node.(type) {
case *ast.Apply:
var nodes []ast.Node
for _, arg := range node.Arguments.Positional {
nodes = append(nodes, arg)
}
for _, arg := range node.Arguments.Named {
nodes = append(nodes, arg.Arg)
}
return nodes
case *ast.ApplyBrace:
return nil
case *ast.Array:
return node.Elements
case *ast.Assert:
return nil
case *ast.Binary:
return nil
case *ast.Conditional:
return nil
case *ast.Dollar:
return nil
case *ast.Error:
return nil
case *ast.Function:
return nil
case *ast.Import:
return nil
case *ast.ImportStr:
return nil
case *ast.Index:
return nil
case *ast.Slice:
return nil
case *ast.Local:
// TODO(sbarzowski) complicated
return nil
case *ast.LiteralBoolean:
return nil
case *ast.LiteralNull:
return nil
case *ast.LiteralNumber:
return nil
case *ast.LiteralString:
return nil
case *ast.Object:
return nil
case *ast.ArrayComp:
return []ast.Node{node.Body}
case *ast.ObjectComp:
return nil
case *ast.Self:
return nil
case *ast.SuperIndex:
return nil
case *ast.InSuper:
return nil
case *ast.Unary:
return nil
case *ast.Var:
return nil
}
panic(fmt.Sprintf("thunkChildren: Unknown node %#v", node))
}
func objectFieldsDirectChildren(fields ast.ObjectFields) ast.Nodes {
result := ast.Nodes{}
for _, field := range fields {
if field.Expr1 != nil {
result = append(result, field.Expr1)
}
}
return result
}
func inObjectFieldsChildren(fields ast.ObjectFields) ast.Nodes {
result := ast.Nodes{}
for _, field := range fields {
if field.MethodSugar {
result = append(result, field.Method)
} else {
if field.Expr2 != nil {
result = append(result, field.Expr2)
}
if field.Expr3 != nil {
result = append(result, field.Expr3)
}
}
}
return result
}
// children that are neither direct nor thunked, e.g. object field body
// They are evaluated in a different environment from their parent.
func specialChildren(node ast.Node) []ast.Node {
switch node := node.(type) {
case *ast.Apply:
return nil
case *ast.ApplyBrace:
return nil
case *ast.Array:
return nil
case *ast.Assert:
return nil
case *ast.Binary:
return nil
case *ast.Conditional:
return nil
case *ast.Dollar:
return nil
case *ast.Error:
return nil
case *ast.Function:
// TODO(sbarzowski) this
return nil
case *ast.Import:
return nil
case *ast.ImportStr:
return nil
case *ast.Index:
return nil
case *ast.Slice:
return nil
case *ast.Local:
return nil
case *ast.LiteralBoolean:
return nil
case *ast.LiteralNull:
return nil
case *ast.LiteralNumber:
return nil
case *ast.LiteralString:
return nil
case *ast.Object:
return inObjectFieldsChildren(node.Fields)
case *ast.ArrayComp:
return []ast.Node{node.Body}
case *ast.ObjectComp:
case *ast.Self:
return nil
case *ast.SuperIndex:
return nil
case *ast.InSuper:
return nil
case *ast.Unary:
return nil
case *ast.Var:
return nil
}
panic(fmt.Sprintf("specialChildren: Unknown node %#v", node))
}
func children(node ast.Node) []ast.Node {
var result []ast.Node
result = append(result, directChildren(node)...)
result = append(result, thunkChildren(node)...)
result = append(result, specialChildren(node)...)
return result
}
func functionContext(funcName string) *string {
r := "function <" + funcName + ">"
return &r
}
func objectContext(objName string) *string {
r := "object <" + objName + ">"
return &r
}
// addContext adds context to a node and its whole subtree.
//
// context is the surrounding context of a node (e.g. a function it's in)
//
// bind is a name that the node is bound to, i.e. if node is a local bind body
// then bind is its name. For nodes that are not bound to variables `anonymous`
// should be passed. For example:
// local x = 2 + 2; x
// In such case bind for binary node 2 + 2 is "x" and for every other node,
// including its children, its anonymous.
func addContext(node ast.Node, context *string, bind string) {
if node == nil {
return
}
node.SetContext(context)
switch node := node.(type) {
case *ast.Function:
funContext := functionContext(bind)
addContext(node.Body, funContext, anonymous)
for i := range node.Parameters.Named {
// Default arguments have the same context as the function body.
addContext(node.Parameters.Named[i].DefaultArg, funContext, anonymous)
}
case *ast.Object:
// TODO(sbarzowski) include fieldname, maybe even chains
outOfObject := directChildren(node)
for _, f := range outOfObject {
// This actually is evaluated outside of object
addContext(f, context, anonymous)
}
objContext := objectContext(bind)
inObject := inObjectFieldsChildren(node.Fields)
for _, f := range inObject {
// This actually is evaluated outside of object
addContext(f, objContext, anonymous)
}
case *ast.ObjectComp:
outOfObject := directChildren(node)
for _, f := range outOfObject {
// This actually is evaluated outside of object
addContext(f, context, anonymous)
}
objContext := objectContext(bind)
inObject := inObjectFieldsChildren(node.Fields)
for _, f := range inObject {
// This actually is evaluated outside of object
addContext(f, objContext, anonymous)
}
case *ast.Local:
for _, bind := range node.Binds {
namedThunkContext := "thunk <" + string(bind.Variable) + "> from <" + *context + ">"
if bind.Fun != nil {
addContext(bind.Fun, &namedThunkContext, string(bind.Variable))
} else {
addContext(bind.Body, &namedThunkContext, string(bind.Variable))
}
}
addContext(node.Body, context, bind)
default:
for _, child := range directChildren(node) {
addContext(child, context, anonymous)
}
// TODO(sbarzowski) avoid "thunk from <thunk from..."
thunkContext := "thunk from <" + *context + ">"
for _, child := range thunkChildren(node) {
addContext(child, &thunkContext, anonymous)
}
}
}

View File

@ -245,6 +245,7 @@ type position struct {
type lexer struct { type lexer struct {
fileName string // The file name being lexed, only used for errors fileName string // The file name being lexed, only used for errors
input string // The input string input string // The input string
source *ast.Source
pos position // Current position in input pos position // Current position in input
prev position // Previous position in input prev position // Previous position in input
@ -263,6 +264,7 @@ func makeLexer(fn string, input string) *lexer {
return &lexer{ return &lexer{
fileName: fn, fileName: fn,
input: input, input: input,
source: ast.BuildSource(input),
pos: position{byteNo: 0, lineNo: 1, lineStart: 0}, pos: position{byteNo: 0, lineNo: 1, lineStart: 0},
prev: position{byteNo: lexEOF, lineNo: 0, lineStart: 0}, prev: position{byteNo: lexEOF, lineNo: 0, lineStart: 0},
tokenStartLoc: ast.Location{Line: 1, Column: 1}, tokenStartLoc: ast.Location{Line: 1, Column: 1},
@ -337,7 +339,7 @@ func (l *lexer) emitFullToken(kind tokenKind, data, stringBlockIndent, stringBlo
data: data, data: data,
stringBlockIndent: stringBlockIndent, stringBlockIndent: stringBlockIndent,
stringBlockTermIndent: stringBlockTermIndent, stringBlockTermIndent: stringBlockTermIndent,
loc: ast.MakeLocationRange(l.fileName, l.tokenStartLoc, l.location()), loc: ast.MakeLocationRange(l.fileName, l.source, l.tokenStartLoc, l.location()),
}) })
l.fodder = fodder{} l.fodder = fodder{}
} }
@ -367,6 +369,10 @@ func (l *lexer) addFodder(kind fodderKind, data string) {
l.fodder = append(l.fodder, fodderElement{kind: kind, data: data}) l.fodder = append(l.fodder, fodderElement{kind: kind, data: data})
} }
func (l *lexer) makeStaticErrorPoint(msg string, loc ast.Location) StaticError {
return StaticError{Msg: msg, Loc: ast.MakeLocationRange(l.fileName, l.source, loc, loc)}
}
// lexNumber will consume a number and emit a token. It is assumed // lexNumber will consume a number and emit a token. It is assumed
// that the next rune to be served by the lexer will be a leading digit. // that the next rune to be served by the lexer will be a leading digit.
func (l *lexer) lexNumber() error { func (l *lexer) lexNumber() error {
@ -431,9 +437,9 @@ outerLoop:
case r >= '0' && r <= '9': case r >= '0' && r <= '9':
state = numAfterDigit state = numAfterDigit
default: default:
return MakeStaticErrorPoint( return l.makeStaticErrorPoint(
fmt.Sprintf("Couldn't lex number, junk after decimal point: %v", strconv.QuoteRuneToASCII(r)), fmt.Sprintf("Couldn't lex number, junk after decimal point: %v", strconv.QuoteRuneToASCII(r)),
l.fileName, l.prevLocation()) l.prevLocation())
} }
case numAfterDigit: case numAfterDigit:
switch { switch {
@ -451,17 +457,17 @@ outerLoop:
case r >= '0' && r <= '9': case r >= '0' && r <= '9':
state = numAfterExpDigit state = numAfterExpDigit
default: default:
return MakeStaticErrorPoint( return l.makeStaticErrorPoint(
fmt.Sprintf("Couldn't lex number, junk after 'E': %v", strconv.QuoteRuneToASCII(r)), fmt.Sprintf("Couldn't lex number, junk after 'E': %v", strconv.QuoteRuneToASCII(r)),
l.fileName, l.prevLocation()) l.prevLocation())
} }
case numAfterExpSign: case numAfterExpSign:
if r >= '0' && r <= '9' { if r >= '0' && r <= '9' {
state = numAfterExpDigit state = numAfterExpDigit
} else { } else {
return MakeStaticErrorPoint( return l.makeStaticErrorPoint(
fmt.Sprintf("Couldn't lex number, junk after exponent sign: %v", strconv.QuoteRuneToASCII(r)), fmt.Sprintf("Couldn't lex number, junk after exponent sign: %v", strconv.QuoteRuneToASCII(r)),
l.fileName, l.prevLocation()) l.prevLocation())
} }
case numAfterExpDigit: case numAfterExpDigit:
@ -558,8 +564,8 @@ func (l *lexer) lexSymbol() error {
l.resetTokenStart() // Throw out the leading /* l.resetTokenStart() // Throw out the leading /*
for r = l.next(); ; r = l.next() { for r = l.next(); ; r = l.next() {
if r == lexEOF { if r == lexEOF {
return MakeStaticErrorPoint("Multi-line comment has no terminating */", return l.makeStaticErrorPoint("Multi-line comment has no terminating */",
l.fileName, commentStartLoc) commentStartLoc)
} }
if r == '*' && l.peek() == '/' { if r == '*' && l.peek() == '/' {
commentData := l.input[l.tokenStart : l.pos.byteNo-1] // Don't include trailing */ commentData := l.input[l.tokenStart : l.pos.byteNo-1] // Don't include trailing */
@ -584,8 +590,8 @@ func (l *lexer) lexSymbol() error {
numWhiteSpace := checkWhitespace(l.input[l.pos.byteNo:], l.input[l.pos.byteNo:]) numWhiteSpace := checkWhitespace(l.input[l.pos.byteNo:], l.input[l.pos.byteNo:])
stringBlockIndent := l.input[l.pos.byteNo : l.pos.byteNo+numWhiteSpace] stringBlockIndent := l.input[l.pos.byteNo : l.pos.byteNo+numWhiteSpace]
if numWhiteSpace == 0 { if numWhiteSpace == 0 {
return MakeStaticErrorPoint("Text block's first line must start with whitespace", return l.makeStaticErrorPoint("Text block's first line must start with whitespace",
l.fileName, commentStartLoc) commentStartLoc)
} }
for { for {
@ -595,8 +601,7 @@ func (l *lexer) lexSymbol() error {
l.acceptN(numWhiteSpace) l.acceptN(numWhiteSpace)
for r = l.next(); r != '\n'; r = l.next() { for r = l.next(); r != '\n'; r = l.next() {
if r == lexEOF { if r == lexEOF {
return MakeStaticErrorPoint("Unexpected EOF", return l.makeStaticErrorPoint("Unexpected EOF", commentStartLoc)
l.fileName, commentStartLoc)
} }
cb.WriteRune(r) cb.WriteRune(r)
} }
@ -618,8 +623,7 @@ func (l *lexer) lexSymbol() error {
} }
l.backup() l.backup()
if !strings.HasPrefix(l.input[l.pos.byteNo:], "|||") { if !strings.HasPrefix(l.input[l.pos.byteNo:], "|||") {
return MakeStaticErrorPoint("Text block not terminated with |||", return l.makeStaticErrorPoint("Text block not terminated with |||", commentStartLoc)
l.fileName, commentStartLoc)
} }
l.acceptN(3) // Skip '|||' l.acceptN(3) // Skip '|||'
l.emitFullToken(tokenStringBlock, cb.String(), l.emitFullToken(tokenStringBlock, cb.String(),
@ -710,7 +714,7 @@ func Lex(fn string, input string) (tokens, error) {
l.resetTokenStart() // Don't include the quotes in the token data l.resetTokenStart() // Don't include the quotes in the token data
for r = l.next(); ; r = l.next() { for r = l.next(); ; r = l.next() {
if r == lexEOF { if r == lexEOF {
return nil, MakeStaticErrorPoint("Unterminated String", l.fileName, stringStartLoc) return nil, l.makeStaticErrorPoint("Unterminated String", stringStartLoc)
} }
if r == '"' { if r == '"' {
l.backup() l.backup()
@ -728,7 +732,7 @@ func Lex(fn string, input string) (tokens, error) {
l.resetTokenStart() // Don't include the quotes in the token data l.resetTokenStart() // Don't include the quotes in the token data
for r = l.next(); ; r = l.next() { for r = l.next(); ; r = l.next() {
if r == lexEOF { if r == lexEOF {
return nil, MakeStaticErrorPoint("Unterminated String", l.fileName, stringStartLoc) return nil, l.makeStaticErrorPoint("Unterminated String", stringStartLoc)
} }
if r == '\'' { if r == '\'' {
l.backup() l.backup()
@ -757,15 +761,14 @@ func Lex(fn string, input string) (tokens, error) {
} else if quot == '\'' { } else if quot == '\'' {
kind = tokenVerbatimStringSingle kind = tokenVerbatimStringSingle
} else { } else {
return nil, MakeStaticErrorPoint( return nil, l.makeStaticErrorPoint(
fmt.Sprintf("Couldn't lex verbatim string, junk after '@': %v", quot), fmt.Sprintf("Couldn't lex verbatim string, junk after '@': %v", quot),
l.fileName,
stringStartLoc, stringStartLoc,
) )
} }
for r = l.next(); ; r = l.next() { for r = l.next(); ; r = l.next() {
if r == lexEOF { if r == lexEOF {
return nil, MakeStaticErrorPoint("Unterminated String", l.fileName, stringStartLoc) return nil, l.makeStaticErrorPoint("Unterminated String", stringStartLoc)
} else if r == quot { } else if r == quot {
if l.peek() == quot { if l.peek() == quot {
l.next() l.next()
@ -799,9 +802,9 @@ func Lex(fn string, input string) (tokens, error) {
return nil, err return nil, err
} }
} else { } else {
return nil, MakeStaticErrorPoint( return nil, l.makeStaticErrorPoint(
fmt.Sprintf("Could not lex the character %s", strconv.QuoteRuneToASCII(r)), fmt.Sprintf("Could not lex the character %s", strconv.QuoteRuneToASCII(r)),
l.fileName, l.prevLocation()) l.prevLocation())
} }
} }

View File

@ -61,11 +61,11 @@ func makeUnexpectedError(t *token, while string) error {
} }
func locFromTokens(begin, end *token) ast.LocationRange { func locFromTokens(begin, end *token) ast.LocationRange {
return ast.MakeLocationRange(begin.loc.FileName, begin.loc.Begin, end.loc.End) return ast.LocationRangeBetween(&begin.loc, &end.loc)
} }
func locFromTokenAST(begin *token, end ast.Node) ast.LocationRange { func locFromTokenAST(begin *token, end ast.Node) ast.LocationRange {
return ast.MakeLocationRange(begin.loc.FileName, begin.loc.Begin, end.Loc().End) return ast.LocationRangeBetween(&begin.loc, end.Loc())
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -219,12 +219,19 @@ func (p *parser) parseBind(binds *ast.LocalBinds) error {
} }
} }
var fun *ast.Function
if p.peek().kind == tokenParenL { if p.peek().kind == tokenParenL {
p.pop() p.pop()
params, gotComma, err := p.parseParameters("function parameter") params, gotComma, err := p.parseParameters("function parameter")
if err != nil { if err != nil {
return err return err
} }
fun = &ast.Function{
Parameters: *params,
TrailingComma: gotComma,
}
}
_, err = p.popExpectOp("=") _, err = p.popExpectOp("=")
if err != nil { if err != nil {
return err return err
@ -233,22 +240,15 @@ func (p *parser) parseBind(binds *ast.LocalBinds) error {
if err != nil { if err != nil {
return err return err
} }
if fun != nil {
fun.Body = body
*binds = append(*binds, ast.LocalBind{ *binds = append(*binds, ast.LocalBind{
Variable: ast.Identifier(varID.data), Variable: ast.Identifier(varID.data),
Body: body, Body: body,
FunctionSugar: true, Fun: fun,
Params: params,
TrailingComma: gotComma,
}) })
} else { } else {
_, err = p.popExpectOp("=")
if err != nil {
return err
}
body, err := p.parse(maxPrecedence)
if err != nil {
return err
}
*binds = append(*binds, ast.LocalBind{ *binds = append(*binds, ast.LocalBind{
Variable: ast.Identifier(varID.data), Variable: ast.Identifier(varID.data),
Body: body, Body: body,
@ -299,6 +299,7 @@ func (p *parser) parseObjectAssignmentOp() (plusSugar bool, hide ast.ObjectField
// +gen set // +gen set
type LiteralField string type LiteralField string
// Parse object or object comprehension without leading brace
func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) { func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
var fields ast.ObjectFields var fields ast.ObjectFields
literalFields := make(literalFieldSet) literalFields := make(literalFieldSet)
@ -451,11 +452,21 @@ func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
return nil, nil, err return nil, nil, err
} }
var method *ast.Function
if isMethod {
method = &ast.Function{
Parameters: *params,
TrailingComma: methComma,
Body: body,
}
}
fields = append(fields, ast.ObjectField{ fields = append(fields, ast.ObjectField{
Kind: kind, Kind: kind,
Hide: hide, Hide: hide,
SuperSugar: plusSugar, SuperSugar: plusSugar,
MethodSugar: isMethod, MethodSugar: isMethod,
Method: method,
Expr1: expr1, Expr1: expr1,
Id: id, Id: id,
Params: params, Params: params,
@ -475,6 +486,8 @@ func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
return nil, nil, MakeStaticError(fmt.Sprintf("Duplicate local var: %v", id), varID.loc) return nil, nil, MakeStaticError(fmt.Sprintf("Duplicate local var: %v", id), varID.loc)
} }
// TODO(sbarzowski) Can we reuse regular local bind parsing here?
isMethod := false isMethod := false
funcComma := false funcComma := false
var params *ast.Parameters var params *ast.Parameters
@ -496,6 +509,15 @@ func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
return nil, nil, err return nil, nil, err
} }
var method *ast.Function
if isMethod {
method = &ast.Function{
Parameters: *params,
TrailingComma: funcComma,
Body: body,
}
}
binds.Add(id) binds.Add(id)
fields = append(fields, ast.ObjectField{ fields = append(fields, ast.ObjectField{
@ -503,6 +525,7 @@ func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
Hide: ast.ObjectFieldVisible, Hide: ast.ObjectFieldVisible,
SuperSugar: false, SuperSugar: false,
MethodSugar: isMethod, MethodSugar: isMethod,
Method: method,
Id: &id, Id: &id,
Params: params, Params: params,
TrailingComma: funcComma, TrailingComma: funcComma,
@ -1165,5 +1188,7 @@ func Parse(t tokens) (ast.Node, error) {
return nil, MakeStaticError(fmt.Sprintf("Did not expect: %v", p.peek()), p.peek().loc) return nil, MakeStaticError(fmt.Sprintf("Did not expect: %v", p.peek()), p.peek().loc)
} }
addContext(expr, &topLevelContext, anonymous)
return expr, nil return expr, nil
} }

View File

@ -16,6 +16,7 @@ limitations under the License.
package parser package parser
import ( import (
"fmt"
"testing" "testing"
) )
@ -124,15 +125,19 @@ var tests = []string{
func TestParser(t *testing.T) { func TestParser(t *testing.T) {
for _, s := range tests { for _, s := range tests {
t.Run(s, func(t *testing.T) {
fmt.Println(s)
tokens, err := Lex("test", s) tokens, err := Lex("test", s)
if err != nil { if err != nil {
t.Errorf("Unexpected lex error\n input: %v\n error: %v", s, err) t.Errorf("Unexpected lex error\n input: %v\n error: %v", s, err)
continue return
} }
_, err = Parse(tokens) _, err = Parse(tokens)
if err != nil { if err != nil {
t.Errorf("Unexpected parse error\n input: %v\n error: %v", s, err) t.Errorf("Unexpected parse error\n input: %v\n error: %v", s, err)
} }
})
} }
} }
@ -246,19 +251,21 @@ var errorTests = []testError{
func TestParserErrors(t *testing.T) { func TestParserErrors(t *testing.T) {
for _, s := range errorTests { for _, s := range errorTests {
t.Run(s.input, func(t *testing.T) {
tokens, err := Lex("test", s.input) tokens, err := Lex("test", s.input)
if err != nil { if err != nil {
t.Errorf("Unexpected lex error\n input: %v\n error: %v", s.input, err) t.Errorf("Unexpected lex error\n input: %v\n error: %v", s.input, err)
continue return
} }
_, err = Parse(tokens) _, err = Parse(tokens)
if err == nil { if err == nil {
t.Errorf("Expected parse error but got success\n input: %v", s.input) t.Errorf("Expected parse error but got success\n input: %v", s.input)
continue return
} }
if err.Error() != s.err { if err.Error() != s.err {
t.Errorf("Error string not as expected\n input: %v\n expected error: %v\n actual error: %v", s.input, s.err, err.Error()) t.Errorf("Error string not as expected\n input: %v\n expected error: %v\n actual error: %v", s.input, s.err, err.Error())
} }
})
} }
} }

View File

@ -36,10 +36,6 @@ func MakeStaticErrorMsg(msg string) StaticError {
return StaticError{Msg: msg} return StaticError{Msg: msg}
} }
func MakeStaticErrorPoint(msg string, fn string, l ast.Location) StaticError {
return StaticError{Msg: msg, Loc: ast.MakeLocationRange(fn, l, l)}
}
func MakeStaticError(msg string, lr ast.LocationRange) StaticError { func MakeStaticError(msg string, lr ast.LocationRange) StaticError {
return StaticError{Msg: msg, Loc: lr} return StaticError{Msg: msg, Loc: lr}
} }

View File

@ -48,20 +48,15 @@ func traceElementToTraceFrame(trace *TraceElement) TraceFrame {
tf := TraceFrame{Loc: *trace.loc} tf := TraceFrame{Loc: *trace.loc}
if trace.context != nil { if trace.context != nil {
// TODO(sbarzowski) maybe it should never be nil // TODO(sbarzowski) maybe it should never be nil
tf.Name = trace.context.Name tf.Name = *trace.context
} else { } else {
tf.Name = "" tf.Name = ""
} }
return tf return tf
} }
type TraceContext struct {
// Human readable name - e.g. function <foo>
Name string
}
// TODO(sbarzowski) better name // TODO(sbarzowski) better name
type TraceElement struct { type TraceElement struct {
loc *ast.LocationRange loc *ast.LocationRange
context *TraceContext context ast.Context
} }

4
testdata/".golden vendored
View File

@ -1 +1,5 @@
testdata/":2:1 Unexpected end of file. testdata/":2:1 Unexpected end of file.

4
testdata/'.golden vendored
View File

@ -1 +1,5 @@
testdata/':2:1 Unexpected end of file. testdata/':2:1 Unexpected end of file.

View File

@ -1 +1,5 @@
testdata/arrcomp5:1:14-15 Unknown variable: x testdata/arrcomp5:1:14-15 Unknown variable: x
[y for y in [x + 1] for x in [1, 2, 3]]

View File

@ -1 +1,5 @@
testdata/arrcomp_if4:1:33-34 Unknown variable: y testdata/arrcomp_if4:1:33-34 Unknown variable: y
[[x, y] for x in [1,2,3,4,5] if y == 3 for y in [1,2,3,4,5]]

View File

@ -1 +1,13 @@
RUNTIME ERROR: x RUNTIME ERROR: x
-------------------------------------------------
testdata/arrcomp_if6:1:20-28 $
[x for x in [1] if error "x"]
-------------------------------------------------
<builtin> builtin function <flatMap>
-------------------------------------------------
During evaluation

View File

@ -1 +1,11 @@
RUNTIME ERROR: Unexpected type number, expected boolean RUNTIME ERROR: Unexpected type number, expected boolean
-------------------------------------------------
-------------------------------------------------
<builtin> builtin function <flatMap>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Assertion failed RUNTIME ERROR: Assertion failed
-------------------------------------------------
-------------------------------------------------
During evaluation

View File

@ -1 +1,15 @@
RUNTIME ERROR: Assertion failed. {"x": 1} != {"x": 2} RUNTIME ERROR: Assertion failed. {"x": 1} != {"x": 2}
-------------------------------------------------
<std>:649:13-56 function <anonymous>
error "Assertion failed. " + a + " != " + b,
-------------------------------------------------
testdata/assert_equal4:1:1-32 $
std.assertEqual({x: 1}, {x: 2})
-------------------------------------------------
During evaluation

17
testdata/assert_equal5.golden vendored Normal file
View File

@ -0,0 +1,17 @@
RUNTIME ERROR: Assertion failed.
!=
-------------------------------------------------
<std>:649:13-56 function <anonymous>
error "Assertion failed. " + a + " != " + b,
-------------------------------------------------
testdata/assert_equal5:1:1-29 $
std.assertEqual("\n ", "\n")
-------------------------------------------------
During evaluation

1
testdata/assert_equal5.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
std.assertEqual("\n ", "\n")

15
testdata/assert_equal6.golden vendored Normal file
View File

@ -0,0 +1,15 @@
RUNTIME ERROR: Assertion failed.  !=
-------------------------------------------------
<std>:649:13-56 function <anonymous>
error "Assertion failed. " + a + " != " + b,
-------------------------------------------------
testdata/assert_equal6:1:1-34 $
std.assertEqual("\u001b[31m", "")
-------------------------------------------------
During evaluation

1
testdata/assert_equal6.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
std.assertEqual("\u001b[31m", "")

View File

@ -1 +1,8 @@
RUNTIME ERROR: Assertion failed RUNTIME ERROR: Assertion failed
-------------------------------------------------
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Custom Message RUNTIME ERROR: Custom Message
-------------------------------------------------
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: function expected 1 argument(s), but got 0 RUNTIME ERROR: function expected 1 argument(s), but got 0
-------------------------------------------------
testdata/bad_function_call:1:1-18 $
(function(x) x)()
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: function expected 1 argument(s), but got 2 RUNTIME ERROR: function expected 1 argument(s), but got 2
-------------------------------------------------
testdata/bad_function_call2:1:1-22 $
(function(x) x)(1, 2)
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: function expected 1 argument(s), but got 2 RUNTIME ERROR: function expected 1 argument(s), but got 2
-------------------------------------------------
testdata/bad_function_call_and_error:1:1-38 $
(function(x) x)(error "x", error "y")
-------------------------------------------------
During evaluation

View File

@ -1 +1,15 @@
RUNTIME ERROR: x RUNTIME ERROR: x
-------------------------------------------------
testdata/bitwise_and4:1:5-13 $
1 & error "x"
-------------------------------------------------
testdata/bitwise_and4:1:1-13 $
1 & error "x"
-------------------------------------------------
During evaluation

View File

@ -1 +1,15 @@
RUNTIME ERROR: x RUNTIME ERROR: x
-------------------------------------------------
testdata/bitwise_xor7:1:5-13 $
1 ^ error "x"
-------------------------------------------------
testdata/bitwise_xor7:1:1-13 $
1 ^ error "x"
-------------------------------------------------
During evaluation

1
testdata/block_string.golden vendored Normal file
View File

@ -0,0 +1 @@
"x\n\ty\nz\n"

5
testdata/block_string.jsonnet vendored Normal file
View File

@ -0,0 +1,5 @@
|||
x
y
z
|||

View File

@ -1 +1,8 @@
RUNTIME ERROR: Codepoints must be >= 0, got -1 RUNTIME ERROR: Codepoints must be >= 0, got -1
-------------------------------------------------
<builtin> builtin function <char>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Invalid unicode codepoint, got 1.114112e+06 RUNTIME ERROR: Invalid unicode codepoint, got 1.114112e+06
-------------------------------------------------
<builtin> builtin function <char>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Unexpected type number, expected string RUNTIME ERROR: Unexpected type number, expected string
-------------------------------------------------
<builtin> builtin function <objectHasEx>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Unexpected type number, expected object RUNTIME ERROR: Unexpected type number, expected object
-------------------------------------------------
<builtin> builtin function <objectHasEx>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Overflow RUNTIME ERROR: Overflow
-------------------------------------------------
<builtin> builtin function <exp>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Overflow RUNTIME ERROR: Overflow
-------------------------------------------------
<builtin> builtin function <exp>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Overflow RUNTIME ERROR: Overflow
-------------------------------------------------
<builtin> builtin function <log>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Not a number RUNTIME ERROR: Not a number
-------------------------------------------------
<builtin> builtin function <log>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Not a number RUNTIME ERROR: Not a number
-------------------------------------------------
<builtin> builtin function <log>
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: Unexpected type number, expected function RUNTIME ERROR: Unexpected type number, expected function
-------------------------------------------------
testdata/call_number:1:1-5 $
42()
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: Overflow RUNTIME ERROR: Overflow
-------------------------------------------------
testdata/div4:1:1-19 $
1/(1e-160)/(1e-160)
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: Division by zero. RUNTIME ERROR: Division by zero.
-------------------------------------------------
testdata/div_by_zero:1:1-4 $
1/0
-------------------------------------------------
During evaluation

20
testdata/double_thunk.golden vendored Normal file
View File

@ -0,0 +1,20 @@
RUNTIME ERROR: xxx
-------------------------------------------------
testdata/double_thunk:1:21-31 thunk <y> from <thunk <x> from <$>>
local x = local y = error "xxx"; y; x
-------------------------------------------------
testdata/double_thunk:1:34-35 thunk <x> from <$>
local x = local y = error "xxx"; y; x
-------------------------------------------------
testdata/double_thunk:1:37-38 $
local x = local y = error "xxx"; y; x
-------------------------------------------------
During evaluation

1
testdata/double_thunk.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
local x = local y = error "xxx"; y; x

View File

@ -1 +1,10 @@
RUNTIME ERROR: 42 RUNTIME ERROR: 42
-------------------------------------------------
testdata/error:1:1-10 $
error "42"
-------------------------------------------------
During evaluation

15
testdata/error_from_array.golden vendored Normal file
View File

@ -0,0 +1,15 @@
RUNTIME ERROR: xxx
-------------------------------------------------
testdata/error_from_array:1:2-12 thunk from <$>
[error "xxx"][0]
-------------------------------------------------
testdata/error_from_array:1:1-17 $
[error "xxx"][0]
-------------------------------------------------
During evaluation

1
testdata/error_from_array.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
[error "xxx"][0]

15
testdata/error_from_func.golden vendored Normal file
View File

@ -0,0 +1,15 @@
RUNTIME ERROR: xxx
-------------------------------------------------
testdata/error_from_func:1:25-32 function <foo>
local foo = function(x) error x; foo("xxx")
-------------------------------------------------
testdata/error_from_func:1:34-44 $
local foo = function(x) error x; foo("xxx")
-------------------------------------------------
During evaluation

1
testdata/error_from_func.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
local foo = function(x) error x; foo("xxx")

View File

@ -1 +1,5 @@
testdata/error_hexnumber:1:2-5 Did not expect: (IDENTIFIER, "x42") testdata/error_hexnumber:1:2-5 Did not expect: (IDENTIFIER, "x42")
0x42

15
testdata/error_in_method.golden vendored Normal file
View File

@ -0,0 +1,15 @@
RUNTIME ERROR: xxx
-------------------------------------------------
testdata/error_in_method:1:23-30 function <anonymous>
local obj = { foo(x): error x }; obj.foo("xxx")
-------------------------------------------------
testdata/error_in_method:1:34-48 $
local obj = { foo(x): error x }; obj.foo("xxx")
-------------------------------------------------
During evaluation

4
testdata/error_in_method.jsonnet vendored Normal file
View File

@ -0,0 +1,4 @@
local obj = { foo(x): error x }; obj.foo("xxx")

15
testdata/error_in_object_local.golden vendored Normal file
View File

@ -0,0 +1,15 @@
RUNTIME ERROR: xxx
-------------------------------------------------
testdata/error_in_object_local:1:20-29 function <anonymous>
{ local foo(bar) = error bar, baz: foo("xxx") }
-------------------------------------------------
testdata/error_in_object_local:1:36-46 object <anonymous>
{ local foo(bar) = error bar, baz: foo("xxx") }
-------------------------------------------------
During manifestation

View File

@ -0,0 +1 @@
{ local foo(bar) = error bar, baz: foo("xxx") }

View File

@ -1 +1,13 @@
RUNTIME ERROR: xxx RUNTIME ERROR: xxx
-------------------------------------------------
<extvar:errorVar>:1:1-11 $
error 'xxx'
-------------------------------------------------
<builtin> builtin function <extVar>
-------------------------------------------------
During evaluation

View File

@ -1 +1,5 @@
<extvar:UndeclaredX>:1:1-2 Unknown variable: x <extvar:UndeclaredX>:1:1-2 Unknown variable: x
x

View File

@ -1 +1,5 @@
<extvar:staticErrorVar>:1:1-2 Unexpected: (")", ")") while parsing terminal <extvar:staticErrorVar>:1:1-2 Unexpected: (")", ")") while parsing terminal
)

View File

@ -1 +1,8 @@
RUNTIME ERROR: Undefined external variable: UNKNOWN RUNTIME ERROR: Undefined external variable: UNKNOWN
-------------------------------------------------
<builtin> builtin function <extVar>
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: Field name must be string, got number RUNTIME ERROR: Field name must be string, got number
-------------------------------------------------
testdata/fieldname_not_string:1:1-15 $
{ [1234]: 42 }
-------------------------------------------------
During evaluation

View File

@ -1 +1,5 @@
RUNTIME ERROR: Couldn't manifest function in JSON output. RUNTIME ERROR: Couldn't manifest function in JSON output.
-------------------------------------------------
During manifestation

View File

@ -1 +1,7 @@
testdata/import_block_literal:(1:8)-(3:4) Block string literals not allowed in imports testdata/import_block_literal:(1:8)-(3:4) Block string literals not allowed in imports
import |||
block_literals_for_imports_are_not_allowed_and_make_exactly_zero_sense
|||

View File

@ -1 +1,5 @@
testdata/import_computed:1:9-16 Computed imports are not allowed testdata/import_computed:1:9-16 Computed imports are not allowed
import "a" + "b"

View File

@ -1 +1,10 @@
RUNTIME ERROR: read testdata: is a directory RUNTIME ERROR: read testdata: is a directory
-------------------------------------------------
testdata/import_failure_directory:3:1-10 $
import '.'
-------------------------------------------------
During evaluation

View File

@ -1 +1,5 @@
testdata/".jsonnet:2:1 Unexpected end of file. testdata/".jsonnet:2:1 Unexpected end of file.

View File

@ -1 +1,7 @@
testdata/importstr_block_literal:(1:11)-(3:4) Block string literals not allowed in imports testdata/importstr_block_literal:(1:11)-(3:4) Block string literals not allowed in imports
importstr |||
block_literals_for_imports_are_not_allowed_and_make_exactly_zero_sense
|||

View File

@ -1 +1,5 @@
testdata/importstr_computed:1:12-19 Computed imports are not allowed testdata/importstr_computed:1:12-19 Computed imports are not allowed
importstr "a" + "b"

View File

@ -1 +1,5 @@
testdata/insuper2:1:11-13 Expected token OPERATOR but got (in, "in") testdata/insuper2:1:11-13 Expected token OPERATOR but got (in, "in")
{ } { "x" in super }

View File

@ -1 +1,5 @@
testdata/insuper4:1:2-13 Can't use super outside of an object. testdata/insuper4:1:2-13 Can't use super outside of an object.
"x" in super

View File

@ -1 +1,5 @@
testdata/insuper6:1:10-20 Unknown variable: undeclared testdata/insuper6:1:10-20 Unknown variable: undeclared
{ assert undeclared in super }

View File

@ -1 +1,15 @@
RUNTIME ERROR: should happen RUNTIME ERROR: should happen
-------------------------------------------------
testdata/lazy_operator2:1:9-29 $
true && error "should happen"
-------------------------------------------------
testdata/lazy_operator2:1:1-29 $
true && error "should happen"
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: Couldn't open import "no chance a file with this name exists": No match locally or in the Jsonnet library paths. RUNTIME ERROR: Couldn't open import "no chance a file with this name exists": No match locally or in the Jsonnet library paths.
-------------------------------------------------
testdata/nonexistent_import:1:1-50 $
importstr 'no chance a file with this name exists'
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: Couldn't open import "ąęółńśćźż \" ' \n\n\t\t": No match locally or in the Jsonnet library paths. RUNTIME ERROR: Couldn't open import "ąęółńśćźż \" ' \n\n\t\t": No match locally or in the Jsonnet library paths.
-------------------------------------------------
testdata/nonexistent_import_crazy:1:1-45 $
importstr "ąęółńśćźż \" \' \n\n\t\t"
-------------------------------------------------
During evaluation

View File

@ -1 +1,5 @@
testdata/number_leading_zero:1:2-4 Did not expect: (NUMBER, "42") testdata/number_leading_zero:1:2-4 Did not expect: (NUMBER, "42")
042

View File

@ -1 +1,5 @@
testdata/object_comp_assert:1:32-35 Object comprehension cannot have asserts. testdata/object_comp_assert:1:32-35 Object comprehension cannot have asserts.
{ assert self.x == 5, ["x"]: 5 for i in [42] }

View File

@ -1 +1,5 @@
testdata/object_comp_bad_field:1:9-12 Object comprehensions can only have [e] fields. testdata/object_comp_bad_field:1:9-12 Object comprehensions can only have [e] fields.
{ x: 42 for _ in [1] }

View File

@ -1 +1,5 @@
testdata/object_comp_bad_field2:1:11-14 Object comprehensions can only have [e] fields. testdata/object_comp_bad_field2:1:11-14 Object comprehensions can only have [e] fields.
{ "x": 42 for _ in [1] }

View File

@ -1 +1,8 @@
RUNTIME ERROR: Duplicate field name: "x" RUNTIME ERROR: Duplicate field name: "x"
-------------------------------------------------
<builtin> builtin function <$objectFlatMerge>
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: xxx RUNTIME ERROR: xxx
-------------------------------------------------
testdata/object_comp_err_elem:1:11-21 object <anonymous>
{ ["x"]: error "xxx" for x in [1] }
-------------------------------------------------
During manifestation

View File

@ -1 +1,13 @@
RUNTIME ERROR: xxx RUNTIME ERROR: xxx
-------------------------------------------------
testdata/object_comp_err_index:1:4-14 $
{ [error "xxx"]: 42 for x in [1] }
-------------------------------------------------
<builtin> builtin function <$objectFlatMerge>
-------------------------------------------------
During evaluation

View File

@ -1 +1,5 @@
testdata/object_comp_illegal:1:15-18 Object comprehension can only have one field. testdata/object_comp_illegal:1:15-18 Object comprehension can only have one field.
{ local x = 5 for y in [1, 2, 3] }

View File

@ -1 +1,13 @@
RUNTIME ERROR: Field name must be string, got number RUNTIME ERROR: Field name must be string, got number
-------------------------------------------------
testdata/object_comp_int_index:1:1-30 $
{ [x]: x for x in [1, 2, 3] }
-------------------------------------------------
<builtin> builtin function <$objectFlatMerge>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Object assertion failed. RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,13 @@
RUNTIME ERROR: Object assertion failed. RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
testdata/object_invariant11:1:1-19 $
{ assert false }.x
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: x RUNTIME ERROR: x
-------------------------------------------------
testdata/object_invariant13:1:10-18 object <anonymous>
{ assert error "x" }
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: xxx RUNTIME ERROR: xxx
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Object assertion failed. RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,34 @@
RUNTIME ERROR: Field does not exist: x RUNTIME ERROR: Field does not exist: x
-------------------------------------------------
testdata/object_invariant7:1:16-21 object <anonymous>
{ x: 5, assert super.x == 5 }
-------------------------------------------------
<std>:969:29-30 thunk from <thunk <ta> from <function <anonymous>>>
local ta = std.type(a);
-------------------------------------------------
<builtin> builtin function <type>
-------------------------------------------------
<std>:971:33-35 thunk from <function <anonymous>>
if !std.primitiveEquals(ta, tb) then
-------------------------------------------------
<builtin> builtin function <primitiveEquals>
-------------------------------------------------
<std>:971:12-40 function <anonymous>
if !std.primitiveEquals(ta, tb) then
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Object assertion failed. RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Object assertion failed. RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Object assertion failed. RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Object assertion failed. RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: yyy RUNTIME ERROR: yyy
-------------------------------------------------
-------------------------------------------------
During manifestation

3
testdata/object_local.golden vendored Normal file
View File

@ -0,0 +1,3 @@
{
"baz": 42
}

2
testdata/object_local.jsonnet vendored Normal file
View File

@ -0,0 +1,2 @@
{ local foo(bar) = bar, baz: foo(42) }

Some files were not shown because too many files have changed in this diff Show More