From a0914b40fdd8f5e2d7d0ffc6fc07671847c05e12 Mon Sep 17 00:00:00 2001 From: Mingun Date: Wed, 2 Dec 2020 00:52:18 +0500 Subject: [PATCH] Fix non-exhaustive warnings with the Ast.expr.Call node Because function pointers is forbidden in the expression language from now parser won't allow anymore following constructions: - 42() - "string"() - true() - (...)() - []() Fixes the warnings (3): ``` [warn] /home/runner/work/kaitai_struct_compiler/kaitai_struct_compiler/compiler/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala:319:24: Exhaustivity analysis reached max recursion depth, not all missing cases are reported. [warn] (Please try with scalac -Ypatmat-exhaust-depth 40 or -Ypatmat-exhaust-depth off.) [warn] val fromFunc = func match { [warn] ^ [warn] /home/runner/work/kaitai_struct_compiler/kaitai_struct_compiler/compiler/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala:319:24: Exhaustivity analysis reached max recursion depth, not all missing cases are reported. [warn] (Please try with scalac -Ypatmat-exhaust-depth 40 or -Ypatmat-exhaust-depth off.) [warn] val fromFunc = func match { [warn] ^ [warn] /home/runner/work/kaitai_struct_compiler/kaitai_struct_compiler/compiler/shared/src/main/scala/io/kaitai/struct/translators/CommonMethods.scala:192:5: Exhaustivity analysis reached max recursion depth, not all missing cases are reported. [warn] (Please try with scalac -Ypatmat-exhaust-depth 40 or -Ypatmat-exhaust-depth off.) [warn] func match { [warn] ^ ``` --- .../struct/exprlang/ExpressionsSpec.scala | 59 ++++++++++++++++++- .../kaitai/struct/GraphvizClassCompiler.scala | 7 +-- .../scala/io/kaitai/struct/exprlang/Ast.scala | 12 +++- .../kaitai/struct/exprlang/Expressions.scala | 13 +++- .../struct/translators/CommonMethods.scala | 17 +++--- .../struct/translators/TypeDetector.scala | 21 +++---- 6 files changed, 97 insertions(+), 32 deletions(-) diff --git a/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala b/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala index 309630fac..debd4ff8c 100644 --- a/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala +++ b/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala @@ -391,7 +391,7 @@ class ExpressionsSpec extends AnyFunSpec { ) } - // Attribute / method call + // Attributes it("parses 123.to_s") { Expressions.parse("123.to_s") should be (Attribute(IntNum(123),identifier("to_s"))) } @@ -404,6 +404,63 @@ class ExpressionsSpec extends AnyFunSpec { Expressions.parse("foo.bar") should be (Attribute(Name(identifier("foo")),identifier("bar"))) } + // Method calls + describe("parses method") { + it("without parameters") { + Expressions.parse("foo.bar()") should be (Call(Name(identifier("foo")),identifier("bar"), Seq())) + } + + it("with parameters") { + Expressions.parse("foo.bar(42)") should be (Call(Name(identifier("foo")),identifier("bar"), Seq(IntNum(42)))) + } + + it("on strings") { + Expressions.parse("\"foo\".bar(42)") should be (Call(Str("foo"),identifier("bar"), Seq(IntNum(42)))) + Expressions.parse("'foo'.bar(42)") should be (Call(Str("foo"),identifier("bar"), Seq(IntNum(42)))) + } + + it("on booleans") { + Expressions.parse("true.bar(42)") should be (Call(Bool(true), identifier("bar"), Seq(IntNum(42)))) + Expressions.parse("false.bar(42)") should be (Call(Bool(false),identifier("bar"), Seq(IntNum(42)))) + } + + it("on integer") { + Expressions.parse("42.bar(42)") should be (Call(IntNum(42), identifier("bar"), Seq(IntNum(42)))) + } + + it("on float") { + Expressions.parse("42.0.bar(42)") should be (Call(FloatNum(42.0), identifier("bar"), Seq(IntNum(42)))) + } + + it("on array") { + Expressions.parse("[].bar(42)") should be (Call(List(Nil), identifier("bar"), Seq(IntNum(42)))) + } + + it("on slice") { + Expressions.parse("foo[1].bar(42)") should be ( + Call( + Subscript(Name(identifier("foo")), IntNum(1)), + identifier("bar"), + Seq(IntNum(42)) + ) + ) + } + + it("on group") { + Expressions.parse("(42).bar(42)") should be (Call(IntNum(42), identifier("bar"), Seq(IntNum(42)))) + } + + it("on expression") { + Expressions.parse("(1+2).bar(42)") should be ( + Call( + BinOp(IntNum(1), Add, IntNum(2)), + identifier("bar"), + Seq(IntNum(42)) + ) + ) + } + } + describe("strings") { it("single-quoted") { // \" -> \" diff --git a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala index 0e8386cb1..d3af35d4e 100644 --- a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala @@ -315,11 +315,8 @@ class GraphvizClassCompiler(classSpecs: ClassSpecs, topClass: ClassSpec) extends case _ => affectedVars(value) } - case Ast.expr.Call(func, args) => - val fromFunc = func match { - case Ast.expr.Attribute(obj: Ast.expr, methodName: Ast.identifier) => affectedVars(obj) - } - fromFunc ::: affectedVars(Ast.expr.List(args)) + case Ast.expr.Call(value, _, args) => + affectedVars(value) ::: affectedVars(Ast.expr.List(args)) case Ast.expr.Subscript(value, idx) => affectedVars(value) ++ affectedVars(idx) case SwitchType.ELSE_CONST => diff --git a/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala b/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala index 2acf1943b..9b295f0cf 100644 --- a/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala +++ b/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala @@ -71,7 +71,17 @@ object Ast { // case class Dict(keys: Seq[expr], values: Seq[expr]) extends expr /** Represents `X < Y`, `X > Y` and so on. */ case class Compare(left: expr, ops: cmpop, right: expr) extends expr - case class Call(func: expr, args: Seq[expr]) extends expr + /** + * Represents function call on some expression: + * ``` + * .() + * ``` + * + * @param obj expression on which method is called + * @param methodName method to call + * @param args method arguments + */ + case class Call(obj: expr, methodName: identifier, args: Seq[expr]) extends expr case class IntNum(n: BigInt) extends expr case class FloatNum(n: BigDecimal) extends expr case class Str(s: String) extends expr diff --git a/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala b/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala index 0ef98ce71..65e954347 100644 --- a/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala +++ b/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala @@ -141,13 +141,20 @@ object Expressions { def list_contents[$: P] = P( test.rep(1, ",") ~ ",".? ) def list[$: P] = P( list_contents ).map(Ast.expr.List(_)) - def call[$: P] = P("(" ~ arglist ~ ")").map { case (args) => (lhs: Ast.expr) => Ast.expr.Call(lhs, args)} + def call[$: P] = P("(" ~ arglist ~ ")") def slice[$: P] = P("[" ~ test ~ "]").map { case (args) => (lhs: Ast.expr) => Ast.expr.Subscript(lhs, args)} def cast[$: P] = P( "." ~ "as" ~ "<" ~ TYPE_NAME ~ ">" ).map( typeName => (lhs: Ast.expr) => Ast.expr.CastToType(lhs, typeName) ) - def attr[$: P] = P("." ~ NAME).map(id => (lhs: Ast.expr) => Ast.expr.Attribute(lhs, id)) - def trailer[$: P]: P[Ast.expr => Ast.expr] = P( call | slice | cast | attr ) + // Returns function that accept lsh expression and returns Attribute or Call + // node depending on existence of parameters + def attr[$: P] = P("." ~ NAME ~ call.?).map { + case (id, args) => (lhs: Ast.expr) => args match { + case Some(args) => Ast.expr.Call(lhs, id, args) + case None => Ast.expr.Attribute(lhs, id) + } + } + def trailer[$: P]: P[Ast.expr => Ast.expr] = P( slice | cast | attr ) def exprlist[$: P]: P[Seq[Ast.expr]] = P( expr.rep(1, sep = ",") ~ ",".? ) def testlist[$: P]: P[Seq[Ast.expr]] = P( test.rep(1, sep = ",") ~ ",".? ) diff --git a/shared/src/main/scala/io/kaitai/struct/translators/CommonMethods.scala b/shared/src/main/scala/io/kaitai/struct/translators/CommonMethods.scala index 6b177d35e..2f39e5058 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/CommonMethods.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/CommonMethods.scala @@ -198,18 +198,15 @@ abstract trait CommonMethods[T] extends TypeDetector { * @return result of translation as [[T]] */ def translateCall(call: Ast.expr.Call): T = { - val func = call.func + val obj = call.obj val args = call.args - func match { - case Ast.expr.Attribute(obj: Ast.expr, methodName: Ast.identifier) => - val objType = detectType(obj) - MethodArgType.byDataType(objType) match { - case Some(argType) => - invokeMethod(argType, methodName.name, obj, args) - case None => - throw new MethodNotFoundError(methodName.name, objType) - } + val objType = detectType(obj) + MethodArgType.byDataType(objType) match { + case Some(argType) => + invokeMethod(argType, call.methodName.name, obj, args) + case None => + throw new MethodNotFoundError(call.methodName.name, objType) } } diff --git a/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala b/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala index 49cf61ef2..878e2e45c 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala @@ -238,23 +238,20 @@ class TypeDetector(provider: TypeProvider) { /** * Detects resulting data type of a given function call expression. Typical function * call expression in KSY is `foo.bar(arg1, arg2)`, which is represented in AST as - * `Call(Attribute(foo, bar), Seq(arg1, arg2))`. + * `Call(foo, bar, Seq(arg1, arg2))`. * @note Must be kept in sync with [[CommonMethods.translateCall]] * @param call function call expression * @return data type */ def detectCallType(call: Ast.expr.Call): DataType = { - call.func match { - case Ast.expr.Attribute(obj: Ast.expr, methodName: Ast.identifier) => - val objType = detectType(obj) - // TODO: check number and type of arguments in `call.args` - (objType, methodName.name) match { - case (_: StrType, "substring") => CalcStrType - case (_: StrType, "to_i") => CalcIntType - case (_: BytesType, "to_s") => CalcStrType - case _ => - throw new MethodNotFoundError(methodName.name, objType) - } + val objType = detectType(call.obj) + // TODO: check number and type of arguments in `call.args` + (objType, call.methodName.name) match { + case (_: StrType, "substring") => CalcStrType + case (_: StrType, "to_i") => CalcIntType + case (_: BytesType, "to_s") => CalcStrType + case _ => + throw new MethodNotFoundError(call.methodName.name, objType) } }