https://kotlinlang.org logo
#compiler
Title
# compiler
y

Youssef Shoaib [MOD]

05/08/2021, 7:12 PM
Hello there! I've heard that the plan is to eventually disallow suppressing certain compiler errors (like "INVISIBLE_MEMBER" and "INVISIBLE_REFERENCE" for example) which is absolutely understandable, but would compiler plugins still be able to suppress certain compiler errors one way or another? Currently, I'm generating some sources in an AnalysisHandler (which I know will eventually be deprecated don't worry but I'm just using it for now and the logic should be the same for the future) based on existing sources and shadowing them using
@Suppress("REDECLARATION")
but I'm wondering if maybe there's a better way to do that (like a custom call resolved I guess) and if eventually this trick will be completely disallowed
r

raulraja

05/09/2021, 9:37 AM
Hi @Youssef Shoaib [MOD], I think if you are in need of suppressing a diagnostic you don’t need to emit sources that include annotations with
@Suppress
. The binding trace and analysis phase gives you access to the diagnostics though you need to use reflection to mutate it https://github.com/arrow-kt/arrow-meta/blob/8f9e80bfbbcb12c16f025bc5ca7d69cbeeec08[…]lugin/src/main/kotlin/arrow/meta/dsl/analysis/AnalysisSyntax.kt If you end up finding a better way without that hack I’d love to know. 🙂
Regardless of suppression inspecting and modifying diagnostics is important to compiler plugins. Imagine a compiler plugin that enhances the compiler error message with additional context. To do that it would probably need access to diagnostics. I hope the stable compiler plugin API considers this.
y

Youssef Shoaib [MOD]

05/09/2021, 11:59 AM
After looking into it a bit, the easiest way to mutate it without reflection is by using an intermediary Java file to grab the
MutableDiagnosticsWithSuppression
since it is protected and so it's available in package like this:
Copy code
package org.jetbrains.kotlin.resolve;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.resolve.diagnostics.MutableDiagnosticsWithSuppression;

public class MutableDiagnosticsUtils {
    public static MutableDiagnosticsWithSuppression getMutableDiagnosticsFromTrace(@NotNull DelegatingBindingTrace trace) {
        return trace.getMutableDiagnostics();
    }
}
but other than that it seems like the only way to delete diagnostics is to unsafely cast
getOwnDiagnostics
to a mutable list (or maybe by somehow changing the binding trace? I'm not sure if that's even possible but if it is it'll work). You could also call
clear
on it and then
report
all the diagnostics that you want to keep. Also be careful that the current implementation in Meta is a bit flaky since the
readOnlyView
that the diagnostics depends on for its
all
result is cached based on the
modificationCounter
, and so it's probably a good idea to increment that after your
removeIf
call
👌 1
u

udalov

05/11/2021, 6:50 PM
Maybe a silly question, but how does exactly your plugin work? If it produces redeclaration by adding a declaration with the same signature (by Kotlin rules), this must lead to multiple declarations with the same JVM signature eventually resulting in
ClassFormatError
at runtime?
y

Youssef Shoaib [MOD]

05/11/2021, 7:41 PM
@udalov Oh well simply it is used to shadow external declarations at compile-time only, and then the plan (which I haven't implemented yet tbh) is to then delete the generated shadows and replace any left-over calls with normal ones. To put it more into context, I'm performing manual inlining for inline functions at the IR level and so I shadow external
inline
declarations that fit a certain criteria, let them be compiled normally next to the source code, then inline that IR, and (optionally) delete the shadows afterwards. This doesn't produce clashes because we're in a different .class file and compilation unit (at least I think that's why). Think for example if I wanted to inline the
let
function from stdlib. The plugin shadows it then uses its IR to inline it at each callsite. To go more into details, this happens exactly by (and yes this is incredibly hacky) finding the function calls I need to inline (in an
AnalysisHandler
's
analysisComplete
method), using the
BindingContext
to find the
.class
file the called function is coming from, using the
SourceFile
attribute in that
.class
file to know what source file they came from, then scanning the sources jar of that dependency to find all the matching source files, interpreting the file as a
KtFile
, then only keeping the declarations that match the functions I need. Afterwards, the code gets compiled, then the IR for each function gets inlined (I use
FunctionInlining
which JS and Native use). Of course, what I can do is mangle the method names and then in IR find the calls to those external methods and use the mangled methods' IR instead; I was originally planning on doing that, but then I simply forgot to mangle the names and it just worked so I rolled with it for the time being because of its simplicity, but I might have to revert to that idea in the future though. I guess only time will tell. This is all for a proof-of-concept compiler plugin to support KT-44530, which is (as a TL;DR) optimising lambdas (and `fun interface`s, and inline classes with an underlying type of the 2 aforementioned ones) that are returned by inline functions and are only used in other inline function calls and/or to call invoke on and allowing them to be stored in local
val
s. This opens up really clever techniques that even allow the implementations of efficient struct-like classes with no overhead. The YouTrack issue has an example implementation of an efficient Pair datatype. If you want to see this total cleverly-cobbled-together-yet-beautiful-mess of a code base, this is the github repo, which also has other examples of what this optimisation brings to the table. (Sorry for the long rant btw loL. I'm just really passionate about this topic and I've learned a lot from the Kotlin compiler's source code that helped me even attempt to make a plugin like this and get it to work lol)
👍 1
u

udalov

05/11/2021, 7:46 PM
Thanks for the explanation! Yeah, it seems you can simply produce declarations with different names and remember in your plugin code, where they came from.
🙏 1
4 Views