-
Notifications
You must be signed in to change notification settings - Fork 2
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
Handle compare_by
option
#154
Conversation
mc-java-comparable/src/main/kotlin/io/spine/tools/mc/java/comparable/AddCompareToMethod.kt
Outdated
Show resolved
Hide resolved
@armiol @alexander-yevsyukov PTAL Also, I suggest overriding |
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.
@yevhenii-nadtochii please see my comments so far. I will continue the review in IDE, as it's difficult to follow via GitHub.
* ``` | ||
* | ||
* Please note the line breaks were added manually for reader's convenience. | ||
* The builder doesn't add them. It will be a single line of text. |
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.
After the code is automatically formatted by PSI, will it still be a single line of text?
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 see in integration tests output that the comparator remains unformatted.
This comment relates to this particular builder so that no one expected build(): String
to return a string as from the KDoc snippet.
* The builder doesn't add them. It will be a single line of text. | ||
* | ||
* @param cls The message class to be used as comparator's generic parameter. | ||
* @param descending If true, the default order is reversed. |
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.
Which order is a "default" one?
* The builder doesn't add them. It will be a single line of text. | ||
* | ||
* @param cls The message class to be used as comparator's generic parameter. | ||
* @param descending If true, the default order is reversed. |
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 am not sure, but if you explain "descending" as "in a reversed order", maybe the parameter should be called something like reversed
?
And honestly, "descending" is more about sorting to me.
mc-java-base/src/main/kotlin/io/spine/tools/mc/java/base/FieldPathExts.kt
Show resolved
Hide resolved
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.
Please see my comments.
val currentField = message.field(fieldPath.root) | ||
val remainingFields = fieldPath.fieldNameList.drop(1) | ||
val remainingPath = fieldPath { fieldName.addAll(remainingFields) } | ||
val nextMessage = findMessage(currentField.type.message)!!.first |
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.
What if the referenced field is not of message type? I believe we need to tell about such an error.
* The function returns a non-`null` result if a class denoted by this [ClassName] | ||
* is present on the classpath. | ||
*/ | ||
public fun MessageType.javaClass(accordingTo: ProtoFileHeader): Class<*>? { |
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.
This extension should go to ProtoData too.
} | ||
|
||
else -> error( | ||
"The field `$path` has an unrecognized Proto field type: `$type`. " + |
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.
We need to standardize on capitalization of the word "proto". I think in the short form it should go in lowercase, while "Protobuf" is capitalized. This is the way Google use it in their sources, from what I remember.
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.
Also, I'm not sure about "unrecognized". We do recognize the type because it's in the source code, descriptors, and is (at least) proto-compilable. Otherwise, we wouldn't have got this far.
But this type isn't probably "comparable" because it's a list, or a map.
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.
This unrecognized
is a stub error thrown when ast.Type
has an unknown kind
. It is not about a non-comparable type.
kind
is represented by oneof
, which has nothing to do with Kotlin's when
(as for example, with sealed
classes):
message Type {
oneof kind {
option (is_required) = true;
TypeName message = 1;
TypeName enumeration = 2;
PrimitiveType primitive = 3;
}
}
So, we have to provide else
branch in case some new kind
appears.
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.
OK. I get it. The problem it is buried inside extensions, and is not obvious from the branching here. Hence, my comment.
mc-java-comparable/src/main/kotlin/io/spine/tools/mc/java/comparable/action/AddComparator.kt
Show resolved
Hide resolved
|
||
else -> error( | ||
"The field `$path` has an unrecognized Proto field type: `$type`. " + | ||
"The supported Proto fields: primitives, enums and messages." |
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.
Suppose you're the person who gets this error message in console. What it would take to figure out the root cause of the error. Let's sum what we see in the diag message:
- "Unrecognized type".
- It's not "supported".
- Supported types are primitives, enums, and messages. Please notice Oxford comma in the enumeration.
- Stacktrace having "comparable".
In the end the combination above would give the reader idea that the field isn't probably comparable.
But can we make this path to the conclusion a bit shorter?
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.
Discussed the improved wording for the error message:
The field `$path` declared in the message type `$messageType`
is not of the supported type (`$fieldType`) for the comparison.
Supported field types are: primitives, enums, and messages.
* Adds the comparison [field] to this [ComparatorBuilder]. | ||
* | ||
* The requirements to the comparison fields are described in docs to [CompareByOption] | ||
* option in detail. |
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.
The reference to documentation details should probably be a part of the error diagnostics I mentioned above.
when { | ||
hasCompareByOption -> { | ||
check(fromRegistry == null) { | ||
"The type of `${path.joined}` field must either have `compare_by` options OR " + |
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.
This message is good!
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.
Please also consider my comment about braces around the option name here.
|
||
fromRegistry != null -> { | ||
val className = "${clazz.canonicalName}.class" | ||
val comparator = "io.spine.compare.ComparatorRegistry.get($className)" |
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.
Can we use the reference
extension to the ComparatorRegistry
class here? This way finding usages would pick up this dependency.
...ava-comparable/src/main/kotlin/io/spine/tools/mc/java/comparable/action/ComparatorBuilder.kt
Show resolved
Hide resolved
...ava-comparable/src/test/java/io/spine/tools/mc/java/comparable/tests/env/LocalDateTimes.java
Show resolved
Hide resolved
@armiol @alexander-yevsyukov PTAL I will try bringing typed expressions to |
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.
LGTM with a couple of comments regarding diag. messages to consider before merging.
|
||
is MessageComparisonField -> { | ||
check(field.type.hasCompareByOption) { | ||
"The type of `${path.joined}` field should have `compare_by` option itself " + |
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.
We usually put the option in round braces when mentioning in the docs or diagnostic messages. I don't know who started this, and we probably don't have it standardized in our guidelines. But (compare_by)
looks more visible and is clearly different to a field.
Also, the definite article should be probably put before the name.
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.
@armiol, for your attention.
when { | ||
hasCompareByOption -> { | ||
check(fromRegistry == null) { | ||
"The type of `${path.joined}` field must either have `compare_by` options OR " + |
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.
Please also consider my comment about braces around the option name here.
private fun unsupportedFieldType(fieldPath: FieldPath, fieldType: Any?): Nothing = error( | ||
"The field `$fieldPath` declared in the message `$type` is not of the supported type " + | ||
" (`$fieldType`) for the comparison. Supported field types are: " + | ||
"primitives, enums, and comparable messages. Checks out docs to `compare_by` " + |
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.
Please consider braces around the option name in this message.
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.
@yevhenii-nadtochii LGTM.
However, please see my comment. Maybe, that's something we need to discuss.
|
||
@Nested | ||
inner class | ||
`not generate comparator` { |
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.
What do we think of oneof
?
It's now in a gray zone. I would either have tests explicitly covering our support or our refusal to support the comparison by oneof
fields.
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've added the negative tests to cover oneof
. Its support was not declared.
This PR adds support for
Comparable
messages (#152).Now, we have
mc-java-comparable
plugin, which discovers messages withcompare_by
option and generates code to make them comparable.The following render actions are applied:
ImplementComparable
– addsComparable<...>
interface to the message.AddComparator
– adds aprivate static field
containing theComparator<...>
itself.AddCompareTo
– adds an overridden method accordingly to the interface contract.External messages
An external message is a message, which is not being generated by the current codegen session. Usually, they come from the dependencies. For example,
google.protobuf.Timestamp
. When a user wants to use a field in comparison, the type of which is external, a comparator should be provided within the ComparatorRegistry. By default, the registry provides comparators forTimestamp
andDuration
only.If the external message has
compare_by
option (our messages, generated bymc-java
as well), then the presence of comparator is not required. Though, the plugin will throw if there is the option and comparator in the registry simultaneously.Concerns
AddComparator
: this is the smartest action. It performs analysis of Proto messages, indeed. I'm not sure if it should be done in an entity ofDirectMessageAction
type.ComparableActionsRenderer
: can we store actions in one place? Our model doesn't specify a distinct set of actions for each message, and probably wouldn't. We carry the same collection of action classes in each discovered message. One could think that each message may have its own set of actions.Other changes
implementsInterface
assertion is stricter now, requiring the asserted interface to be located before an opening brace of the class body.