Using Kotlin Symbol Processing API to compile String expression - kotlin

I have a requirement where I need to make operation on Strings. This operation is expensive at runtime, and need to be made only on static expression. Therefore I want to do it at compile time.
Since the operation need to be made a lot of time, in many different modules, having to copy paste, doing manually the conversion, to past the result in the source code is long and slow.
I would like to add an annotation on the Strings which is required to convert, and have a file generated with all the results.
An example could be
val original = #Convert "Hello world"
functionRequiringTheConvertedStringValue(DATA.get(original))
original being equal to "Hello world" and the Data.get function giving the result.
With the DATA class being a static file containing all the string converted and able to fetch them.
Therefore I want to use an annotation processing, but when reading the examples, and the examples, I can't find a way to achieve the logic I want to implement.
I created a SymbolProcessor, but I am not able to get any expression, I can only get functions (KSFunctionDeclarationImpl) and classes (KSClassDeclarationImpl).
...
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation("com.example.annotation.Convert")
symbols.forEach {
logger.warn("class: ${it.javaClass.name}")
}
val ret = symbols.filter { !it.validate() }.toList()
return ret
}
When I use the annotation on an expression, I do not get it using the getSymbolsWithAnnotation.
My question being, how to get the expression and their value which are annotated by the annotation I created.
Edit:
Seems to be an open issued : https://github.com/google/ksp/issues/1194

Related

Kotlin Path.useLines { } - How not to get IOException("Stream closed")?

Kotlin has nice wrappers and shortcuts, but sometimes I get caught not understanding them.
I have this simplified code:
class PipeSeparatedItemsReader (private val filePath: Path) : ItemsReader {
override fun readItems(): Sequence<ItemEntry> {
return filePath.useLines { lines ->
lines.map { ItemEntry("A","B","C","D",) }
}
}
And then I have:
val itemsPath = Path(...).resolve()
val itemsReader = PipeSeparatedItemsReader(itemsPath)
for (itemEntry in itemsReader.readItems())
updateItem(itemEntry)
// I have also tried itemsReader.readItems().forEach { ... }
Which is quite straightforward - I expect this code to give me a sequence which opens a file and reads the lines, parses them, and gives ItemEntrys, and when used up, close the file.
What I get, however, is IOException("Stream closed").
Somehow, even before the first item is read (I have debugged), somewhere within Kotlin's wrappers, the reader.in becomes null, so this exception is thrown in hasNext().
I have seen a similar question here: Kotlin to chain multiple sequences from different InputStream?
That one includes a lot of Java boilerplate which I would like to avoid.
How should I code this sequence using Path.useLines()?
Every Kotlin helper with "use" in the name closes the underlying resource at the end of the lambda you pass (at least that's a convention in the stdlib as far as I know). The most common example being AutoCloseable.use.
The Path.useLines extension is no exception:
Calls the block callback giving it a sequence of all the lines in this file and closes the reader once the processing is complete. [emphasis mine]
This means useLines closes the sequence of lines once the block is done, and thus you cannot return a lazy sequence out of it because you can't use it after the useLines function returns.
So, if you want to return a sequence of lines for later use, you cannot return a transformed sequence from that of useLines directly. Sequences actually cannot detect when someone is done using them, hence why useLines needs a lambda to give a "scope" or "lifetime" to the sequence and know when to close the underlying reader.
If you want to wrap this, you have 2 major options: either split the sequence operation and the close operation (make your PipeSeparatedItemsReader closeable), or use a lambda to process things in-place in readItems() the same way useLines does.

Why does `EffectScope.shift` need the type parameter `B`?

The move to the new continuations API in Arrow brought with it a handy new function: shift, in theory letting me get rid of ensure(false) { NewError() } or NewError().left().bind() constructs.
But I'm not sure how to properly use it. The documentation states that it is intended to short-circuit the continuation, and there are no conditionals, so it should always take the parameter, and (in either parlance) "make it a left value", and exit the scope.
So what is the type parameter B intended to be used for? It determines the return type of shift, but shift will not return. Given no more context, B can not be inferred, leading to this kind of code:
val res = either {
val intermediate = mayReturnNull()
if (intermediate == null) {
shift<Nothing>(IntermediateWasNull())
}
process(intermediate)
}
Note the <Nothing> (and ignore the contrived example, the main point is that shifts return type can not be inferred – the actual type parameter does not even matter).
I could wrap shift like this:
suspend fun <L> EffectScope<L>.fail(left: L): Nothing = shift(left)
But I feel like that is missing the point. Any explanations/hints would be greatly appreciated.
That is a great question!
This is more a matter of style, ideally we'd have both but they conflict so we cannot have both APIs available.
So shift always returns Nothing in its implementation, and so the B parameter is completely artificial.
This is something that is true for a lot of other things in Kotlin, such as object EmptyList : List<Nothing>. The Kotlin Std however exposes it as fun <A> emptyList(): List<A> = EmptyList.
For Arrow to stay consistent with APIs found in Kotlin Std, and to remain as Kotlin idiomatic as possible we also require a type argument just like emptyList. This has been up for discussion multiple times, and the Kotlin languages authors have stated that it was decided too explicitly require A for emptyList since that results in the best and most consistent ergonomics in Kotlin.
In the example you shared I would however recommend using ensureNotNull which will also smart-cast intermediate to non-null.
Arrow attempts to build the DSL so that you don't need to rely on shift in most cases, and you should prefer ensure and ensureNotNull when possible.
val res = either {
val intermediate = mayReturnNull()
ensureNotNull(intermediate) { IntermediateWasNull() }
process(intermediate) // <-- smart casted to non-null
}

Using inline function kotlin

I know there was documented in main kotlin page, but there is no clear explanation about when to use it, why this function need a receiver as a function. What would be the correct way to create a correct definition of inline function.
This is inline function
inline fun String?.toDateString(rawDateFormat: String = MMMM_DD_YYYY, outputDate: String = MM_DD_YYYY, block: (date: String) -> String): String {
return try {
var sdf = SimpleDateFormat(rawDateFormat, Locale.US)
val date = sdf.parse(this.orEmpty())
sdf = SimpleDateFormat(outputDate, Locale.US)
block(sdf.format(date ?: Date()).orEmpty())
} catch (ex: Exception) {
block("")
}
}
The same way we also can do
inline fun String?.toDateString(rawDateFormat: String = MMMM_DD_YYYY, outputDate: String = MM_DD_YYYY): String {
return try {
var sdf = SimpleDateFormat(rawDateFormat, Locale.US)
val date = sdf.parse(this.orEmpty())
sdf = SimpleDateFormat(outputDate, Locale.US)
sdf.format(date ?: Date()).orEmpty()
} catch (ex: Exception) {
""
}
}
If anyone could have a detail explanation about this?
Edit:
I understand that the inline function will insert the code whenever it called by the compiler. But this come to my attention, when I want to use inline function without functional parameter receiver type the warning show as this in which should have a better explain. I also want to understand why this is such recommendation.
There are few things here.
First, you ask about using a function with a receiver.  In both cases here, the receiver is the String? part of String?.toDateString().  It means that you can call the function as if it were a method of String, e.g. "2021-01-15 12:00:00".toDateString(…).
The original String? is accessible as this within the function; you can see it in the sdf.parse(this.orEmpty()) call.  (It's not always as obvious as this; you could simply call sdf.parse(orEmpty()), where the this. is implied.)
Then you ask about inline functions.  All you have to do is to mark the function as inline, and the compiler will automatically insert its code wherever it's called, instead of defining a function in the usual way.  But you don't need to worry about how it's implemented; there are just a few visible effects in the code.  In particular, if a function is inline and accepts a function parameter, then its lambda can do a few things (such as calling return) that it couldn't otherwise do.
Which leads us to what I think is your real question: about the block function parameter.  Your first example has this parameter, with the type (date: String) -> String — i.e. a function taking a single String parameter and returning another String.  (The technical term for this is that toDateString() is a higher-order function.)
The toDateString() function calls this block function before returning, applying it to the date string it has formatted before returning it to the caller.
As to why it does this, it's hard to tell.  That's why we put documentation comments before functions: to explain anything that's not obvious from the code!  Ideally, there would be a comment explaining why you're required to supply a block lamdba (or function reference), when it's not vital to what the function does.
There are times when blocks passed this way are very useful.  For example, the joinToString() function accepts an optional transform parameter, which it applies to each item before joining it to the list.  If it didn't, the effect would be a lot more awkward to obtain.  (You'd probably have to apply a map() to the collection before calling joinToString(), which would be less efficient.)
But this isn't one of those times.  As your second example shows, toDateString() would work perfectly well without the block parameter — and then if you needed to pass the result through another function, you could just call it on toDateString()'s result.
Perhaps if you included a link to the ‘main kotlin page’ where you saw this, it might give some more context?
The edited question also asks about the IDE warning.  This is shown when it thinks inlining a function won't give a significant improvement.
When no lambdas are involved, the only potential benefit from inlining a function is performance, and that's a trade-off.  It might avoid the overhead of a function call wherever it's called — but the Java runtime will often inline small functions anyway, all on its own.  And having the compiler do the inlining comes at the cost of duplicating the function's code everywhere it's called; the increased code size is less likely to fit into memory caches, and less likely to be optimised by the Java runtime — so that can end up reducing the performance overall.  Because this isn't always obvious, the IDE gives a warning.
It's different when lambdas are involved, though.  In that case, inlining affects functionality: for example, it allows non-local returns and reified type parameters.  So in that case there are good reasons for using inline regardless of any performance implications, and the IDE doesn't give the warning.
(In fact, if a function calls a lambda it's passed, inlining can have a more significant performance benefit: not only does the function itself get inlined, but the lambda itself usually does as well, removing two levels of function call — and the lambda is often called repeatedly, so there can be a real saving.)

Kotlin expression fun vs normal fun - differences

Let's assume that I have two functions which do the same stuff.
First one:
fun doSomething() = someObject.getSomeData()
Second one:
fun doSomething(): SomeData {
return someObject.getSomeData()
}
Are there any technical differences between expression functions and standard function in Kotlin excluding the way how they look?
Is compiled output the same?
Are there any advantages using one instead another?
As #Sơn Phan says, they both compile to exactly the same bytecode.
So the differences are simply about conciseness.  The expression form omits the braces and return; it also lets you omit the return type (using type inference as needed).  As the question illustrates, the expression form can be shorter — and when all else is equal, shorter tends to be easier to read and understand.
So whether the expression form is appropriate is usually a matter of style rather than correctness.  For example, this function could be on one line:
fun String.toPositiveIntegers() = split(",").mapNotNull{ it.toIntOrNull() }.filter{ it >= 0 }
But it's a bit long, and probably better to split it.  You could keep the expression form:
fun String.toPositiveIntegers()
= split(",")
.mapNotNull{ it.toIntOrNull() }
.filter{ it >= 0 }
Or use a traditional function form:
fun String.toPositiveIntegers(): List<Int> {
return split(",")
.mapNotNull{ it.toIntOrNull() }
.filter{ it >= 0 }
}
(I tend to prefer the former, but there are arguments both ways.)
Similarly, I rather like using it when the body is a simple lambda, e.g.:
fun createMyObject() = MyObject.apply {
someConfig(someField)
someOtherConfig()
}
…but I expect some folk wouldn't.
One gotcha when using the expression form is the type inference.  Generally speaking, in Kotlin it's good to let the compiler figure out the type when it can; but for function return values, that's not always such a good idea.  For example:
fun myFun(): String = someProperty.someFunction()
will give a compilation error if the someFunction() is ever changed to return something other than a String — even a nullable String?.  However:
fun myFun() = someProperty.someFunction()
…would NOT give a compilation error; it would silently change the function's return type.  That can mask bugs, or make them harder to find.  (It's not a very common problem, but I've hit it myself.)  So you might consider specifying the return type, even though you don't need to, whenever there's a risk of it changing.
One particular case of this is when calling a Java function which doesn't have an annotation specifying its nullability.  Kotlin will treat the result as a ‘platform type’ (which means it can't tell whether it's nullable); returning such a platform type is rarely a good idea, and IntelliJ has a warning suggesting that you specify the return type explicitly.
1. Compiled output
Yes the compiled output will be completely the same
2. Advantage
You usually use expression function when the body of a function is only one line of expression to make it a oneliner function. Its advantage mainly about making the code more concise. Imagine instead of all the brackets and return, you only need a = to make things done.

Karate - custom assertion in expected JSON schema

I'm looking to perform custom assertions on fields in JSON loaded from file.
I understand that we have fuzzy matching, but I'd like to perform something more custom e.g. have a function which parses a date as a LocalDateTime:
public class DateUtil {
public static boolean matchesMyDateFormat(String dateStr) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
LocalDateTime.parse(dateStr, formatter);
} catch (DateTimeParseException e) {
return false;
}
return true;
}
}
This would be called by the following:
* def matchesMyDateFormat =
"""
function fn(dateX){
return Java.type('com.karate.DateUtil').matchesMyDateFormat(dateX);
}
"""
* def expected = read('expected.json')
* def actual = read('actual.json')
* match expected == actual
Where expected.json looks like this:
{
"date1" : "#? matchesMyDateFormat(_)"
}
NB this is specifically for JSON loaded from file and not on JSON which is specified in the feature file itself (e.g. like for isValidTime() here: https://github.com/intuit/karate/blob/master/karate-junit4/src/test/java/com/intuit/karate/junit4/demos/schema-like.feature).
Few reasons for wishing to do it this way:
Some of the payloads I need to assert have a lot of date fields coming back, with different formats. Asserting like the above would tie in nicely with Karate's excellent way of validating schema. Doing this in the feature files, however, would require a lot of code, i.e. a line of code for each date (I realize match each could be used - but even this would get complex, depending on the nesting of the fields.)
I would be able to add this function to my common utils feature file, so it can be re-used throughout the project's expected response schema.
Beyond this I'd be looking to do other things, like check that one date occurs before another (but I'd want to do this using various types in Java, e.g. taking time zone into consideration).
I'd also be looking for the format matching method to take in another param, which lets the tester specify a custom format string.
NB: I've read through the docs and the other SO answers related to date assertions and believe this is a slightly different ask.
Is the above possible to do in Karate at the moment?
You can add functions in karate-config.js which will be "global". For example:
var config = {};
config.isValidDate = read('classpath:is-valid-date.js');
return config;
Now you can use isValidDate(_) in any feature. Note that JS functions can take multiple arguments, e.g:
* match foo == { bar: "#? isValidDate(_, 'MYFORMAT')" }
In 0.9.6.RC4 we made improvements so that you can move complex conditional logic and even match operations into re-usable JS files: https://github.com/intuit/karate/issues/1202
Be warned, doing a lot of this may lead to un-readable tests: https://stackoverflow.com/a/54126724/143475
One hint, you can use karate.forEach() to extract all date-fields into an array and then a single match each may work.
Finally, if you still feel there is "too much code in your feature files", I don't know, maybe you need to consult a magician.
What I was trying was in fact a valid use-case (as is the alternative solution kindly suggested by Peter Thomas in his answer).
The reason my particular variation wasn't working was this error:
07:22:50.421 assertion failed: path: $.date1, actual: '#? matchesMyDateFormat(_)', expected: '2020-06-10T14:44:57.060Z', reason: not equal
I noticed with a fresh pair of eyes that I should flip the match statement from:
* match expected == actual
To:
* match actual == expected
This way is required in order for Karate to work its magic and call the custom function in expected.json.