Any special reason to have `SymbolProcessor#init` ...
# ksp
e
Any special reason to have
SymbolProcessor#init
like annotation processor interfaces do? Having
init
and
process
in the same interface forces the declaration of `var`s to reference arguments from
init
for later usage in
process
. IMO a better approach would be a factory/provider interface:
Copy code
// ServiceLoader would load this instead
interface SymbolProcessorFactory {
    // same args passed to init
    fun create(codeGenerator: CodeGenerator, ...): SymbolProcessor
}

interface SymbolProcessor {
    fun process(...)
    fun finish()
}
Although it would be a little more work to implement, there are some pretty concise ways of doing it:
Copy code
class Processor(private val codeGenerator: CodeGenerator) : SymbolProcessor {
    override fun process(...) { ... }

    object Factory : SymbolProcessorFactory {
        override fun create(codeGenerator: CodeGenerator, ...) = Processor(codeGenerator) 
    }
}
This would allow full immutability.
👍 2
z
lateinit var
properties are effectively immutable and can be guarded with visibility if you're worried about others touching them, seems like an unnecessary amount of abstraction just to be able to use
val
in a class that has pretty clear guarantees about its lifecycle within the compilation
e
The problem is that I have to read the documentation to know for sure that the
init
won't be called more than once, which I imagine could happen when compiling multiple modules or something like that. Besides usage of
var
(which can be reassigned, even with
lateinit
), this would be a problem in case I did some heavy processing inside
init
, so things such as
isInitialized
start to appear. The proposed abstraction would eliminate these concerns with a contract that is clear and allows immutability. Besides that, it's a common pattern to deal with runtime dependencies and avoid temporal coupling.
Also, it would make the KSP code better because there would be no need to worry about function call order.
Considering all these points, I think it's worth the 3 extra lines of code it would require.
z
The problem is that I have to read the documentation to know for sure that the 
init
 won’t be called more than once
Is the function being named
init
not enough context? I think you’re overthinking this a bit. Compilers have lifecycles, I don’t think it’s unreasonable for KSP to expose those callbacks.
1
e
Even if it's enough context, why create the possibility for developers to make mistakes when Kotlin makes such abstractions so cheap to implement? In fact, my proposal would result in less code necessary for implementing it than using the current interface: (current)
Copy code
class Processor : SymbolProcessor {
    private lateinit var codeGenerator: CodeGenerator

    fun init(codeGenerator: CodeGenerator, ...) {
        this.codeGenerator = codeGenerator
    }

    fun process(...) {}
}
(proposed)
Copy code
class Processor(private val codeGenerator: CodeGenerator) : SymbolProcessor {
    fun process(...) {}

    object Provider : SymbolProcessorProvider {
        override fun create(codeGenerator: CodeGenerator, ...) = Processor(codeGenerator)
    }
}