Skip to content

Commit 07c2a2a

Browse files
committed
catch a bunch of bad casts and incomplete matches, and put in proper error messages
1 parent 167206a commit 07c2a2a

File tree

4 files changed

+95
-27
lines changed

4 files changed

+95
-27
lines changed

sjsonnet/src/sjsonnet/Evaluator.scala

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class Evaluator(parseCache: collection.mutable.Map[String, fastparse.Parsed[Expr
5353

5454

5555
case BinaryOp(_, lhs, Expr.BinaryOp.`in`, Super(offset)) =>
56-
val key = visitExpr(lhs, scope).asInstanceOf[Val.Str]
56+
val key = visitExpr(lhs, scope).cast[Val.Str]
5757
Val.bool(scope.super0.get.value0.contains(key.value))
5858

5959
case $(offset) => scope.dollar
@@ -85,7 +85,7 @@ class Evaluator(parseCache: collection.mutable.Map[String, fastparse.Parsed[Expr
8585
case None => Evaluator.fail("Assertion failed", scope.currentFile, offset, wd)
8686
case Some(msg) =>
8787
Evaluator.fail(
88-
"Assertion failed: " + visitExpr(msg, scope).asInstanceOf[Val.Str].value,
88+
"Assertion failed: " + visitExpr(msg, scope).cast[Val.Str].value,
8989
scope.currentFile,
9090
offset,
9191
wd
@@ -114,7 +114,7 @@ class Evaluator(parseCache: collection.mutable.Map[String, fastparse.Parsed[Expr
114114
)
115115
case Apply(offset, value, Args(args)) =>
116116
val lhs = visitExpr(value, scope)
117-
try lhs.asInstanceOf[Val.Func].apply(
117+
try lhs.cast[Val.Func].apply(
118118
args.map{case (k, v) => (k, Lazy(visitExpr(v, scope)))},
119119
scope.currentFile.last,
120120
extVars,
@@ -143,7 +143,7 @@ class Evaluator(parseCache: collection.mutable.Map[String, fastparse.Parsed[Expr
143143

144144
case Lookup(offset, value, index) =>
145145
if (value.isInstanceOf[Super]){
146-
val key = visitExpr(index, scope).asInstanceOf[Val.Str]
146+
val key = visitExpr(index, scope).cast[Val.Str]
147147
scope.super0.get.value(key.value, scope.currentFile, scope.currentRoot, offset, wd, extVars).force
148148
}else (visitExpr(value, scope), visitExpr(index, scope)) match {
149149
case (v: Val.Arr, i: Val.Num) =>
@@ -165,22 +165,30 @@ class Evaluator(parseCache: collection.mutable.Map[String, fastparse.Parsed[Expr
165165
visitExpr(value, scope) match{
166166
case Val.Arr(a) =>
167167

168-
val range = start.fold(0)(visitExpr(_, scope).asInstanceOf[Val.Num].value.toInt) until end.fold(a.length)(visitExpr(_, scope).asInstanceOf[Val.Num].value.toInt) by stride.fold(1)(visitExpr(_, scope).asInstanceOf[Val.Num].value.toInt)
168+
val range =
169+
start.fold(0)(visitExpr(_, scope).cast[Val.Num].value.toInt) until
170+
end.fold(a.length)(visitExpr(_, scope).cast[Val.Num].value.toInt) by
171+
stride.fold(1)(visitExpr(_, scope).cast[Val.Num].value.toInt)
169172
Val.Arr(range.dropWhile(_ < 0).takeWhile(_ < a.length).map(a))
170173
case Val.Str(s) =>
171-
val range = start.fold(0)(visitExpr(_, scope).asInstanceOf[Val.Num].value.toInt) until end.fold(s.length)(visitExpr(_, scope).asInstanceOf[Val.Num].value.toInt) by stride.fold(1)(visitExpr(_, scope).asInstanceOf[Val.Num].value.toInt)
174+
val range =
175+
start.fold(0)(visitExpr(_, scope).cast[Val.Num].value.toInt) until
176+
end.fold(s.length)(visitExpr(_, scope).cast[Val.Num].value.toInt) by
177+
stride.fold(1)(visitExpr(_, scope).cast[Val.Num].value.toInt)
172178
Val.Str(range.dropWhile(_ < 0).takeWhile(_ < s.length).map(s).mkString)
179+
case x => Evaluator.fail("Can only slice array or string, not " + x.prettyName, scope.currentFile, offset, wd)
173180
}
174181
case Function(offset, params, body) => visitMethod(scope, body, params, offset)
175182
case IfElse(offset, cond, then, else0) =>
176183
visitExpr(cond, scope) match{
177184
case Val.True => visitExpr(then, scope)
178185
case Val.False => else0.fold[Val](Val.Null)(visitExpr(_, scope))
186+
case v => Evaluator.fail("Need boolean, found " + v.prettyName, scope.currentFile, offset, wd)
179187
}
180188
case Comp(offset, value, first, rest) =>
181189
Val.Arr(visitComp(first :: rest.toList, Seq(scope)).map(s => Lazy(visitExpr(value, s))))
182190
case ObjExtend(offset, value, ext) => {
183-
val original = visitExpr(value, scope).asInstanceOf[Val.Obj]
191+
val original = visitExpr(value, scope).cast[Val.Obj]
184192
val extension = visitObjBody(ext, scope)
185193
Evaluator.mergeObjects(original, extension)
186194
}
@@ -349,7 +357,7 @@ class Evaluator(parseCache: collection.mutable.Map[String, fastparse.Parsed[Expr
349357
case None => Evaluator.fail("Assertion failed", scope.currentFile, value.offset, wd)
350358
case Some(msg) =>
351359
Evaluator.fail(
352-
"Assertion failed: " + visitExpr(msg, newScope).asInstanceOf[Val.Str].value,
360+
"Assertion failed: " + visitExpr(msg, newScope).cast[Val.Str].value,
353361
scope.currentFile,
354362
value.offset,
355363
wd

sjsonnet/src/sjsonnet/Format.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ object Format{
9494
case _ =>
9595

9696
val value = formatted.label match{
97-
case None => Materializer(values.asInstanceOf[Val.Arr].value(i).force, extVars, wd)
97+
case None => Materializer(values.cast[Val.Arr].value(i).force, extVars, wd)
9898
case Some(key) =>
9999
values match{
100100
case v: Val.Arr => Materializer(v.value(i).force, extVars, wd)

sjsonnet/src/sjsonnet/Std.scala

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,15 @@ object Std {
129129
}
130130
},
131131
builtin("codepoint", "str"){ (wd, extVars, v1: Val) =>
132-
v1.asInstanceOf[Val.Str].value.charAt(0).toInt
132+
v1.cast[Val.Str].value.charAt(0).toInt
133133
},
134134
builtin("length", "x"){ (wd, extVars, v1: Val) =>
135135
v1 match{
136136
case Val.Str(s) => s.length
137137
case Val.Arr(s) => s.length
138138
case o: Val.Obj => o.getVisibleKeys().count(!_._2)
139139
case o: Val.Func => o.params.args.length
140+
case _ => throw new DelegateError("Cannot get length of " + v1.prettyName)
140141
}
141142
},
142143
builtin("objectHas", "o", "f"){ (wd, extVars, v1: Val.Obj, v2: String) =>
@@ -147,8 +148,7 @@ object Std {
147148
},
148149
builtin("objectFields", "o"){ (wd, extVars, v1: Val.Obj) =>
149150
Val.Arr(
150-
v1.asInstanceOf[Val.Obj]
151-
.getVisibleKeys()
151+
v1.getVisibleKeys()
152152
.collect{case (k, false) => k}
153153
.toSeq
154154
.sorted
@@ -157,8 +157,7 @@ object Std {
157157
},
158158
builtin("objectFieldsAll", "o"){ (wd, extVars, v1: Val.Obj) =>
159159
Val.Arr(
160-
v1.asInstanceOf[Val.Obj]
161-
.getVisibleKeys()
160+
v1.getVisibleKeys()
162161
.collect{case (k, _) => k}
163162
.toSeq
164163
.sorted
@@ -177,10 +176,17 @@ object Std {
177176
}
178177
},
179178
builtin("lines", "arr"){ (wd, extVars, v1: Val.Arr) =>
179+
v1.value.map(_.force).foreach{
180+
case _: Val.Str | Val.Null => // donothing
181+
case x => throw new DelegateError("Cannot call .lines on " + x.prettyName)
182+
}
180183
Materializer.apply(v1, extVars, wd).asInstanceOf[ujson.Js.Arr]
181184
.value
182185
.filter(_ != ujson.Js.Null)
183-
.map{case ujson.Js.Str(s) => s + "\n"}
186+
.map{
187+
case ujson.Js.Str(s) => s + "\n"
188+
case _ => ??? /* we ensure it's all strings above */
189+
}
184190
.mkString
185191
},
186192
builtin("format", "str", "vals"){ (wd, extVars, v1: String, v2: Val) =>
@@ -376,7 +382,16 @@ object Std {
376382
builtin("join", "sep", "arr"){ (wd, extVars, sep: Val, arr: Val.Arr) =>
377383
val res: Val = sep match{
378384
case Val.Str(s) =>
379-
Val.Str(arr.value.map(_.force).filter(_ != Val.Null).map{case Val.Str(x) => x}.mkString(s))
385+
Val.Str(
386+
arr.value
387+
.map(_.force)
388+
.filter(_ != Val.Null)
389+
.map{
390+
case Val.Str(x) => x
391+
case x => throw new DelegateError("Cannot join " + x.prettyName)
392+
}
393+
.mkString(s)
394+
)
380395
case Val.Arr(sep) =>
381396
val out = collection.mutable.Buffer.empty[Lazy]
382397
for(x <- arr.value){
@@ -385,9 +400,11 @@ object Std {
385400
case Val.Arr(v) =>
386401
if (out.nonEmpty) out.appendAll(sep)
387402
out.appendAll(v)
403+
case x => throw new DelegateError("Cannot join " + x.prettyName)
388404
}
389405
}
390406
Val.Arr(out)
407+
case x => throw new DelegateError("Cannot join " + x.prettyName)
391408
}
392409
res
393410
},
@@ -397,6 +414,7 @@ object Std {
397414
x.force match{
398415
case Val.Null => // do nothing
399416
case Val.Arr(v) => out.appendAll(v)
417+
case x => throw new DelegateError("Cannot call flattenArrays on " + x)
400418
}
401419
}
402420
Val.Arr(out)
@@ -406,7 +424,12 @@ object Std {
406424
def sect(x: ujson.Js.Obj) = {
407425
x.value.flatMap{
408426
case (k, ujson.Js.Str(v)) => Seq(k + " = " + v)
409-
case (k, ujson.Js.Arr(vs)) => vs.map{case ujson.Js.Str(v) => k + " = " + v}
427+
case (k, ujson.Js.Arr(vs)) =>
428+
vs.map{
429+
case ujson.Js.Str(v) => k + " = " + v
430+
case x => throw new DelegateError("Cannot call manifestIni on " + x.getClass)
431+
}
432+
case (k, x) => throw new DelegateError("Cannot call manifestIni on " + x.getClass)
410433
}
411434
}
412435
val lines = materialized.obj.get("main").fold(Iterable[String]())(x => sect(x.asInstanceOf[ujson.Js.Obj])) ++
@@ -473,11 +496,16 @@ object Std {
473496
case ujson.Js.Str(s) => s
474497
case ujson.Js.Arr(Seq(ujson.Js.Str(t), attrs: ujson.Js.Obj, children@_*)) =>
475498
tag(t)(
476-
attrs.value.map { case (k, ujson.Js.Str(v)) => attr(k) := v }.toSeq,
499+
attrs.value.map {
500+
case (k, ujson.Js.Str(v)) => attr(k) := v
501+
case (k, v) => throw new DelegateError("Cannot call manifestXmlJsonml on " + v.getClass)
502+
}.toSeq,
477503
children.map(rec)
478504
)
479505
case ujson.Js.Arr(Seq(ujson.Js.Str(t), children@_*)) =>
480506
tag(t)(children.map(rec))
507+
case x =>
508+
throw new DelegateError("Cannot call manifestXmlJsonml on " + x.getClass)
481509
}
482510
}
483511

@@ -487,7 +515,8 @@ object Std {
487515
builtin("base64", "v"){ (wd, extVars, v: Val) =>
488516
v match{
489517
case Val.Str(value) => Base64.getEncoder().encodeToString(value.getBytes)
490-
case Val.Arr(bytes) => Base64.getEncoder().encodeToString(bytes.map(_.force.asInstanceOf[Val.Num].value.toByte).toArray)
518+
case Val.Arr(bytes) => Base64.getEncoder().encodeToString(bytes.map(_.force.cast[Val.Num].value.toByte).toArray)
519+
case x => throw new DelegateError("Cannot base64 encode " + x.prettyName)
491520
}
492521
},
493522

@@ -503,14 +532,15 @@ object Std {
503532
Val.Arr(
504533

505534
if (vs.forall(_.force.isInstanceOf[Val.Str])){
506-
vs.map(_.force.asInstanceOf[Val.Str]).sortBy(_.value).map(Lazy(_))
535+
vs.map(_.force.cast[Val.Str]).sortBy(_.value).map(Lazy(_))
507536
}else if (vs.forall(_.force.isInstanceOf[Val.Num])){
508-
vs.map(_.force.asInstanceOf[Val.Num]).sortBy(_.value).map(Lazy(_))
537+
vs.map(_.force.cast[Val.Num]).sortBy(_.value).map(Lazy(_))
509538
}else {
510539
???
511540
}
512541
)
513542
case Val.Str(s) => Val.Arr(s.sorted.map(c => Lazy(Val.Str(c.toString))))
543+
case x => throw new DelegateError("Cannot sort " + x.prettyName)
514544
}
515545
},
516546
builtin("uniq", "arr"){ (wd, extVars, arr: Val.Arr) =>
@@ -527,7 +557,9 @@ object Std {
527557
vs0.map(_.asInstanceOf[ujson.Js.Str]).sortBy(_.value)
528558
}else if (vs0.forall(_.isInstanceOf[ujson.Js.Num])){
529559
vs0.map(_.asInstanceOf[ujson.Js.Num]).sortBy(_.value)
530-
}else ???
560+
}else {
561+
throw new DelegateError("Every element of the input must be of the same type, string or number")
562+
}
531563

532564
val out = collection.mutable.Buffer.empty[ujson.Js]
533565
for(v <- vs) if (out.isEmpty || out.last != v) out.append(v)
@@ -544,7 +576,9 @@ object Std {
544576
vs0.map(_.asInstanceOf[ujson.Js.Str]).sortBy(_.value)
545577
}else if (vs0.forall(_.isInstanceOf[ujson.Js.Num])){
546578
vs0.map(_.asInstanceOf[ujson.Js.Num]).sortBy(_.value)
547-
}else ???
579+
}else {
580+
throw new DelegateError("Every element of the input must be of the same type, string or number")
581+
}
548582

549583
val out = collection.mutable.Buffer.empty[ujson.Js]
550584
for(v <- vs) if (out.isEmpty || out.last != v) out.append(v)
@@ -567,7 +601,9 @@ object Std {
567601
vs0.map(_.asInstanceOf[ujson.Js.Str]).sortBy(_.value)
568602
}else if (vs0.forall(_.isInstanceOf[ujson.Js.Num])){
569603
vs0.map(_.asInstanceOf[ujson.Js.Num]).sortBy(_.value)
570-
}else ???
604+
}else {
605+
throw new DelegateError("Every element of the input must be of the same type, string or number")
606+
}
571607

572608
val out = collection.mutable.Buffer.empty[ujson.Js]
573609
for(v <- vs) if (out.isEmpty || out.last != v) out.append(v)
@@ -587,7 +623,9 @@ object Std {
587623
vs0.map(_.asInstanceOf[ujson.Js.Str]).sortBy(_.value)
588624
}else if (vs0.forall(_.isInstanceOf[ujson.Js.Num])){
589625
vs0.map(_.asInstanceOf[ujson.Js.Num]).sortBy(_.value)
590-
}else ???
626+
}else {
627+
throw new DelegateError("Every element of the input must be of the same type, string or number")
628+
}
591629

592630
val out = collection.mutable.Buffer.empty[ujson.Js]
593631
for(v <- vs) if (out.isEmpty || out.last != v) out.append(v)

sjsonnet/src/sjsonnet/Val.scala

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import ammonite.ops.Path
44
import sjsonnet.Expr.Member.Visibility
55
import sjsonnet.Expr.Params
66

7+
import scala.reflect.ClassTag
8+
79
object Lazy{
810
def apply(calc0: => Val) = new Lazy(calc0)
911
}
@@ -12,13 +14,33 @@ class Lazy(calc0: => Val){
1214
}
1315
sealed trait Val{
1416
def prettyName: String
17+
def cast[T: ClassTag: PrettyNamed] =
18+
if (implicitly[ClassTag[T]].runtimeClass.isInstance(this)) this.asInstanceOf[T]
19+
else throw new DelegateError(
20+
"Expected " + implicitly[PrettyNamed[T]].s + ", found " + prettyName
21+
)
22+
}
23+
class PrettyNamed[T](val s: String)
24+
object PrettyNamed{
25+
implicit def boolName: PrettyNamed[Val.Bool] = new PrettyNamed("boolean")
26+
implicit def nullName: PrettyNamed[Val.Null.type] = new PrettyNamed("null")
27+
implicit def strName: PrettyNamed[Val.Str] = new PrettyNamed("string")
28+
implicit def numName: PrettyNamed[Val.Num] = new PrettyNamed("number")
29+
implicit def arrName: PrettyNamed[Val.Arr] = new PrettyNamed("array")
30+
implicit def objName: PrettyNamed[Val.Obj] = new PrettyNamed("object")
31+
implicit def funName: PrettyNamed[Val.Func] = new PrettyNamed("function")
1532
}
1633
object Val{
1734
def bool(b: Boolean) = if (b) True else False
18-
case object True extends Val{
35+
sealed trait Bool extends Val{
36+
def value: Boolean
37+
}
38+
case object True extends Bool{
39+
def value = true
1940
def prettyName = "boolean"
2041
}
21-
case object False extends Val{
42+
case object False extends Bool{
43+
def value = false
2244
def prettyName = "boolean"
2345
}
2446
case object Null extends Val{

0 commit comments

Comments
 (0)