https://kotlinlang.org logo
Join the conversationJoin Slack
Channels
100daysofcode
100daysofkotlin
100daysofkotlin-2021
advent-of-code
aem
ai
alexa
algeria
algolialibraries
amsterdam
android
android-architecture
android-databinding
android-studio
androidgithubprojects
androidthings
androidx
androidx-xprocessing
anime
anko
announcements
apollo-kotlin
appintro
arabic
argentina
arkenv
arksemdevteam
armenia
arrow
arrow-contributors
arrow-meta
ass
atlanta
atm17
atrium
austin
australia
austria
awesome-kotlin
ballast
bangladesh
barcelona
bayarea
bazel
beepiz-libraries
belgium
berlin
big-data
books
boston
brazil
brikk
budapest
build
build-tools
bulgaria
bydgoszcz
cambodia
canada
carrat
carrat-dev
carrat-feed
chicago
chile
china
chucker
cincinnati-user-group
cli
clikt
cloudfoundry
cn
cobalt
code-coverage
codeforces
codemash-precompiler
codereview
codingame
codingconventions
coimbatore
collaborations
colombia
colorado
communities
competitive-programming
competitivecoding
compiler
compose
compose-android
compose-desktop
compose-hiring
compose-ios
compose-mp
compose-ui-showcase
compose-wear
compose-web
connect-audit-events
corda
cork
coroutines
couchbase
coursera
croatia
cryptography
cscenter-course-2016
cucumber-bdd
cyprus
czech
dagger
data2viz
databinding
datascience
dckotlin
debugging
decompose
decouple
denmark
deprecated
detekt
detekt-hint
dev-core
dfw
docs-revamped
dokka
domain-driven-design
doodle
dsl
dublin
dutch
eap
eclipse
ecuador
edinburgh
education
effective-kotlin
effectivekotlin
emacs
embedded-kotlin
estatik
event21-community-content
events
exposed
failgood
fb-internal-demo
feed
firebase
flow
fluid-libraries
forkhandles
forum
fosdem
fp-in-kotlin
framework-elide
freenode
french
fritz2
fuchsia
functional
funktionale
gamedev
ge-kotlin
general-advice
georgia
geospatial
german-lang
getting-started
github-workflows-kt
glance
godot-kotlin
google-io
gradle
graphic
graphkool
graphql
graphql-kotlin
graviton-browser
greece
grpc
gsoc
gui
hackathons
hacktoberfest
hamburg
hamkrest
helios
helsinki
hexagon
hibernate
hikari-cp
hire-me
hiring
hongkong
hoplite
http4k
hungary
hyderabad
image-processing
india
indonesia
inkremental
intellij
intellij-plugins
intellij-tricks
internships
introduce-yourself
io
ios
iran
israel
istanbulcoders
italian
jackson-kotlin
jadx
japanese
jasync-sql
java-to-kotlin-refactoring
javadevelopers
javafx
javalin
javascript
jdbi
jhipster-kotlin
jobsworldwide
jpa
jshdq
juul-libraries
jvm-ir-backend-feedback
jxadapter
k2-early-adopters
kaal
kafka
kakao
kalasim
kapt
karachi
karg
karlsruhe
kash_shell
kaskade
kbuild
kdbc
kgen-doc-tools
kgraphql
kinta
klaxon
klock
kloudformation
kmdc
kmm-español
kmongo
knbt
knote
koalaql
koans
kobalt
kobweb
kodein
kodex
kohesive
koin
koin-dev
komapper
kondor-json
kong
kontent
kontributors
korau
korean
korge
korim
korio
korlibs
korte
kotest
kotest-contributors
kotless
kotlick
kotlin-asia
kotlin-beam
kotlin-by-example
kotlin-csv
kotlin-data-storage
kotlin-foundation
kotlin-fuel
kotlin-in-action
kotlin-inject
kotlin-latam
kotlin-logging
kotlin-multiplatform-contest
kotlin-mumbai
kotlin-native
kotlin-pakistan
kotlin-plugin
kotlin-pune
kotlin-roadmap
kotlin-samples
kotlin-sap
kotlin-serbia
kotlin-spark
kotlin-szeged
kotlin-website
kotlinacademy
kotlinbot
kotlinconf
kotlindl
kotlinforbeginners
kotlingforbeginners
kotlinlondon
kotlinmad
kotlinprogrammers
kotlinsu
kotlintest
kotlintest-devs
kotlintlv
kotlinultimatechallenge
kotlinx-datetime
kotlinx-files
kotlinx-html
kotrix
kotson
kovenant
kprompt
kraph
krawler
kroto-plus
ksp
ktcc
ktfmt
ktlint
ktor
ktp
kubed
kug-leads
kug-torino
kvision
kweb
lambdaworld_cadiz
lanark
language-evolution
language-proposals
latvia
leakcanary
leedskotlinusergroup
lets-have-fun
libgdx
libkgd
library-development
linkeddata
lithuania
london
losangeles
lottie
love
lychee
macedonia
machinelearningbawas
madrid
malaysia
mathematics
meetkotlin
memes
meta
metro-detroit
mexico
miami
micronaut
minnesota
minutest
mirror
mockk
moko
moldova
monsterpuzzle
montreal
moonbean
morocco
motionlayout
mpapt
mu
multiplatform
mumbai
munich
mvikotlin
mvrx
myndocs-oauth2-server
naming
navigation-architecture-component
nepal
new-mexico
new-zealand
newname
nigeria
nodejs
norway
npm-publish
nyc
oceania
ohio-kotlin-users
oldenburg
oolong
opensource
orbit-mvi
osgi
otpisani
package-search
pakistan
panamá
pattern-matching
pbandk
pdx
peru
philippines
phoenix
pinoy
pocketgitclient
polish
popkorn
portugal
practical-functional-programming
proguard
prozis-android-backup
pyhsikal
python
python-contributors
quasar
random
re
react
reaktive
realm
realworldkotlin
reductor
reduks
redux
redux-kotlin
refactoring-to-kotlin
reflect
refreshversions
reports
result
rethink
revolver
rhein-main
rocksdb
romania
room
rpi-pico
rsocket
russian
russian_feed
russian-kotlinasfirst
rx
rxjava
san-diego
science
scotland
scrcast
scrimage
script
scripting
seattle
serialization
server
sg-user-group
singapore
skia-wasm-interop-temp
skrape-it
slovak
snake
sofl-user-group
southafrica
spacemacs
spain
spanish
speaking
spek
spin
splitties
spotify-mobius
spring
spring-security
squarelibraries
stackoverflow
stacks
stayhungrystayfoolish
stdlib
stlouis
strife-discord-lib
strikt
students
stuttgart
sudan
swagger-gradle-codegen
swarm
sweden
swing
swiss-user-group
switzerland
talking-kotlin
tallinn
tampa
teamcity
tegal
tempe
tensorflow
terminal
test
testing
testtestest
texas
tgbotapi
thailand
tornadofx
touchlab-tools
training
tricity-kotlin-user-group
trójmiasto
truth
tunisia
turkey
turkiye
twitter-feed
uae
udacityindia
uk
ukrainian
uniflow
unkonf
uruguay
utah
uuid
vancouver
vankotlin
vertx
videos
vienna
vietnam
vim
vkug
vuejs
web-mpp
webassembly
webrtc
wimix_sentry
wwdc
zircon
Powered by Linen
compiler
  • k

    kevin.cianfarini

    03/19/2022, 12:25 AM
    Maybe this is the wrong channel. I have a question about Regex.
    Represents a compiled regular expression
    When we say it's compiled, does that mean the regex is transformed into a state machine? I recently read this article and thought it was very interesting. https://swtch.com/~rsc/regexp/regexp1.html
    s
    e
    • 3
    • 2
  • d

    dalexander

    03/22/2022, 3:23 PM
    Hi, I'm maintaining a compiler plugin that uses PSI and source code generation. I recently read that PSI is going to be deprecated (and removed?) for FIR at some point? Is this true? Is there somewhere I could learn more about this transition to try and guess what the maintenance burden of this change would be?
    e
    b
    • 3
    • 4
  • g

    galex

    03/23/2022, 3:07 PM
    Hello, I'd like to check class names, their supertype & imports they use and depending on some conditions throw an compilation error with an appropriate message. What should I use to do that? Does
    AnalysisHandlerExtension
    fit this use case? Any examples?
    s
    • 2
    • 2
  • e

    eygraber

    03/25/2022, 3:05 PM
    I'm trying Kotlin 1.6.20-RC2 with
    useFir = true
    and I'm getting some errors. What's the best way to report these?
    d
    • 2
    • 1
  • k

    kralli

    03/25/2022, 5:27 PM
    More of Intellij core question, but in the context of the Kotlin compiler: I am looking into fixing the parse error, that arises when writing something like
    val a: List<String>= listOf()
    , where there is no whitespace in between the type parameter and the assignment. Am I right when I conclude, that the
    PsiBuilder
    does not allow for splitting tokens but only for remapping? So is it really not (trivially) possible to convert a GTEQ into a GT and an EQ?
    • 1
    • 1
  • e

    eygraber

    03/27/2022, 4:48 AM
    If I have a
    KtLambdaArgument
    that is the last value in
    Call.valueArguments
    what is the best way to determine if it is a trailing lambda or not?
    • 1
    • 1
  • p

    Peter Mandeljc

    03/31/2022, 7:59 AM
    how much should I care about putting constant strings into companion objects?
    // A
    fun example() = "banana"
    
    // B
    companion object { 
        const val X = "banana"
    }
    fun example() = X
    I guess benefit is storing variable to heap. But on the other hand, I think that adding single-use constants impacts code readability. Any thoughts?
    f
    e
    s
    • 4
    • 6
  • v

    vineethraj49

    04/01/2022, 2:52 PM
    hi, apologies if this has been answered before, is there a way to profile how long it took to compile a single class? context: trying to profile how long it takes to process certain classes with kotlin-maven-plugin
    s
    • 2
    • 1
  • z

    Zac Sweers

    04/03/2022, 4:29 AM
    are the standalone compiler assets hosted on maven central anywhere? Specifically looking at the ones attached to github releases
    b
    • 2
    • 1
  • h

    hfhbd

    04/04/2022, 2:43 PM
    Would it possible to run the compiler in JavaScript or does it need some JVM/C (?) api to compile Kotlin to JavaScript (IR) in your browser? Use case is a local Kotlin web playground
    s
    m
    e
    • 4
    • 7
  • v

    Vladimir Tagakov

    04/07/2022, 1:33 AM
    Hello! Did someone see this kind of behavior?
    private typealias FixesCompilation = (Unit, Unit) -> Unit
    private typealias AlsoFixesCompilation = Unit.(action: Unit) -> Unit
    val d: Unit.(action: Unit) -> Unit
    If any of lines above is presented everything compiles fine, typealias is not used anywhere. But when I remove it I’m getting the
    ISE
    with:
    IrSimpleFunctionPublicSymbolImpl for kotlin/Function2.invoke|3397681596704175240[0] is already bound: FUN IR_EXTERNAL_DECLARATION_STUB name:invoke
    Kotlin version is:
    1.6.10
    full stacktrace in the thread
    s
    • 2
    • 2
  • p

    pakoito

    04/08/2022, 9:15 AM
    Is there any way to hint the compiler that it should not infer
    Any
    in
    fun <T> assertEquals(expected: T, result: T): Unit
    when expected and result don’t have a shared ancestor?
    s
    s
    +4
    • 7
    • 12
  • a

    Asq

    04/09/2022, 1:06 PM
    this slack thread discusses a workaround that perhaps highlights a minor quirk in the compiler deserving attention and could maybe be considered for a fix
    d
    • 2
    • 2
  • t

    Tom Dyas

    04/10/2022, 2:58 AM
    Hello. I’m integrating Kotlin with the Pants build tool. I had an integration question: How should compiler plugins be provided to the compiler? (in my specific case,
    org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
    ) Must it be a jar from under
    KOTLIN_HOME
    or are plugin jars available on Maven Central for resolution with Coursier?
    • 1
    • 2
  • m

    martmists

    04/19/2022, 12:18 PM
    is there any documentation on the IR generation you can do with compiler plugins? I've got the Ir before and after, but I don't quite know how to do the transformation. The naive way would be to use the embedded compiler, template code and extract the compiled IR, but I feel like there should be a simpler way to deal with it, especially considering I can't use libraries with the embedded compiler
    s
    • 2
    • 2
  • n

    neworldlt

    04/19/2022, 3:55 PM
    I am trying to create a plugin that removes
    internal
    modifiers in the compiler process. Like
    kotlin-allopen
    plugin. The idea is to keep a way of having
    internal
    abuse in kotlin generated code. However, after deep-diving I hit a wall. No extension is running because Square's
    Anvil
    (and probably KSP) is generating code that does not compile due to publicly exposed
    internal
    stuff. The only exception is
    DeclarationAttributeAltererExtension
    , which runs during the resolve phase. However,
    DeclarationAttributeAltererExtension
    is able to modify only
    Modality
    . Is there any other way to plugin during
    resolve
    phase in order to fix compilation errors?
    s
    • 2
    • 11
  • m

    martmists

    04/20/2022, 11:16 AM
    A few questions about IR generation: 1. How do I add properties to package-level? 2. How do I use generic types for types? 3. How can I use kotlin/native cinterop methods with FqName, because it seems to complain that the types/names do not exist. 4. How do I use type aliases as types?
    s
    • 2
    • 7
  • m

    Muhammad Sufyian

    04/23/2022, 10:43 AM
    Hello folks , I am trying to understand
    how kotlin compiler works
    and looking for some document or may be any other way that can help me in understand the execution from syntax tree generation to target code . Can anyone point me in the right direction ? Thank you
    j
    m
    • 3
    • 2
  • p

    Peter Mandeljc

    05/03/2022, 4:07 PM
    Should A. be preferred over B. for some reason? A.
    open fun x() = Unit
    B.
    open fun x() { }
    🅰️ 7
    🅱️ 4
    f
    e
    • 3
    • 4
  • g

    galex

    05/05/2022, 9:59 AM
    Hello, I'm trying out compiler plugins and I wonder if
    AnalysisHandlerExtension
    is the right place for things like returning errors if the class name is not valid (as a simple use case). To do so I've implemented something like this:
    class CGAnalysisHandlerExtension : AnalysisHandlerExtension {
      override fun doAnalysis(
        project: Project,
        module: ModuleDescriptor,
        projectContext: ProjectContext,
        files: Collection<KtFile>,
        bindingTrace: BindingTrace,
        componentProvider: ComponentProvider
      ): AnalysisResult? {
    
        files.forEach { file ->
          val fileName = file.name.replace(".kt", "")
          file.children.forEach {
            when (it) {
              is KtClass -> {
                if (it.name != fileName) {
                  bindingTrace.report(UNDERSCORE_IS_RESERVED.on(it))
                  return AnalysisResult.compilationError(bindingTrace.bindingContext)
                }
              }
            }
          }
        }
        return super.doAnalysis(project, module, projectContext, files, bindingTrace, componentProvider)
      }
    }
    Doing so I thought I'd see the IDE underline the className in red with its error but the IDE doesn't react to this report, only when compiling and in the Build Output (see screenshot). So my question is the following: What is the missing piece here to have the IDE show the report/error as any other errors, and can't I have this check actually happen when the class is in the "Analyzing..." phase of the IDE? Thanks in advance!
    r
    j
    • 3
    • 14
  • d

    doubov

    05/05/2022, 10:24 PM
    👋 is this a know bug/limitation of multiple context receivers:
    context(CoroutineExecutorScope<Msg, State, Label>)
    works fine, but
    context(CoroutineExecutorScope<Msg, *, *>)
    throws a compilation error.
    j
    • 2
    • 3
  • q

    Quantum64

    05/09/2022, 9:00 AM
    Is there any way I can diagnose which parts of my project are taking the longest to compile? We're trying to speed up compile times by modularizing a large project so it can be built in parallel, but we don't know which parts will see the greatest compile-time benefits when split out. The project takes several minutes to compile so this would be really helpful for performance.
    m
    • 2
    • 4
  • a

    aballano

    05/09/2022, 11:35 AM
    Hi there, I'm having some issues because type checking not working as expected, I have a generic function such as
    fun <T> something(f: () -> T): T
    and I would like for the compiler to fail if I try to do this:
    val unit: Unit = something {
        1 
    }
    The problem with this is that if we would have something like a Flow inside or any other complex type that we forgot to execute/unwrap/whatever, this would be a bug in the codebase, for example:
    val unit: Unit = something {
        flowOf(1)
    }
    If this would fail as I would expect it to, I would notice that I forgot to collect my flow, but instead is accepting a Flow or any type as Unit, is there a way to enforce this somehow?
    w
    t
    • 3
    • 9
  • a

    Alexander Maryanovsky

    05/17/2022, 7:43 AM
    Funny case where whitespace between tokens makes a difference. This compiles:
    typealias ComposableFunction = @Composable (value: Int) -> Unit
    but this doesn’t:
    typealias ComposableFunction = @Composable(value: Int) -> Unit
    e
    • 2
    • 1
  • m

    martmists

    05/19/2022, 1:07 PM
    I'm getting a compiler issue in my project:
    org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during psi2ir
    File being compiled: (26,9) in /home/mart/git/sites/martmists/src/backendMain/kotlin/com/martmists/backend/data/GithubProject.kt
    The root cause java.util.NoSuchElementException was thrown at: org.jetbrains.kotlin.psi2ir.generators.ArgumentsGenerationUtilsKt$generateReceiver$1.load(ArgumentsGenerationUtils.kt:873)
    Collection contains no element matching the predicate.: KtCallExpression:
    div("project project-github") {
        ...
    s
    • 2
    • 4
  • j

    Jesper Åman

    05/22/2022, 10:48 AM
    Hi there, I'm writing a compiler plugin and am stuck on a (seemingly simple) problem. I have an
    IrGenerationExtension
    that generates some classes and methods, in some of the method bodies I need to include a super call, however, it's not entirely clear to me how to achieve this through the IR-apis. With this code, which is meant to just call
    super.onCreate()
    in a subclass of `android.app.Application`:
    override fun visitFunctionNew(declaration: IrFunction): IrStatement {
        if (declaration.name.asString() == "onCreate"
                && declaration.parentAsClass.hasAnnotation(pluginContext.externalConfigApplicationAnnotation)
            ) {
            ...
            // If body does not already exist
            declaration.body = pluginContext.irBlockBody(declaration.symbol) {
                +irCall(declaration, superQualifierSymbol = pluginContext.applicationClass).apply {
                    dispatchReceiver = irGet(declaration.dispatchReceiverParameter!!)
                }
                ...
            }
            ...
        }
        return super.onVisitFunctionNew(declaration)
    }
    I'm getting the following error:
    Caused by: java.lang.AssertionError: SyntheticAccessorLowering should not attempt to modify other files!
    While lowering this file: FILE fqName:c.z.a.e.sample fileName:/.../MainActivity.kt
    Trying to add this accessor: FUN SYNTHETIC_ACCESSOR name:access$onCreate$s-1072845520 visibility:public modality:FINAL <> ($this:android.app.Application) returnType:kotlin.Unit
    Seems like rather than just invoking super, it tries to generate a synthetic accessor. Any ideas as to why this is not working? I do have a workaround currently that involves just injecting the java byte code further down the chain via a
    ClassBuilderInterceptorExtension
    , but would very much like to avoid this if possible. I'm seeing that there are ways to achieve this easily when dealing with constructors, e.g.
    +irDelegatingConstructorCall(...)
    , is there something similar available for functions generally?
    l
    • 2
    • 4
  • a

    Alexander Maryanovsky

    05/22/2022, 3:38 PM
    Got an exception during compilation. Does this (error message in thread) look like it’s enough to submit a bug report, or should I try to get a small reproduction sample?
    y
    • 2
    • 5
  • q

    Quantum64

    05/24/2022, 4:00 AM
    Is there any experimental FIR plugin API that I can try out?
    d
    • 2
    • 5
  • r

    rrva

    05/24/2022, 5:53 AM
    For JVM development targeting server-side, how alpha is the k2 version inside 1.7 ? Some simpler projects build and pass tests without issue
    d
    • 2
    • 12
  • a

    Alexander Maryanovsky

    05/24/2022, 3:58 PM
    Wouldn’t it be nice if a sealed interface/class with a single implementation could be auto-cast to that implementation, if accessible? When implementing types with a separate public and private interface, (e.g. memento or handle object) it would mean I don’t need to do the silly cast.
    ➕ 3
    :nice: 2
    f
    n
    e
    • 4
    • 41
Powered by Linen
Title
a

Alexander Maryanovsky

05/24/2022, 3:58 PM
Wouldn’t it be nice if a sealed interface/class with a single implementation could be auto-cast to that implementation, if accessible? When implementing types with a separate public and private interface, (e.g. memento or handle object) it would mean I don’t need to do the silly cast.
➕ 3
:nice: 2
f

Fleshgrinder

05/24/2022, 4:20 PM
Why define a sealed interface or class if there is only one implementation? The only reasoning I can see to do so is because we want to prepare for extension, however, that also means that we cannot assume anywhere in code that there only is one implementation. At the same time we have to ask ourselves if sealing it makes sense if we want to extend it. The ability to seal while not being exhaustive is something that was already requested, and something that makes sense. It is the desire to have control over who implements an interface or extends a class, and this is totally valid. However, in this case nobody can assume once more that there only is a single implementation. Hence, no matter how I turn this in my brain, I see no use case. Maybe I'm missing something? 🤔
➕ 2
a

Alexander Maryanovsky

05/24/2022, 4:58 PM
As I mentioned, it would be useful for implementing a memento, or a handle object pattern. You want to hand out some objects and for the users to give you back these objects at a later time. You want these objects to have some internal state, but you don't want to expose it to the users.
So you make a sealed Memento interface with the public API (possibly empty) and a sole implementation that adds the state you want to keep private.
The compiler doesn't even warn when you cast it though, so it's a very mild annoyance. It also indicates that the compiler already knows there's only one implementation.
n

nschulzke

05/24/2022, 5:09 PM
Why do you need an interface (sealed or otherwise) for this instead of just having a
Memento
class that has
internal
and
private
functions?
Give it a private constructor if you want to avoid people creating their own copies of the class.
a

Alexander Maryanovsky

05/24/2022, 5:10 PM
Because private is too restrictive and internal is too permissive.
n

nschulzke

05/24/2022, 5:11 PM
What do you gain by the interface, then? The implementation class will either be private or internal, and if they implement auto-casting you're back to the private state being too accessible.
a

Alexander Maryanovsky

05/24/2022, 5:11 PM
If there was a private-in-file modifier, then it would work.
A private type is private-in-file
A private attribute is private to the class.
n

nschulzke

05/24/2022, 5:12 PM
Ah, fair.
a

Alexander Maryanovsky

05/24/2022, 5:15 PM
But yes, it's a workaround for language limitations, not an intrinsically useful thing.
f

Fleshgrinder

05/24/2022, 6:01 PM
Access modifiers in Kotlin are very limited, same is true for any other JVM language I'm afraid. 😞
e

ephemient

05/25/2022, 1:40 AM
as a workaround, I'd prefer to add a helper like
public sealed interface Foo {
    internal class Impl : Foo
}

internal fun Foo.asImpl(): Foo.Impl = when (this) {
    is Impl -> this
}
over open-coding
as Impl
everywhere
a

Alexander Maryanovsky

05/25/2022, 4:39 AM
I think you don't even need the check. Just “as Impl” works.
f

Fleshgrinder

05/25/2022, 6:41 AM
If the example given by @ephemient works for the use case then there is no difference to:
public class Foo internal constructor() {
  internal val state: Any? = TODO()
  internal fun doSomething() = Unit
}
And if Java is a concern we can tack on
@JvmSynthetic
on all the `internal`s. Regardless, having a
public as Private
is less work then anything else and it's very clear. Having a dedicated auto-casting for it, however, seems unsafe and going against Kotlin's idea of being explicit about type changes. That said, all of these are workarounds for the absence of more fine grained access modifiers and I totally agree that there should be more: • Friend classes • Package private where the package(s) the symbol is visible in can be chosen with wildcard support. • Module private where the module(s) the symbol is visible in can be chosen with wildcard support. • … I think the Java module system is a way to get there and I'm curious how it might evolve beyond what we have today (which is too narrow in functionality by focusing on the needs of the JDK itself, which is totally fine as a starting point).
a

Alexander Maryanovsky

05/25/2022, 9:30 AM
This isn't an object type conversion, it's a refinement of the expression type. Kotlin already does this when it, for example, auto-casts T? to T. It's completely safe with no chance of errors (other than compiler errors).
f

Fleshgrinder

05/25/2022, 11:06 AM
But the type needs to be specified somewhere, so it's either
val private: Private = public
or
val private = public as Private
because without having the type anywhere it's impossible for the compiler to know that one didn't want
Public
. Now continue this to
f(public)
where
f
is
fun f(private: Private)
. In the current compilation where the sealed class only has one member this is legal, in the next one it is not legal anymore since the concrete type is only known at runtime. This may lead to a codebase being broken all over (imagine 100+
f
calls). This is where I see the unsafety. Btw. @ephemient doesn't suffer from this problem, because the conversion is confined into a dedicated function that makes usage search and global fixing easy.
The direction this topic took reminds me of something I was discussing the other day with a colleague regarding Rust's traits vs Kotlin's special casing and limitations it inherits from Java.
interface Into<T> {
  fun into(): T
}
How cool would it be to have this but with multiple inheritance like Rust has it. Kotlin special cased this for
Comparable<T>
with
operator
support. Of course, generic support would be nice, but it would break Java compatibility. However, the
Into<T>
trait would be doable.
public sealed interface Public {
  internal class Private : Public
}

internal operator fun Public.into(): Private = this as Private
For Java we generate:
public static Private toPrivate(Public public) {}
The whole thing would work exactly as it does in Rust with turbofish to help the compiler if it's confused.
f(public.into())
😎
class A {
  operator fun into(): B = TODO()
  operator fun into(): C = TODO()
}

fun g(a: A) {
  val b: B = a.into()
  val c = a.into<C>()
}

// Java
void g(A a) {
  B b = a.toB();
  C c = a.toC();
}
a

Alexander Maryanovsky

05/25/2022, 12:57 PM
It’s exactly like that with sealed classes/interfaces, and it’s intended, by design. Today your
when
is exhaustive, and tomorrow, when another type is added, it’s not, and doesn’t compile.
f

Fleshgrinder

05/25/2022, 1:02 PM
Yes, but not entirely, here you have a choice how you write your code (and the non-exhaustive feature request is exactly about forcing people to write code to handle additions while staying in full control of additions). There are situations in which one knows that there is only going to be n implementations, that's when a fixed sealing is appropriate. This is basically the situation you have too, in your case it's just that there's ever only going to be one and the casting is an annoyance. An unnecessary one at that as you already identified. There is no existing functionality in Kotlin to fix or remedy this, so all we've discussed are workarounds and alternatives. However, we also identified that the real problem is not with sealed but with a lack of expressiveness when it comes to access modifiers. This is what really needs to be addressed, and what would solve your use cases too.
a

Alexander Maryanovsky

05/25/2022, 1:04 PM
Kind of, but not exactly. With more fine-grained access modifiers, the use-case I presented can be solved in a better way, without auto-casting a single sealed type. But it’s possible there are other use-cases, although I can’t think of any at the moment.
Given how Kotlin auto-casts
T?
to
T
as soon as the compiler can infer that the value is not
null
, I think the auto-casting for a single sealed type is natural.
Doubly so when we can see the compiler already understands that the actual type of
Public
is
Private
, as it doesn’t give a warning when you cast it manually.
f

Fleshgrinder

05/25/2022, 1:11 PM
But it’s possible there are other use-cases, although I can’t think of any at the moment.
Absolutely, that's what I wrote in my very first message: maybe I'm missing something. Handling of
T?
to
T
is a very special case that complicates the language, but its applicability is universal so that it's warranted. Here we talk about an edge case that only very few are going to encounter. Complicating the language for such things is never a good idea. It's better to abstract the actual underlying issue and address it with a solution that solves a whole bunch of issues in a generic way. This is always hard for language designers to convey to users, since each user has a particular unique pain point, but that's what language designers have to do. There's also always the danger of introducing a particular solution to a particular problem and then other things break or get harder, but taking the feature back is close to impossible. Another situation that needs to be avoided by all means. Sealed types is a proven thing, but auto-casting if a sealed type has a single implementation is not. What if that single subtype has additional subtypes? Is the cast still safe in that situation? What does it mean for this and that? Lots of ground to cover. From the ad-hoc analysis done by some of us in this thread it already seems not worthwhile to go further for the reasons explained. The special casing of
T?
and
T
is on its own a problem and a fully fledged union/intersection system that works for all types (built-in or user) like Ceylon has it would be that general, universal solution. Everybody wants it, but it's hard. 🤞 we'll get it at some point.
a

Alexander Maryanovsky

05/25/2022, 1:20 PM
Actually, maybe I’m misunderstanding something. Why doesn’t the compiler warn at the cast here?
sealed interface Base{

}

class Sub1: Base{
    val n = 5
}

class Sub2: Base{
    val k = 6
}


fun foo (b: Base){
    println(
        (b as Sub2).k
    )
}
f

Fleshgrinder

05/25/2022, 1:22 PM
It doesn't warn here either:
sealed interface I {
    private class C : I
    private class D : I
}

fun f(i: I) {
    val c = i as C
}
Nor does it warn here:
import java.util.UUID

fun f(x: UUID) {
    val y = x as String
}
a

Alexander Maryanovsky

05/25/2022, 1:22 PM
Hmm, I think the warning is only for casts to a generic type.
f

Fleshgrinder

05/25/2022, 1:22 PM
It is. 🙂
a

Alexander Maryanovsky

05/25/2022, 1:23 PM
It would’ve been more intuitive for
as
to be
as!
or
as!!
f

Fleshgrinder

05/25/2022, 1:25 PM
as
(a cast) is by definition a forced operation, adding more symbols to it does not change that fact. Sure, the JVM (and other systems) have runtime protections to avoid totally silly casts, but they don't have to. In Rust silly casts are possible with
unsafe {}
and in C any cast is always possible. It's just 0s and 1s after all we are dealing with and how we interpret them is up to us.
import java.util.UUID

fun f(x: UUID) {
    val y = x as? String
}
This is possible to avoid the runtime exception.
e

ephemient

05/25/2022, 1:28 PM
unchecked cast warning is because casting a
List<String>
to a
List<Int>
can appear to succeed even though it'll cause problems later. but
UUID as String
is immediate
f

Fleshgrinder

05/25/2022, 1:31 PM
What @ephemient wrote, for the JVM we perform a
List as List
cast due to type erasure.
a

Alexander Maryanovsky

05/25/2022, 1:32 PM
I understand now. I’m just saying an exclamation mark has the connotation of an unsafe operation.
So making it
as!!
would more clearly mark an unsafe operation.
f

Fleshgrinder

05/25/2022, 1:34 PM
It does in writing, yes, but there's is no precedence for this in Kotlin.
!
is used to mark types where nullability is unknown and
!!
is used to throw a NPE if something shall never be null. Very different things.
a

Alexander Maryanovsky

05/25/2022, 1:35 PM
?
is used in more than one place to mark “operation or null if operation can’t succeed”
So that’s a precedent
f

Fleshgrinder

05/25/2022, 1:37 PM
?
is consistently used together with
null
. It either marks a type as nullable or an expression to short circuit if the previous expression is null. There are many languages out there that use symbols in different contexts for totally different things, and that can be fine, or you end up with Perl. 🤷 It's all about trade offs.
View count: 8