-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix subtyping in dict service #7115
Open
Diamekod0221
wants to merge
13
commits into
staging
Choose a base branch
from
fix_subtyping_in_DictService
base: staging
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
d7893c6
remove unnecessary nesting in DictApiEndopoints
d5796f3
Fixed improper subtyping in CanBeSubclassDeterminer
7f14f49
regenerated openapi
1d5ce0c
Changed type in UIProcess
9bd74d5
extract isAssignable
28ff753
Added CanBeSubclassDeterminerSpec
a881083
comment fixup
108a345
Fix decimal typing in SubclassDeterminerSpec
d073fd1
Fix numerical typing in SubclassDeterminerSpec
2e50536
CanBeSubclassDeterminerSpec fixes
50c59b5
Extracted ConversionDeterminer and SubclassDeterminer
81d10a0
Cleaned up tests and naming
43706dc
TypingResultSpec fix
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
83 changes: 83 additions & 0 deletions
83
...onents-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/ConversionDeterminer.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package pl.touk.nussknacker.engine.api.typed | ||
|
||
import cats.data.Validated.condNel | ||
import cats.data.{NonEmptyList, Validated, ValidatedNel} | ||
import cats.implicits.catsSyntaxValidatedId | ||
import org.apache.commons.lang3.ClassUtils | ||
import pl.touk.nussknacker.engine.api.typed.supertype.NumberTypesPromotionStrategy.AllNumbers | ||
import pl.touk.nussknacker.engine.api.typed.typing.{ | ||
SingleTypingResult, | ||
TypedClass, | ||
TypedNull, | ||
TypedObjectWithValue, | ||
TypedUnion, | ||
TypingResult, | ||
Unknown | ||
} | ||
|
||
trait ConversionDeterminer { | ||
|
||
def singleCanBeConvertedTo( | ||
result: typing.SingleTypingResult, | ||
result1: typing.SingleTypingResult | ||
): ValidatedNel[String, Unit] | ||
|
||
/** | ||
* This method checks if `givenType` can by subclass of `superclassCandidate` | ||
* It will return true if `givenType` is equals to `superclassCandidate` or `givenType` "extends" `superclassCandidate` | ||
*/ | ||
def canBeConvertedTo(givenType: TypingResult, superclassCandidate: TypingResult): ValidatedNel[String, Unit] = { | ||
(givenType, superclassCandidate) match { | ||
case (_, Unknown) => ().validNel | ||
case (Unknown, _) => ().validNel | ||
case (TypedNull, other) => canNullBeConvertedTo(other) | ||
case (_, TypedNull) => s"No type can be subclass of ${TypedNull.display}".invalidNel | ||
case (given: SingleTypingResult, superclass: TypedUnion) => | ||
canBeConvertedTo(NonEmptyList.one(given), superclass.possibleTypes) | ||
case (given: TypedUnion, superclass: SingleTypingResult) => | ||
canBeConvertedTo(given.possibleTypes, NonEmptyList.one(superclass)) | ||
case (given: SingleTypingResult, superclass: SingleTypingResult) => singleCanBeConvertedTo(given, superclass) | ||
case (given: TypedUnion, superclass: TypedUnion) => | ||
canBeConvertedTo(given.possibleTypes, superclass.possibleTypes) | ||
} | ||
} | ||
|
||
private def canNullBeConvertedTo(result: TypingResult): ValidatedNel[String, Unit] = result match { | ||
// TODO: Null should not be subclass of typed map that has all values assigned. | ||
case TypedObjectWithValue(_, _) => s"${TypedNull.display} cannot be subclass of type with value".invalidNel | ||
case _ => ().validNel | ||
} | ||
|
||
def canBeConvertedTo( | ||
givenTypes: NonEmptyList[SingleTypingResult], | ||
superclassCandidates: NonEmptyList[SingleTypingResult] | ||
): ValidatedNel[String, Unit] = { | ||
// Would be more safety to do givenTypes.forAll(... superclassCandidates.exists ...) - we wil protect against | ||
// e.g. (String | Int).canBeSubclassOf(String) which can fail in runtime for Int, but on the other hand we can't block user's intended action. | ||
// He/she could be sure that in this type, only String will appear. He/she also can't easily downcast (String | Int) to String so leaving here | ||
// "double exists" looks like a good tradeoff | ||
condNel( | ||
givenTypes.exists(given => superclassCandidates.exists(singleCanBeConvertedTo(given, _).isValid)), | ||
(), | ||
s"""None of the following types: | ||
|${givenTypes.map(" - " + _.display).toList.mkString(",\n")} | ||
|can be a subclass of any of: | ||
|${superclassCandidates.map(" - " + _.display).toList.mkString(",\n")}""".stripMargin | ||
) | ||
} | ||
|
||
def isStrictSubclass(givenClass: TypedClass, givenSuperclass: TypedClass): Validated[NonEmptyList[String], Unit] = { | ||
condNel( | ||
givenClass == givenSuperclass, | ||
(), | ||
f"${givenClass.display} and ${givenSuperclass.display} are not the same" | ||
) orElse | ||
condNel( | ||
isAssignable(givenClass.klass, givenSuperclass.klass), | ||
(), | ||
s"${givenClass.klass} is not assignable from ${givenSuperclass.klass}" | ||
) | ||
} | ||
|
||
def isAssignable(from: Class[_], to: Class[_]): Boolean | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/SubclassDeterminer.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package pl.touk.nussknacker.engine.api.typed | ||
|
||
import cats.data.Validated.condNel | ||
import cats.data.ValidatedNel | ||
import org.apache.commons.lang3.ClassUtils | ||
import pl.touk.nussknacker.engine.api.typed.supertype.NumberTypesPromotionStrategy.AllNumbers | ||
import pl.touk.nussknacker.engine.api.typed.typing.{SingleTypingResult, TypedClass, TypingResult} | ||
|
||
object SubclassDeterminer extends ConversionDeterminer { | ||
|
||
def singleCanBeConvertedTo( | ||
givenType: SingleTypingResult, | ||
superclassCandidate: SingleTypingResult | ||
): ValidatedNel[String, Unit] = { | ||
val givenClass = givenType.runtimeObjType | ||
val givenSuperclas = superclassCandidate.runtimeObjType | ||
|
||
isStrictSubclass(givenClass, givenSuperclas) | ||
} | ||
|
||
def canBeStrictSubclassOf(givenType: TypingResult, superclassCandidate: TypingResult): ValidatedNel[String, Unit] = { | ||
this.canBeConvertedTo(givenType, superclassCandidate) | ||
} | ||
|
||
override def isAssignable(from: Class[_], to: Class[_]): Boolean = { | ||
(from, to) match { | ||
case (f, t) if ClassUtils.isAssignable(f, t, true) => true | ||
// Number double check by hand because lang3 can incorrectly throw false when dealing with java types | ||
case (f, t) if AllNumbers.contains(f) && AllNumbers.contains(t) => | ||
AllNumbers.indexOf(f) >= AllNumbers.indexOf(t) | ||
case _ => false | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Treating specially implicit conversion for floats is not a way to go, we should have option to check can be subclass without conversion in general
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify: we already treat floats specially, the condition here is not changed:
if
is replaced with pattern matching, the condition is extracted to separatedef
above.The only change here is in
if isDecimalNumber
section:that checks
boxedGivenClass
with all possible supertypes (I guess...)replaced with
that checks
boxedGivenClass
with exact given supertype. Aaaand that possibly collides with thejava.math.BigDecimal is quite often returned as a wrapper for all kind of numbers
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, apart from this isAssignable(boxedGivenClass,...) the logic is exactly the same. To clarify, I think the current staging logic is buggy. It only checks whether a givenType CAN be converted, not if the conversion is to the givenSuperclass. Apart from that I've only included a fallback in case lang3 throws a false false, because the lib gives errors when it comes to decimal java types. In general the program execution flow is the same as intended before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have moved this logic to a separate method and restored the previous logic in the handleNumberConversions method