diff --git a/docs/Changelog.md b/docs/Changelog.md index cb8cd55f697..c3ecf6957c5 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -58,7 +58,6 @@ * [#7364](https://github.com/TouK/nussknacker/pull/7364) PeriodicDeploymentManger is no longer a separate DM, but instead is an optional functionality and decorator for all DMs * in order to use it, DM must implement interface `schedulingSupported`, that handles deployments on a specific engine * implementation provided for Flink DM -* [#7443](https://github.com/TouK/nussknacker/pull/7443) Indexing on record is more similar to indexing on map. The change lets us access record values dynamically. For example now spel expression "{a: 5, b: 10}[#input.field]" compiles and has type "Integer" inferred from types of values of the record. This lets us access record value based on user input, for instance if user passes "{"field": "b"}" to scenario we will get value "10", whereas input {"field": "c"} would result in "null". Expression "{a: 5}["b"]" still does not compile because it is known at compile time that record does not have property "b". * [#7335](https://github.com/TouK/nussknacker/pull/7335) introduced `managersDirs` config to configure deployment managers directory paths (you can use `MANAGERS_DIR` env in case of docker-based deployments). The default is `./managers`. ## 1.18 diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpressionParseError.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpressionParseError.scala index 5182bd6bae6..49b27a7f186 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpressionParseError.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpressionParseError.scala @@ -106,10 +106,6 @@ object SpelExpressionParseError { override def message: String = s"There is no property '$property' in type: ${typ.display}" } - case class NoPropertyTypeError(typ: TypingResult, propertyType: TypingResult) extends MissingObjectError { - override def message: String = s"There is no property of type '${propertyType.display}' in type: ${typ.display}" - } - case class UnknownMethodError(methodName: String, displayableType: String) extends MissingObjectError { override def message: String = s"Unknown method '$methodName' in $displayableType" } diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/Typer.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/Typer.scala index 995a8b6afe6..94398a81cd6 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/Typer.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/Typer.scala @@ -26,7 +26,6 @@ import pl.touk.nussknacker.engine.spel.SpelExpressionParseError.IllegalOperation import pl.touk.nussknacker.engine.spel.SpelExpressionParseError.MissingObjectError.{ ConstructionOfUnknown, NoPropertyError, - NoPropertyTypeError, NonReferenceError, UnresolvedReferenceError } @@ -200,16 +199,11 @@ private[spel] class Typer( case _ => typeFieldNameReferenceOnRecord(indexString, record) } case indexKey :: Nil if indexKey.canBeConvertedTo(Typed[String]) => - if (dynamicPropertyAccessAllowed) valid(Unknown) - else - record.runtimeObjType.params match { - case _ :: value :: Nil if record.runtimeObjType.klass == classOf[java.util.Map[_, _]] => valid(value) - case _ => valid(Unknown) - } - case e :: Nil => + if (dynamicPropertyAccessAllowed) valid(Unknown) else invalid(DynamicPropertyAccessError) + case _ :: Nil => indexer.children match { case (ref: PropertyOrFieldReference) :: Nil => typeFieldNameReferenceOnRecord(ref.getName, record) - case _ => if (dynamicPropertyAccessAllowed) valid(Unknown) else invalid(NoPropertyTypeError(record, e)) + case _ => if (dynamicPropertyAccessAllowed) valid(Unknown) else invalid(DynamicPropertyAccessError) } case _ => invalid(IllegalIndexingOperation) diff --git a/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSpec.scala b/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSpec.scala index e25f05e9ca1..6291e7c8ff4 100644 --- a/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSpec.scala +++ b/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSpec.scala @@ -272,18 +272,6 @@ class SpelExpressionSpec extends AnyFunSuite with Matchers with ValidatedValuesD private def evaluate[T: TypeTag](expr: String, context: Context = ctx): T = parse[T](expr = expr, context = context).validExpression.evaluateSync[T](context) - test("should be able to dynamically index record") { - evaluate[Int]("{a: 5, b: 10}[#input.toString()]", Context("abc").withVariable("input", "a")) shouldBe 5 - evaluate[Integer]("{a: 5, b: 10}[#input.toString()]", Context("abc").withVariable("input", "asdf")) shouldBe null - } - - test("should figure out result type when dynamically indexing record") { - evaluate[Int]( - "{a: {g: 5, h: 10}, b: {g: 50, h: 100}}[#input.toString()].h", - Context("abc").withVariable("input", "b") - ) shouldBe 100 - } - test("parsing first selection on array") { parse[Any]("{1,2,3,4,5,6,7,8,9,10}.^[(#this%2==0)]").validExpression .evaluateSync[java.util.ArrayList[Int]](ctx) should equal(2) diff --git a/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/TyperSpec.scala b/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/TyperSpec.scala index 9da82310d9f..f69278af040 100644 --- a/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/TyperSpec.scala +++ b/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/TyperSpec.scala @@ -13,10 +13,8 @@ import pl.touk.nussknacker.engine.api.typed.typing._ import pl.touk.nussknacker.engine.definition.clazz.ClassDefinitionTestUtils import pl.touk.nussknacker.engine.dict.{KeysDictTyper, SimpleDictRegistry} import pl.touk.nussknacker.engine.expression.PositionRange -import pl.touk.nussknacker.engine.spel.SpelExpressionParseError.MissingObjectError.{ - NoPropertyError, - NoPropertyTypeError -} +import pl.touk.nussknacker.engine.spel.SpelExpressionParseError.IllegalOperationError.DynamicPropertyAccessError +import pl.touk.nussknacker.engine.spel.SpelExpressionParseError.MissingObjectError.NoPropertyError import pl.touk.nussknacker.engine.spel.SpelExpressionParseError.UnsupportedOperationError.MapWithExpressionKeysError import pl.touk.nussknacker.engine.spel.Typer.TypingResultWithContext import pl.touk.nussknacker.engine.spel.TyperSpecTestData.TestRecord._ @@ -164,8 +162,10 @@ class TyperSpec extends AnyFunSuite with Matchers with ValidatedValuesDetailedMe typeExpression(s"$testRecordExpr[#var]", "var" -> s"$nonPresentKey").invalidValue.toList should matchPattern { case NoPropertyError(typingResult, key) :: Nil if typingResult == testRecordTyped && key == nonPresentKey => } + // TODO: this behavior is to be fixed - ideally this should behave the same as above typeExpression(s"$testRecordExpr[$nonPresentKey]").invalidValue.toList should matchPattern { - case NoPropertyError(typingResult, key) :: Nil if typingResult == testRecordTyped && key == nonPresentKey => + case NoPropertyError(typingResult, key) :: DynamicPropertyAccessError :: Nil + if typingResult == testRecordTyped && key == nonPresentKey => } } @@ -183,17 +183,6 @@ class TyperSpec extends AnyFunSuite with Matchers with ValidatedValuesDetailedMe } } - test("indexing on records with key which is not known at compile time treats record as map") { - typeExpression("{a: 5, b: 10}[#var.toString()]", "var" -> "a").validValue.finalResult.typingResult shouldBe Typed - .typedClass[Int] - } - - test("indexing on records with non string key produces error") { - typeExpression("{a: 5, b: 10}[4]").invalidValue.toList should matchPattern { - case NoPropertyTypeError(_, _) :: Nil => - } - } - private def buildTyper(dynamicPropertyAccessAllowed: Boolean = false) = new Typer( dictTyper = new KeysDictTyper(new SimpleDictRegistry(Map.empty)), strictMethodsChecking = false,