Skip to content

Commit

Permalink
Suppress match errors if closures contained errors
Browse files Browse the repository at this point in the history
These changes modify the type checker to run on closures contained in
the expression before the containing expression. This way the type
checker can suppress less specific/actionable match errors in the same
way it does for ref errors.

Fixes #438
  • Loading branch information
tsandall committed Aug 31, 2017
1 parent e058634 commit 0403a6b
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 7 deletions.
52 changes: 45 additions & 7 deletions ast/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,34 @@ func (tc *typeChecker) CheckBody(env *TypeEnv, body Body) (*TypeEnv, Errors) {
}

WalkExprs(body, func(expr *Expr) bool {

closureErrs := tc.checkClosures(env, expr)
for _, err := range closureErrs {
tc.err(err)
}

hasClosureErrors := len(closureErrs) > 0

vis := newRefChecker(env)
Walk(vis, expr)
for _, err := range vis.errs {
tc.err(err)
}

refErrors := len(vis.errs) > 0
hasRefErrors := len(vis.errs) > 0

if err := tc.checkExpr(env, expr); err != nil {
// Suppress errors if another error occurred that is more
// actionable. In this case, if there is a ref error and the
// expression error is due to a nil type, it's likely caused by
// inability to infer a value's type referred to in the expr.
if !refErrors || !causedByNilType(err) {
// Suppress this error if a more actionable one has occurred. In
// this case, if an error occurred in a ref or closure contained in
// this expression, and the error is due to a nil type, then it's
// likely to be the result of the more specific error.
skip := (hasClosureErrors || hasRefErrors) && causedByNilType(err)
if !skip {
tc.err(err)
}
}
return false

return true
})

return env, tc.errs
Expand Down Expand Up @@ -103,6 +113,34 @@ func (tc *typeChecker) CheckTypes(env *TypeEnv, sorted []util.T) (*TypeEnv, Erro
return env, tc.errs
}

func (tc *typeChecker) checkClosures(env *TypeEnv, expr *Expr) Errors {
var result Errors
WalkClosures(expr, func(x interface{}) bool {
switch x := x.(type) {
case *ArrayComprehension:
_, errs := newTypeChecker().CheckBody(env, x.Body)
if len(errs) > 0 {
result = errs
return true
}
case *SetComprehension:
_, errs := newTypeChecker().CheckBody(env, x.Body)
if len(errs) > 0 {
result = errs
return true
}
case *ObjectComprehension:
_, errs := newTypeChecker().CheckBody(env, x.Body)
if len(errs) > 0 {
result = errs
return true
}
}
return false
})
return result
}

func (tc *typeChecker) checkFunc(env *TypeEnv, fn *Func) {
tc.inFunc = true
defer func() {
Expand Down
13 changes: 13 additions & 0 deletions ast/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,19 @@ func TestCheckErrorSuppression(t *testing.T) {
if !ok {
t.Fatalf("Expected ref error but got: %v", errs)
}

query = `_ = [true | count(1)]`

_, errs = newTypeChecker().CheckBody(newTypeChecker().checkLanguageBuiltins(), MustParseBody(query))
if len(errs) != 1 {
t.Fatalf("Expected exactly one error but got: %v", errs)
}

_, ok = errs[0].Details.(*ArgErrDetail)
if !ok {
t.Fatalf("Expected arg error but got: %v", errs)
}

}

func TestCheckBadCardinality(t *testing.T) {
Expand Down

0 comments on commit 0403a6b

Please sign in to comment.