@@ -10,9 +10,11 @@ import (
10
10
"fmt"
11
11
"regexp"
12
12
"sort"
13
+ "strings"
13
14
14
15
"github.com/open-policy-agent/opa/ast"
15
16
"github.com/open-policy-agent/opa/internal/future"
17
+ "github.com/open-policy-agent/opa/types"
16
18
)
17
19
18
20
// Opts lets you control the code formatting via `AstWithOpts()`.
@@ -37,6 +39,7 @@ func Source(filename string, src []byte) ([]byte, error) {
37
39
if err != nil {
38
40
return nil , err
39
41
}
42
+
40
43
formatted , err := Ast (module )
41
44
if err != nil {
42
45
return nil , fmt .Errorf ("%s: %v" , filename , err )
@@ -142,6 +145,7 @@ func AstWithOpts(x interface{}, opts Opts) ([]byte, error) {
142
145
143
146
w := & writer {
144
147
indent : "\t " ,
148
+ errs : make ([]* ast.Error , 0 ),
145
149
}
146
150
147
151
switch x := x .(type ) {
@@ -178,6 +182,9 @@ func AstWithOpts(x interface{}, opts Opts) ([]byte, error) {
178
182
return nil , fmt .Errorf ("not an ast element: %v" , x )
179
183
}
180
184
185
+ if len (w .errs ) > 0 {
186
+ return nil , w .errs
187
+ }
181
188
return squashTrailingNewlines (w .buf .Bytes ()), nil
182
189
}
183
190
@@ -228,6 +235,7 @@ type writer struct {
228
235
inline bool
229
236
beforeEnd * ast.Comment
230
237
delay bool
238
+ errs ast.Errors
231
239
}
232
240
233
241
func (w * writer ) writeModule (module * ast.Module , o fmtOpts ) {
@@ -587,7 +595,7 @@ func (w *writer) writeSomeDecl(decl *ast.SomeDecl, comments []*ast.Comment) []*a
587
595
w .write ("," )
588
596
}
589
597
case ast.Call :
590
- comments = w .writeInOperator (false , val [1 :], comments )
598
+ comments = w .writeInOperator (false , val [1 :], comments , decl . Location , ast . BuiltinMap [ val [ 0 ]. String ()]. Decl )
591
599
}
592
600
}
593
601
@@ -622,7 +630,7 @@ func (w *writer) writeFunctionCall(expr *ast.Expr, comments []*ast.Comment) []*a
622
630
623
631
switch operator {
624
632
case ast .Member .Name , ast .MemberWithKey .Name :
625
- return w .writeInOperator (false , terms [1 :], comments )
633
+ return w .writeInOperator (false , terms [1 :], comments , terms [ 0 ]. Location , ast . BuiltinMap [ terms [ 0 ]. String ()]. Decl )
626
634
}
627
635
628
636
bi , ok := ast .BuiltinMap [operator ]
@@ -647,6 +655,9 @@ func (w *writer) writeFunctionCall(expr *ast.Expr, comments []*ast.Comment) []*a
647
655
comments = w .writeTerm (terms [2 ], comments )
648
656
return comments
649
657
}
658
+ // NOTE(Trolloldem): in this point we are operating with a built-in function with the
659
+ // wrong arity even when the assignment notation is used
660
+ w .errs = append (w .errs , ArityFormatMismatchError (terms [1 :], terms [0 ].String (), terms [0 ].Location , bi .Decl ))
650
661
return w .writeFunctionCallPlain (terms , comments )
651
662
}
652
663
@@ -708,7 +719,7 @@ func (w *writer) writeTermParens(parens bool, term *ast.Term, comments []*ast.Co
708
719
case ast.Var :
709
720
w .write (w .formatVar (x ))
710
721
case ast.Call :
711
- comments = w .writeCall (parens , x , comments )
722
+ comments = w .writeCall (parens , x , term . Location , comments )
712
723
case fmt.Stringer :
713
724
w .write (x .String ())
714
725
}
@@ -760,7 +771,7 @@ func (w *writer) formatVar(v ast.Var) string {
760
771
return v .String ()
761
772
}
762
773
763
- func (w * writer ) writeCall (parens bool , x ast.Call , comments []* ast.Comment ) []* ast.Comment {
774
+ func (w * writer ) writeCall (parens bool , x ast.Call , loc * ast. Location , comments []* ast.Comment ) []* ast.Comment {
764
775
bi , ok := ast .BuiltinMap [x [0 ].String ()]
765
776
if ! ok || bi .Infix == "" {
766
777
return w .writeFunctionCallPlain (x , comments )
@@ -769,13 +780,22 @@ func (w *writer) writeCall(parens bool, x ast.Call, comments []*ast.Comment) []*
769
780
if bi .Infix == "in" {
770
781
// NOTE(sr): `in` requires special handling, mirroring what happens in the parser,
771
782
// since there can be one or two lhs arguments.
772
- return w .writeInOperator (true , x [1 :], comments )
783
+ return w .writeInOperator (true , x [1 :], comments , loc , bi . Decl )
773
784
}
774
785
775
786
// TODO(tsandall): improve to consider precedence?
776
787
if parens {
777
788
w .write ("(" )
778
789
}
790
+
791
+ // NOTE(Trolloldem): writeCall is only invoked when the function call is a term
792
+ // of another function. The only valid arity is the one of the
793
+ // built-in function
794
+ if len (bi .Decl .Args ()) != len (x )- 1 {
795
+ w .errs = append (w .errs , ArityFormatMismatchError (x [1 :], x [0 ].String (), loc , bi .Decl ))
796
+ return comments
797
+ }
798
+
779
799
comments = w .writeTermParens (true , x [1 ], comments )
780
800
w .write (" " + bi .Infix + " " )
781
801
comments = w .writeTermParens (true , x [2 ], comments )
@@ -786,7 +806,16 @@ func (w *writer) writeCall(parens bool, x ast.Call, comments []*ast.Comment) []*
786
806
return comments
787
807
}
788
808
789
- func (w * writer ) writeInOperator (parens bool , operands []* ast.Term , comments []* ast.Comment ) []* ast.Comment {
809
+ func (w * writer ) writeInOperator (parens bool , operands []* ast.Term , comments []* ast.Comment , loc * ast.Location , f * types.Function ) []* ast.Comment {
810
+ if len (operands ) != len (f .Args ()) {
811
+ // The number of operands does not math the arity of the `in` operator
812
+ operator := ast .Member .Name
813
+ if len (f .Args ()) == 3 {
814
+ operator = ast .MemberWithKey .Name
815
+ }
816
+ w .errs = append (w .errs , ArityFormatMismatchError (operands , operator , loc , f ))
817
+ return comments
818
+ }
790
819
kw := "in"
791
820
switch len (operands ) {
792
821
case 2 :
@@ -1356,3 +1385,36 @@ func ensureFutureKeywordImport(imps []*ast.Import, kw string) []*ast.Import {
1356
1385
imp .Location = defaultLocation (imp )
1357
1386
return append (imps , imp )
1358
1387
}
1388
+
1389
+ // ArgErrDetail but for `fmt` checks since compiler has not run yet.
1390
+ type ArityFormatErrDetail struct {
1391
+ Have []string `json:"have"`
1392
+ Want []string `json:"want"`
1393
+ }
1394
+
1395
+ // arityMismatchError but for `fmt` checks since the compiler has not run yet.
1396
+ func ArityFormatMismatchError (operands []* ast.Term , operator string , loc * ast.Location , f * types.Function ) * ast.Error {
1397
+ want := make ([]string , len (f .Args ()))
1398
+ for i := range f .Args () {
1399
+ want [i ] = types .Sprint (f .Args ()[i ])
1400
+ }
1401
+
1402
+ have := make ([]string , len (operands ))
1403
+ for i := 0 ; i < len (operands ); i ++ {
1404
+ have [i ] = ast .TypeName (operands [i ].Value )
1405
+ }
1406
+ err := ast .NewError (ast .TypeErr , loc , "%s: %s" , operator , "arity mismatch" )
1407
+ err .Details = & ArityFormatErrDetail {
1408
+ Have : have ,
1409
+ Want : want ,
1410
+ }
1411
+ return err
1412
+ }
1413
+
1414
+ // Lines returns the string representation of the detail.
1415
+ func (d * ArityFormatErrDetail ) Lines () []string {
1416
+ return []string {
1417
+ "have: " + "(" + strings .Join (d .Have , "," ) + ")" ,
1418
+ "want: " + "(" + strings .Join (d .Want , "," ) + ")" ,
1419
+ }
1420
+ }
0 commit comments