├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── README.md ├── _config.yml ├── notes ├── code-coloring.md ├── value-classes.md └── web-workers.md └── proposals ├── android-extensions-entity-caching.md ├── annotation-instantiation.md ├── bound-callable-references.md ├── context-receivers.md ├── coroutines.md ├── data-class-inheritance.md ├── destructuring-in-parameters.md ├── enhancing-main-convention.md ├── experimental.md ├── explicit-api-mode.md ├── extensions ├── android-parcelable.md ├── opening-classes-for-frameworks.md └── serialization.md ├── functional-types-with-big-arity-on-jvm.md ├── generic-values-and-valueof-for-enums.md ├── inline-classes.md ├── inline-properties.md ├── jdk-dependent-built-ins.md ├── js ├── JsName.md └── _Glossary.md ├── jsr-305-custom-nullability-qualifiers.md ├── jvm-field-annotation-in-interface-companion.md ├── jvm-records.md ├── jvm-static-annotation-in-interface-companion.md ├── kotlin-contracts.md ├── lateinit-property-isinitialized-intrinsic.md ├── local-and-top-level-lateinit-vars.md ├── local-delegated-properties.md ├── named-arguments-in-their-own-position.md ├── opt-in.md ├── repeatable-annotations.md ├── scope-control-for-implicit-receivers.md ├── scripting-support.md ├── sealed-class-inheritance.md ├── sealed-interface-freedom.md ├── stdlib ├── TEMPLATE.md ├── abstract-collections.md ├── bignumber-operations.md ├── char-int-conversions.md ├── durations-and-clocks.md ├── durations-and-time-measurement.md ├── group-and-fold.md ├── locale-agnostic-case-conversions.md ├── locale-agnostic-string-conversions.md ├── map-copying.md ├── occurrences-of.md ├── on-each.md ├── random.md ├── result.md ├── runningFold-and-runningReduce.md ├── scan-and-scanReduce.md ├── string-to-number.md └── window-sliding.md ├── type-aliases.md ├── underscore-for-unused-parameters.md ├── underscores-in-numeric-literals.md ├── unsigned-types.md └── val-in-when-subject.md /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 11 | - [ ] 已仔细阅读 kotlin-web-site-cn#⁠35 及其中链接的内容 12 | - [ ] 阅读过 Kotlin 参考的大部分章节 13 | - [ ] 每一句都已经过谷歌机翻作为参考 14 | - [ ] 每一句都已经过搜狗机翻作为参考 15 | - [ ] 翻译中所用术语均与术语表中一致 16 | - [ ] 注释也已翻译,除了 `//sampleStart` 与 `//sampleEnd` 17 | - [ ] 正文与注释中的标点均已使用全角 18 | - [ ] 中文与英文/数字之间、中文与 `` ` `` 之间都以空格分隔 19 | - [ ] 英文与标点之间未留空格 20 | - [ ] 行级对照,中文句子中间断行处已使用 HTML 注释 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KEEP - Kotlin Evolution and Enhancement Process 2 | 3 | This repository contains proposals for the [Kotlin Programming Language](https://kotlinlang.org), including 4 | draft design notes and discussions for in-progress proposals as well as 5 | the design documentation on the changes that were already implemented. 6 | 7 | The proposals themselves are colloquially referred to as KEEPs. 8 | They cover both the language itself and its standard library. 9 | 10 | The implementation of the corresponding design enhancements usually lives in 11 | the [Kotlin Source Code Repository](https://github.com/JetBrains/kotlin). 12 | 13 | ## Current KEEPs 14 | 15 | * Current in-progress KEEPs are listed in [issues](https://github.com/Kotlin/KEEP/issues). 16 | * New KEEPs and additions to current KEEPs are submitted as [pull requests](https://github.com/Kotlin/KEEP/pulls). 17 | * When KEEPs are implemented, the corresponding design documents are merged into this repository and stored in a [proposals](proposals) directory. 18 | 19 | ### Design notes 20 | 21 | Some feature ideas that are being discussed for Kotlin represent important directions of potential enhancement, but 22 | are not complete to call them design proposals, yet they still need to be discussed 23 | with the Kotlin community to gather use-cases for these features, their potential syntax, impact on existing Kotlin code, etc. 24 | They are called "design notes" and are stored in a separate [notes](notes) directory. 25 | 26 | ## How to contribute to the design process 27 | 28 | Language enhancements/features usually go through the following informal stages: 29 | 30 | 1. Discussion of an idea. 31 | 2. Collection of use-cases. 32 | 3. Design proposal and prototype implementation. 33 | 4. Experimental support in the language. 34 | 5. Further refinement and stable release. 35 | 36 | All stages involve gathering of feedback on the corresponding feature. 37 | It does not stop even when the feature becomes stable. 38 | The community feedback on all stages is crucial to the open philosophy of Kotlin language enhancement process. 39 | 40 | ### Contributing ideas 41 | 42 | If you have a vague idea on the potential enhancement, not sure whether it is worthwhile and/or 43 | fits the Kotlin language, or just want to get community feedback, you can use either 44 | of the two channels you feel most comfortable with: 45 | 46 | * [#language-proposals](https://kotlinlang.slack.com/messages/language-proposals/) channel in Kotlin public Slack 47 | (get invite [here](http://slack.kotlinlang.org/)); 48 | * [Kotlin Forum in Language design category](https://discuss.kotlinlang.org/c/language-design). 49 | 50 | ### Contributing use-cases and specific enhancement proposals 51 | 52 | If you have a use-case that is not covered by the language or have a specific language enhancement in mind, 53 | then, please, file an [YouTrack issue](https://kotl.in/issue) in the `Language Design` subsystem. 54 | While many popular enhancements and language design problems are already filed in 55 | [Kotlin YouTrack](https://youtrack.jetbrains.com/issues/KT?project=kt), your contribution to them is still very important: 56 | 57 | * 👍 Vote for the issues you encounter in your work. 58 | * 📝 Comment with the description of your specific use-cases. 59 | 60 | Our practice of language design shows that contributing **real-life use-cases** is the most valuable piece of 61 | feedback from the Kotlin community. 62 | 63 | ### Contributing design proposals (KEEPs) 64 | 65 | Design proposals in this repository are expected to be well thought-through and usually come with 66 | a prototype implementation that demonstrates their feasibility. All design proposals in this repository 67 | shall be backed by maintainers of the corresponding subsystems in the Kotlin compiler or its standard library. 68 | 69 | If you are in doubt of whether the proposal meets those criteria, please start with the discussion 70 | of your idea, use-case, or a specific enhancement in the appropriate channels and secure support for your general 71 | idea/proposal from maintainers before submitting a KEEP. 72 | 73 | We will be gradually moving KEEPs that are not backed by Kotlin maintainers into YouTrack issues for further 74 | discussions. 75 | 76 | ### Contributing to existing KEEPs 77 | 78 | * For in-progress KEEPs, please keep discussions in the corresponding issue. 79 | * If you find problems with the _text_ or have text correction for merged KEEPs, feel free to create a separate 80 | issue, or a pull-request with the proposed correction. 81 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /proposals/android-extensions-entity-caching.md: -------------------------------------------------------------------------------- 1 | # View holder pattern support and caching options 2 | 3 | * Type: Android Extensions Proposal 4 | * Author: Yan Zhulanow 5 | 6 | ## Summary 7 | 8 | Add the `LayoutContainer` interface and the `@ContainerOptions` annotation in order to configure Android Extensions container options. 9 | 10 | ## Current state 11 | 12 | Android Extensions does not have any configurable options for now. Synthetic properties are available for `Activity`, `Fragment` (Android SDK / support-v4) and `View` classes. 13 | 14 | ## Use cases 15 | 16 | * Change `View` cache implementation or disable caching for a specific class; 17 | * Support the view holder pattern. 18 | 19 | ## Description 20 | 21 | ### `@ContainerOptions` annotation 22 | 23 | Caching options are set using the `@ContainerOptions` annotation. Its declaration: 24 | 25 | ```kotlin 26 | @Target(AnnotationTarget.CLASS) 27 | annotation class ContainerOptions( 28 | val cacheImplementation: CacheImplementation = HASH_MAP 29 | ) 30 | ``` 31 | 32 | Any Kotlin `Activity`, `Fragment`, or `LayoutContainer` classes can be annotated. "Annotation is not applicable" is displayed on the annotation applied to any other Kotlin class. Annotation is not applicable to Java classes. 33 | 34 | The `@ContainerOptions` annotation (as well as other classes/interfaces mentioned in this document) should be placed in the separate JAR artifact available in Maven. The alternative way is described below. 35 | 36 | ### Caching options 37 | 38 | `cacheImplementation` parameter of `@ContainerOptions` has a type of `CacheImplementation`: 39 | 40 | ```kotlin 41 | enum class CacheImplementation { 42 | SPARSE_ARRAY, 43 | HASH_MAP, 44 | NO_CACHE 45 | } 46 | ``` 47 | 48 | `HASH_MAP` is the default implementation (and the only implementation for now). `HashMap` provides constant-time performance for `get()` and `put()`, though the caching involves `int` View identifier boxing. 49 | 50 | As an alternative, `SparseArray` from the Android SDK can be used. `SparseArray` uses binary search to find elements, and it is good for relatively small number of items. Identifier boxing is not needed because keys are primitive integers. 51 | 52 | Also, there might be useful to disable caching for some particular class. `NO_CACHE` value can be used in this case: 53 | 54 | ```kotlin 55 | @ContainerOptions(NO_CACHE) 56 | class MyActivity : Activity() { ... } 57 | ``` 58 | 59 | Cache implementation list is fixed for now. 60 | 61 | ### View holder pattern 62 | 63 | The base idea of the view holder pattern is that you have some base `View`, and you want to get its children: 64 | 65 | ```kotlin 66 | // Declaration site 67 | class MyViewHolder(val baseView: View) { 68 | val firstName = baseView.findViewById(R.id.first_name) 69 | val secondName = baseView.findView(R.id.second_name) 70 | } 71 | 72 | ... 73 | 74 | // Use site 75 | val v: MyViewHolder = MyViewHolder(baseView) 76 | v.firstName.text = user.firstName 77 | v.secondName.text = user.secondName 78 | ``` 79 | 80 | The main advantage of this pattern is that `findViewById()` is called once for each widget. 81 | 82 | We can already use Android Extensions with the `MyViewHolder` class (extension properties for the `View` receiver are already available): 83 | 84 | ```kotlin 85 | v.baseView.first_name.text = user.firstName 86 | ``` 87 | 88 | Though we lose the View caching feature. The solution is to add an `LayoutContainer` interface: 89 | 90 | ```kotlin 91 | interface LayoutContainer { 92 | val containerView: View? 93 | } 94 | ``` 95 | 96 | So the previous code fragment can be written like this: 97 | 98 | ```kotlin 99 | // Declaration site 100 | class MyViewHolder(override val containerView: View): LayoutContainer 101 | 102 | ... 103 | 104 | // Use site 105 | val v = MyViewHolder(baseView) 106 | v.first_name.text = user.firstName 107 | v.second_name.text = user.secondName 108 | ``` 109 | 110 | Extensions properties `first_name` and `second_name` are available also for `LayoutContainer` and placed inside the `kotlinx.android.synthetic..` package. 111 | 112 | As mentioned earlier, `LayoutContainer` implementations can also be annotated with `@ContainerOptions`. 113 | 114 | ## Additional information 115 | 116 | * There should be an extra annotation checker (`@ContainerOptions` is applicable only to `Activity`, `Fragment` or `LayoutContainer` descendants). 117 | 118 | ## Alternatives 119 | 120 | For now there is no runtime dependency for Android Extensions, and the alternative way is to make all described classes synthetic. It requires some (dirty) hacks. 121 | 122 | ## Related issues 123 | 124 | * [KT-9892](https://youtrack.jetbrains.com/issue/KT-9892) Android Extensions: Support view holder pattern and custom View classes. 125 | 126 | * [KT-10542](https://youtrack.jetbrains.com/issue/KT-10542) Android Extensions: No cache for Views. 127 | 128 | ## Future advancements 129 | 130 | New parameters can be added to the `@AndroidEntityOptions` annotation, providing the additional functionality. 131 | 132 | ## Open questions 133 | 134 | * Do we need to change the default cache implementation to `SparseArray`? 135 | * Looks like no, because "It is generally slower than a traditional HashMap, since lookups require a binary search and adds and removes require inserting and deleting entries in the array" ([Android Documentation](https://developer.android.com/reference/android/util/SparseArray.html)) 136 | -------------------------------------------------------------------------------- /proposals/annotation-instantiation.md: -------------------------------------------------------------------------------- 1 | # Instantiation of annotation classes 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Leonid Startsev 5 | * **Status**: In review 6 | * **Prototype**: In progress 7 | * **Issue:** [KT-45395](https://youtrack.jetbrains.com/issue/KT-45395) 8 | 9 | ## Synopsis 10 | 11 | Allow calling constructors of annotation classes in arbitrary code 12 | to obtain an instance of a particular annotation class. 13 | 14 | ## Motivation 15 | 16 | ### 1. Better interoperability with Java 17 | 18 | In Java, annotations are represented as interfaces, and therefore it is possible to implement 19 | those interfaces. Various Java APIs make use of this and may require an instance of `Annotation` interface 20 | to be passed to them (see [KT-25947](https://youtrack.jetbrains.com/issue/KT-25947) for details). 21 | 22 | ### 2. Less kotlin-reflect usage 23 | 24 | While [it's possible to call annotation constructor via reflection](https://youtrack.jetbrains.com/issue/KT-25947#focus=Comments-27-4203054.0-0), such an approach is unobvious, cumbersome and involves usage of Kotlin reflection implementation in a separate kotlin-reflect runtime library. 25 | The ability to directly call annotation constructor would make this task easier. 26 | 27 | ### 3. Better multiplatform support 28 | 29 | While annotations are common in the JVM world, their applications in multiplatform programming are still limited. 30 | Sometimes, it makes user code a second-class citizen compared to specialized solutions, such as a compiler plugin. 31 | For example, while [kotlinx.serialization plugin](extensions/serialization.md) automatically instantiates and stores [SerialInfo annotations](https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization/-serial-info/index.html) in generated serializer, 32 | user-written serializer need to implement and store such annotations manually (see [Kotlin/kotlinx.serialization#328](https://github.com/Kotlin/kotlinx.serialization/issues/328)). 33 | 34 | ## Description 35 | 36 | While it might seem logical to follow Java convention and allow annotation classes to be open, it is clear that all use-cases exist around an annotation class instance. Therefore, it makes sense to provide a mechanism to instantiate an annotation class instead of implementing it. Such an approach also allows complying with [`Annotation`'s contract on equals and hashCode](https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Annotation.html#equals-java.lang.Object-) without additional user code written. 37 | 38 | The most straightforward way to instantiate a class in Kotlin is to call its constructor. This proposal suggests allowing user code to call annotation class constructor to obtain an instance. User code may look like this: 39 | 40 | ```kotlin 41 | annotation class InfoMarker(val info: String) 42 | 43 | fun processInfo(marker: InfoMarker) = ... 44 | 45 | fun main(args: Array) { 46 | if (args.size != 0) 47 | processInfo(getAnnotationReflective(args)) 48 | else 49 | processInfo(InfoMarker("default")) 50 | } 51 | ``` 52 | 53 | Current annotation class' limitations (no members including secondary constructors, no non-vals parameters, etc.) remain intact. 54 | 55 | ### Implementation 56 | 57 | Note that a particular implementation class for JVM is not used in the example and is hidden as an implementation detail. 58 | This allows the compiler to perform certain optimizations — a special synthetic implementation class can be generated on-demand, like with lambdas or SAM 59 | conversions, to avoid additional code generation for every annotation in a project. 60 | Also, on Native, unlike JVM, annotation classes themselves don't have to be interfaces and can be instantiated directly. 61 | 62 | ### Java interop 63 | 64 | With the strategy that generates annotation implementation on-demand, it becomes possible in future 65 | to allow calling constructors of annotations defined in Java code as well. 66 | On the other side, synthetic implementation of Kotlin annotation is not visible to Java code. This is not necessarily a problem because Java clients can still manually implement any annotation in Java way. -------------------------------------------------------------------------------- /proposals/data-class-inheritance.md: -------------------------------------------------------------------------------- 1 | # Data Class Inheritance 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Alexander Udalov 5 | * **Status**: Accepted 6 | * **Prototype**: Implemented in Kotlin 1.1 7 | 8 | Goal: allow `data` classes to inherit from other (non-final) classes. 9 | 10 | ## Feedback 11 | 12 | Discussion of this proposal is held in [this issue](https://github.com/Kotlin/KEEP/issues/31). 13 | 14 | ## Motivation / use cases 15 | 16 | * Algebraic data types, or (in Kotlin terms) sealed hierarchies: 17 | 18 | ``` kotlin 19 | sealed class Something { 20 | object Singleton : Something() 21 | data class Complex(...) : Something() // <-- ! 22 | } 23 | ``` 24 | 25 | https://discuss.kotlinlang.org/t/sealed-classes-design/82 26 | http://stackoverflow.com/questions/35921234/kotlin-sealed-class-cannot-contain-data-classes-why 27 | 28 | ## Description 29 | 30 | Data classes should be allowed to have base classes. Below we explain why it's safe, and what are the exact rules to generate special members in such data classes. 31 | 32 | Note that any data class itself remains to be **final** and so cannot be used as a base class. 33 | 34 | #### Allowing data classes to have base classes 35 | 36 | It seems that there has not been much profit in prohibiting data classes with superclasses. It only helped to reduce confusion in some cases: 37 | * if the superclass has its own primary constructor, it may be confusing because its `val`s are not considered the components of the data class 38 | * we could restrict primary constructors in base classes, but it would still be possible to create properties in the body of a class 39 | * if the superclass has `equals`/`hashCode`, it may be confused with data class' `equals`/`hashCode` 40 | * restricting custom `equals` in the base class breaks a use case when a general implementation of `equals` in the base class makes sense, for example in collections 41 | * in fact, implementing correct equality in a hierarchy is impossible in general 42 | 43 | #### Member generation rules 44 | 45 | The main effect of the `data` modifier on a class is special members generated by the compiler: `equals`, `hashCode`, `toString`, `copy` and `componentN` functions. 46 | 47 | In the case when the base class of a data class has a member `M_b` that may clash with a generated member `M_g`, we should behave as follows: 48 | * if the signatures are override-compatible 49 | * if `M_b` is final, **don't generate** `M_g` 50 | * use case: a general `toString` or `equals` in the base class that uses the public API to render or compare instances 51 | * use case: `toString` of the sealed class wants to do case-analysis on the type of this instead of overriding `toString` in each subclass 52 | * downside: for both of these cases there can't be a partial behavior: either all data-subclasses override a member of the base class, or none do 53 | * if `M_b` is open, **generate** `M_g` to override `M_b` **without notice** 54 | * use case: base class defines component1, 2, 3 to express that any of its cases can be decomposed into (at least) three components, but implementations of components are different in subclasses/subclasses rely on data to implement component functions 55 | * downside: if the user is not aware of some members being generated, they'd be surprised to get such an override 56 | * if the signatures are override-incompatible (e.g. return type of `M_b` is not a supertype of the return type of `M_g`) 57 | * report an error 58 | 59 | #### Examples 60 | 61 | Sealed hierarchy use case: 62 | ``` kotlin 63 | sealed class Either { 64 | data class Left(val value: L) : Either() 65 | data class Right(val value: R) : Either() 66 | } 67 | ``` 68 | 69 | Generation of special members in cases when members are present in the base class: 70 | ``` kotlin 71 | open class Base { 72 | override /* open */ fun hashCode() = 42 73 | override final fun toString() = "Base" 74 | } 75 | 76 | data class Derived(val value: String): Base() 77 | 78 | fun test() { 79 | val d = Derived("Derived") 80 | println(d) // prints "Base" 81 | println(d.hashCode()) // prints hashCode computed in Derived, NOT 42 82 | println(d == Derived("Derived")) // prints true 83 | } 84 | ``` 85 | 86 | Properties of base class' primary constructor do not participate in `componentN` and other special functions: 87 | ``` kotlin 88 | open class Base(val baseParam: Int) 89 | 90 | data class Derived(val dataParam: String) : Base(dataParam.length) 91 | 92 | fun test() { 93 | val d = Derived("OK") 94 | val x = d.component1() // x is String and its value is "OK" 95 | val (y) = d // similarly, y is "OK" 96 | val (a, b) = d // error, no component2 in Derived 97 | } 98 | ``` 99 | 100 | ## Issues to be fixed 101 | 102 | * [KT-10330](https://youtrack.jetbrains.com/issue/KT-10330). 103 | The original issue with the sealed hierarchy use case. 104 | * [KT-11306](https://youtrack.jetbrains.com/issue/KT-11306). 105 | Unless this issue is fixed, it's not possible to specify for example abstract `toString` in the base class and rely on auto-generated members in data subclasses. This can hurt in sealed hierarchies where some of the subclasses are data classes and some are not. 106 | 107 | ## Alternatives 108 | 109 | To support exactly the sealed hierarchy use case, we could allow sealed interfaces (data classes can inherit from interfaces). However: 110 | * it's not possible to store data in an interface 111 | * it's possible to accidentally inherit such interface from Java, which would break exhaustive `when`s 112 | 113 | ## Future improvements 114 | 115 | * Similarly to the base class restriction, disallowing non-`val`/`var` constructor parameters for data classes seems pretty harsh. It looks like such parameters could be allowed at least if they are the last ones, i.e. there are no `val`/`var` parameters after that. This includes `vararg`s and parameters with default values. 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /proposals/destructuring-in-parameters.md: -------------------------------------------------------------------------------- 1 | # Destructuring in Lambda Parameters 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Andrey Breslav 5 | * **Status**: Implemented in Kotlin 1.1 6 | * **Discussion**: [KEEP-32](https://github.com/Kotlin/KEEP/issues/32) 7 | 8 | ## Goal 9 | 10 | Support destructuring declarations in parameters of lambdas. 11 | 12 | NOTE: Support in parameters of functions (declarations and expressions), constructors and setters will be discussed for future-proof design, but is not proposed to be implemented at this time. 13 | 14 | ## Use cases 15 | 16 | To be supported now: 17 | 18 | ``` kotlin 19 | // decomposing pairs in a lambda 20 | listOfPairs.map { 21 | (a, b) -> a + b 22 | } 23 | ``` 24 | 25 | May be supported later: 26 | 27 | ``` kotlin 28 | // decompose a parameter: 29 | fun foo((a, b): Pair, c: Bar) 30 | // can be called as 31 | foo(pair, bar) 32 | 33 | // decompose a constructor parameter 34 | class C(val (a, b): Pair) {} 35 | ``` 36 | 37 | ## Syntax 38 | 39 | We allow parentheses in lambda parameter lists: 40 | 41 | In Kotlin 1.0 the syntax for a lambda is: 42 | 43 | ``` 44 | functionLiteral 45 | : "{" statements "}" 46 | : "{" (SimpleName (":" type)?){","} "->" statements "}" 47 | ; 48 | ``` 49 | 50 | Here we propose to extend it: 51 | 52 | ``` 53 | functionLiteral 54 | : "{" statements "}" 55 | : "{" lambdaParameter{","} "->" statements "}" 56 | ; 57 | 58 | lambdaParameter 59 | : variableDeclarationEntry 60 | : multipleVariableDeclarations (":" type)? 61 | ; 62 | 63 | // already defined, shown for reference 64 | 65 | variableDeclarationEntry 66 | : SimpleName (":" type)? 67 | ; 68 | 69 | multipleVariableDeclarations 70 | : "(" variableDeclarationEntry{","} ")" 71 | ; 72 | ``` 73 | 74 | > Note: while modifiers in front of lambda parameters are mentioned in the grammar for 1.0, they are not actually supported by the parser. 75 | > If we ever support modifiers here, we should allow them in front of individual components, but for now, the actual rule should be without them 76 | 77 | Examples: 78 | 79 | ``` kotlin 80 | { a -> ... } // one parameter 81 | { a, b -> ... } // two parameters 82 | { (a, b) -> ... } // a destructured pair 83 | { (a, b), c -> ... } // a destructured pair and another parameter 84 | ``` 85 | 86 | > Question: Should we support nested destructuring for parameters? 87 | > ``` 88 | { ((a, b), c) -> ... } // a destructured pair whose first component is a pair 89 | ``` 90 | > NOTE: this is not supported for destructuring val's and var's 91 | 92 | A type may be specified for the whole destructured parameter, for its individual components (independently), or both: 93 | 94 | ``` kotlin 95 | { (a, b: B) -> ... } 96 | { (a, b): Pair -> ... } 97 | { (a: A, b): Pair -> ... } 98 | ``` 99 | 100 | > Note: no changes to the syntax of function types is needed since the syntax for call sites (where a lambda is invoked) is not changed. 101 | 102 | ### Semantics and checks 103 | 104 | Semantically, destructured parameters work as if they are normal parameters and a destructuring assignments are performed before the body of the lambda, in the left-to-right order: 105 | 106 | ``` kotlin 107 | { 108 | (a, b), (c, d) -> 109 | body() 110 | } 111 | ``` 112 | 113 | is translated to 114 | 115 | ``` 116 | { 117 | _p1, _p2 -> 118 | val (a, b) = _p1 119 | val (c, d) = _p2 120 | body() 121 | } 122 | ``` 123 | 124 | **Typing rule**: Types of the components must be compatible with component-functions (`componentN()`) resolvable on corresponding parameters. When such functions can not be applied or their return types are not assignable to declared types of components, an error is reported. 125 | 126 | Component-functions are resolved in the same scope as the first statement of the lambda. 127 | The reason is that we want to preserve desugaring semantics described above. 128 | 129 | But the following example still must not compile because 130 | the relevant parameters are not and cannot be marked as operators. 131 | ``` kotlin 132 | { 133 | component1: P.() -> A, 134 | component2: P.() -> B, 135 | (a, b): P // error: component functions not resolved 136 | -> 137 | ... 138 | ``` 139 | 140 | When type inference is concerned with the shape of a lambda, a destructured parameter acts as a normal one (single), and its type may be used if declared. Types of components do not affect type inference even when specified: there's no way to know what type would be destructured into the given component types. 141 | 142 | **Naming rule**: all names declared in a parameter list of the same lambda (normal parameters and components of destructured ones) must be unique, i.e. this is an error: `{ (a, b), a -> ... }` 143 | 144 | 145 | ## IDE changes 146 | 147 | - Intention actions for lambda parameters: 148 | - introduce destructuring 149 | - collapse destructuring 150 | - Inspection to detect a manual destructuring, e.g.: 151 | 152 | ``` kotlin 153 | { 154 | val (a, b) = it 155 | ... 156 | } 157 | ``` 158 | - + a quick-fix to replace this with `{ (a, b) -> ... }` 159 | - Actions to transform a for-loop into map/fiter/... should now support transferring destructured variables between `for` and lambdas. 160 | - Adjustments may be needed in *Change signature* for lambdas 161 | -------------------------------------------------------------------------------- /proposals/enhancing-main-convention.md: -------------------------------------------------------------------------------- 1 | # Enhancing main entry point convention 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Denis Zharkov 5 | * **Contributors**: Andrey Breslav, Roman Elizarov 6 | * **Status**: Submitted 7 | * **Prototype**: Implemented in 1.3-RC 8 | * **Discussion**: [KEEP-158](https://github.com/Kotlin/KEEP/issues/158) 9 | 10 | ## Summary 11 | 12 | - Support top-level `fun main()` as an entry point to a program 13 | - Support top-level `suspend fun main(args: Array)` as an entry point to a program 14 | - `args` parameter can be omitted 15 | - Related platforms: JVM, JS 16 | 17 | ## Motivation 18 | 19 | ### Making `args` an optional parameter 20 | We'd like to minimize the amount of boilerplate needed to write a small program in Kotlin. Especially, it's important when 21 | the program is the first one written in the language, e.g. "Hello, world" or other small samples made for educational 22 | purposes. 23 | 24 | And mostly, such programs don't need the `args` parameter. 25 | 26 | ### Allowing suspend modifier 27 | - Again, it might be useful when one's learning the language and starting experimenting with coroutines 28 | - Also, one might need it when writing a small utility program using some kind of async API, 29 | e.g. [ktor-client](https://github.com/ktorio/ktor/tree/master/ktor-client) 30 | 31 | ## Parameterless main 32 | 33 | ### Rules and semantics 34 | - A public top-level function named `main` is considered as an entry point if there's no other entry point with 35 | an array parameter (e.g. `suspend fun main(args: Array)`, `suspend fun main(vararg array: String)`, etc.) in the containing file 36 | - Only functions with `Unit` as a return type are considered 37 | - `suspend` modifier is also allowed in the case 38 | - As for other entry points, do not report conflicting overloads for 39 | parameterless main-functions in the same package if they belong to different files 40 | - Unlike main-functions with parameters, cases like the following are unsupported: 41 | ```kotlin 42 | @JvmName("main") 43 | fun noMain() {} // is not an entry point 44 | ``` 45 | - Once a parameterless main is used as an entry point, received program arguments are just ignored 46 | 47 | ### Implementation details on JVM 48 | Since being compiled straightforwardly a parameterless main function doesn't satisfy the requirements for entry points 49 | on JVM, another synthetic public static method with a `String[]` needs to be generated by the compiler. 50 | 51 | So, the following code: 52 | ```kotlin 53 | fun main() { 54 | println("Hello, world!") 55 | } 56 | ``` 57 | should be compiled in something like: 58 | ```java 59 | class FileNameKt { 60 | public static void main() { 61 | System.out.println("Hello, world!"); 62 | } 63 | 64 | /* ACC_SYNTHETIC */ 65 | public static void main(String[] args){ 66 | main(); 67 | } 68 | } 69 | ``` 70 | 71 | Presence of `ACC_SYNTHETIC` is needed for several reasons: 72 | - This declaration is not assumed to be called from Java 73 | - Debugger may use this as an indicator to skip the relevant frame 74 | 75 | ### Implementation details on JS 76 | In Kotlin/JS the invocation of the `main` function is controlled by the compiler itself. 77 | Normally it is just another JavaScript statement `main([]);` at the end of the module definition. 78 | 79 | In case of a main function with no parameters no arguments are passed, i.e. there is a `main();` 80 | statement at the end of the module definition in the generated JavaScript file. 81 | 82 | ## Suspend main convention 83 | 84 | ### Rules and semantics 85 | - A public top-level function named `main` with `suspend` modifier is considered as an entry point if it would be 86 | an entry point without the modifier. Examples: 87 | ```kotlin 88 | suspend fun main(args: Array) // OK 89 | suspend fun main(args: Array) // OK 90 | suspend fun main(vararg args: String) // OK 91 | suspend fun main() // OK, by the rules from the previous section 92 | 93 | @JvmName("main") 94 | suspend fun noMain(args: Array) // not an entry point: wrong name 95 | 96 | object A { 97 | suspend fun main(args: Array) // not an entry point: non-top-level 98 | } 99 | ``` 100 | 101 | - Semantically, the behavior of such entry point may be slightly different on different platforms. 102 | Briefly, it should work like the body of the entry point is passed as a lambda to [`runBlocking`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run-blocking.html) 103 | from kotlinx.coroutines. 104 | Mainly, the idea is the following: 105 | - If no suspension happens, the execution should be the same as for regular non-suspend main function 106 | - Otherwise, the beginning of the function body should be executed in the regular main thread until the first 107 | suspension point. Then the program should be run until the started coroutine is not completed 108 | - If the coroutine was completed normally then the whole program should be terminated normally, as well 109 | - If the coroutine was completed with an exception then the whole program 110 | should be terminated just like the exception was thrown synchronously 111 | and wasn't caught anywhere including the entry point 112 | 113 | 114 | ### Implementation details on JVM 115 | Because suspend functions have an additional `Continuation` parameter they can't be a valid suspension point on JVM, too. 116 | Just like in case of parameterless main, the compiler generates a synthetic bridge having a valid entry-point signature 117 | and delegating to the implementation. 118 | 119 | The content of a generated bridge is similar to the following 120 | ```kotlin 121 | /* ACC_SYNTHETIC */ 122 | fun main(args: Array) { 123 | kotlin.coroutines.jvm.internal.runSuspend { 124 | main(args) // calling the suspend version 125 | } 126 | } 127 | ``` 128 | 129 | and `runSuspend` is defined like: 130 | ```kotlin 131 | @SinceKotlin("1.3") 132 | internal fun runSuspend(block: suspend () -> Unit) { 133 | val run = RunSuspend() 134 | block.startCoroutine(run) 135 | run.await() 136 | } 137 | 138 | private class RunSuspend : Continuation { 139 | override val context: CoroutineContext 140 | get() = EmptyCoroutineContext 141 | 142 | var result: Result? = null 143 | 144 | override fun resumeWith(result: Result) = synchronized(this) { 145 | this.result = result 146 | (this as Object).notifyAll() 147 | } 148 | 149 | fun await() = synchronized(this) { 150 | while (true) { 151 | when (val result = this.result) { 152 | null -> (this as Object).wait() 153 | else -> { 154 | result.getOrThrow() // throw up failure 155 | return 156 | } 157 | } 158 | } 159 | } 160 | } 161 | ``` 162 | 163 | ### Implementation details on JS 164 | Similar to the previous case, Kotlin/JS generates a direct invocation to the 165 | main function in the generated JavaScript file. 166 | 167 | In case of `suspend fun main(args: Array)` if looks like this: 168 | 169 | ```js 170 | main([], kotlin.coroutines.js.internal.EmptyContinuation); 171 | ``` 172 | 173 | where `EmptyContinuation` is defined as: 174 | 175 | ```kotlin 176 | @PublishedApi 177 | internal val EmptyContinuation = Continuation(EmptyCoroutineContext) { result -> 178 | result.getOrThrow() 179 | } 180 | ``` 181 | 182 | Basically it launches the `suspend main` function with an empty context, and rethrows 183 | any exceptions that have occured during it's execution. 184 | 185 | In case of a suspend `main` with no parameters, only the continuation is passed: 186 | ```js 187 | main(kotlin.coroutines.js.internal.EmptyContinuation); 188 | ``` 189 | 190 | ## Entry points in singleton instances 191 | Both entry points convention enhancements are only applied to top-level functions, while on JVM one can declare an entry 192 | point within an object, e.g.: 193 | ```kotlin 194 | object A { 195 | @JvmStatic 196 | fun main(args: Array) { 197 | 198 | } 199 | } 200 | ``` 201 | 202 | This proposal does not extend the convention for objects for several reasons: 203 | - Such functions are not Kotlin-way to define an entry point. At least, because they are clearly platform-specific and mostly 204 | they remain after Java-to-Kotlin conversion. 205 | - For sake of simplicity 206 | - There's no *actual* support for such entry points in the language: these functions themselves satisfy signature requirements 207 | on JVM without any special treatment by the compiler 208 | 209 | ## Remaining questions/issues 210 | - New enhanced entry points convention is not supported by Kotlin Native because it's unclear where a body of a suspend main 211 | functions should be executed on different targets (iOS, linux, windows, etc.) 212 | -------------------------------------------------------------------------------- /proposals/experimental.md: -------------------------------------------------------------------------------- 1 | Discussion is moved to [this file](opt-in.md) -------------------------------------------------------------------------------- /proposals/explicit-api-mode.md: -------------------------------------------------------------------------------- 1 | # Explicit API mode 2 | 3 | * **Type**: Design proposal 4 | * **Authors**: Ilya Gorbunov, Leonid Startsev 5 | * **Contributors**: Roman Elizarov, Vsevolod Tolstopyatov 6 | * **Status**: Implemented in 1.4 7 | * **Original proposal and discussion**: [KEEP-45](https://github.com/Kotlin/KEEP/issues/45) 8 | 9 | ## Synopsis 10 | 11 | Provide an 'Explicit API' mode in the compiler which helps library authoring. 12 | Such mode should prevent delivery of unintended public API/ABI to the clients by requiring explicit visibility modifier and explicit return types for public declarations. 13 | 14 | ## Motivation 15 | 16 | There were a couple of hot discussions about the default visibility before release: 17 | 18 | * https://discuss.kotlinlang.org/t/public-by-default-for-classes/110 19 | * https://discuss.kotlinlang.org/t/kotlins-default-visibility-should-be-internal/1400 20 | 21 | While such a decision is convenient for application development, the main concern against public-by-default visibility was that it becomes too easy for library authors to expose something accidentally, release it, and then have to make breaking changes to hide it back. 22 | 23 | ## Proposal 24 | 25 | Introduce 'Explicit API' compiler mode. Compilation in such mode differs from the default mode in the following aspects: 26 | 27 | * Compiler requires you to specify explicit visibility for a declaration when leaving default visibility would result in exposing that declaration to the public API. 28 | 29 | * Compiler requires you to specify the explicit type of property/function when it is exposed to the public/published API. 30 | 31 | * Compiler requires you to explicitly [propagate experimental status](https://kotlinlang.org/docs/reference/experimental.html#propagating-use) for functions which contain experimental types in the signature. 32 | 33 | * Compiler warns you when declaration exposed to public API does not have a KDoc. 34 | 35 | ### Public API definition 36 | 37 | #### Classes 38 | 39 | A class is considered to be effectively public if all of the following conditions are met: 40 | 41 | - it has one of the following visibilities in Kotlin: 42 | - no visibility 43 | - *public* 44 | - *protected* 45 | - it isn't a local class 46 | - in case if the class is a member in another class, it is contained in the *effectively public* class 47 | - in case if the class is a protected member in another class, it is contained in the *non-final* class 48 | 49 | #### Members 50 | 51 | A member of the class (i.e. a field or a method) is considered to be effectively public 52 | if all of the following conditions are met: 53 | 54 | - it has one of the following visibilities in Kotlin: 55 | - no visibility 56 | - *public* 57 | - *protected* 58 | 59 | > Note that Kotlin visibility of a field exposed by `lateinit` property is the visibility of its setter. 60 | 61 | - in case if the member is protected, it is contained in *non-final* class 62 | 63 | ### Published API 64 | 65 | If a declaration has *internal* visibility modifier, and the declaration itself or its containing class is annotated with `PublishedApi`, and all other conditions from previous sections are met, it is considered **published** API. 66 | 67 | As with public API, you should avoid making [binary incompatible changes](https://github.com/JetBrains/kotlin/blob/master/libraries/tools/binary-compatibility-validator/ReadMe.md#what-makes-an-incompatible-change-to-the-public-binary-api) to published API. 68 | 69 | However, published API is usually not visible in the sources from the point of view of a library client. Therefore, the compiler in 'Explicit API' mode will not complain about missing KDoc or missing visibility modifier (because it is `internal` anyway). Explicit return type is still required for published API to prevent implementation details exposure. 70 | 71 | ### Opt-in requirements 72 | 73 | When one is writing a library, they should use `@RequiresOptIn(...)` annotation to propagate the opt-in requirements of declarations they use (previously known as experimental declarations). 74 | If the types that require opt-in are used as implementation details across all library, it might be convenient to mark the whole module with `-Xopt-in`. 75 | In that case, it would be easy to forget to mark the corresponding public API as propagative. 76 | Therefore, in Explicit API mode, the compiler would still require explicit `@RequireOptIn` or `@OptIn` annotation on a declaration with opt-in requiring types in the signature, even if the whole module opts in via `-Xopt-in`. 77 | 78 | ### Inspection exclusions 79 | 80 | After careful review, we decided that some declarations should not require explicit visibility modifiers even in 'Explicit API' mode for the sake of readability, simplicity, and common sense. These declarations are: 81 | 82 | 1. Primary constructors 83 | 84 | Because explicit visibility also requires you to insert keyword `constructor`. 85 | 86 | 2. Properties of data classes and annotations 87 | 88 | Because such classes usually fit in one line of code and do not profit much from information hiding. 89 | 90 | 3. Methods marked with `override` 91 | 92 | Because default visibility for such methods is the visibility of an overridden method. 93 | 94 | 4. Property getters and setters 95 | 96 | Because getters can't change visibility and setter-only explicit visibility looks ugly. 97 | 98 | However, if you still want to insert an explicit visibility modifier for such declarations, it would not be marked as redundant by IDE. 99 | 100 | ## Implementation 101 | 102 | This mode is enabled by the compiler flag `-Xexplicit-api={strict|warning}`. `strict` state of the flag means that compiler will issue errors when public API declaration does not have explicit visibility or explicit return type. 103 | `warning` means that the compiler will issue warnings (this will help migration). 104 | Note: missing KDoc is always a warning, regardless of the state of the flag. 105 | 106 | To ease setting up this mode, a DSL is provided in the Kotlin Gradle plugin: 107 | 108 | *Groovy Syntax* 109 | 110 | ```gradle 111 | kotlin { 112 | // convenience methods 113 | explicitApi() 114 | // or 115 | explicitApiWarning() 116 | 117 | // setting level explicitly 118 | explicitApi = 'strict' 119 | // or 120 | explicitApi = 'warning' 121 | } 122 | ``` 123 | 124 | *Kotlin Syntax* 125 | 126 | ```kotlin 127 | import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode 128 | 129 | kotlin { 130 | // convenience methods 131 | explicitApi() 132 | // or 133 | explicitApiWarning() 134 | 135 | // setting level explicitly 136 | explicitApi = ExplicitApiMode.Strict 137 | // or 138 | explicitApi = ExplicitApiMode.Warning 139 | } 140 | ``` 141 | 142 | This is a per-module setting that enables explicit API mode only for module production sources. 143 | 144 | Maven plugin option is TBD. 145 | 146 | Kotlin plugin in IntelliJ IDEA would recognize that explicit mode is enabled and would offer corresponding intentions and quick-fixes. 147 | 148 | The following **IDE** inspections would be disabled either because they're redundant or duplicated in the compiler: 149 | 150 | * 'Redundant visibility modifier' for public API declarations 151 | * 'Public API declaration has implicit return type' (would be replaced with compiler diagnostic) 152 | * 'Missing KDoc comment for public declaration' (would be replaced with compiler diagnostic) 153 | 154 | Effectively, 'disabled in IDE/replaced with compiler diagnostic' means that you would not be able to control these inspections via 'Preferences - Editor - Inspections'. 155 | 156 | ## Alternatives 157 | 158 | Besides embedding such mode as a compiler flag, the following alternatives were considered and discarded: 159 | 160 | 1. Compiler plugin 161 | 162 | While it is possible to make this functionality as a separate plugin, the lack of stability and consistency in the compiler plugin API (because there is no official public definition of plugin API/compiler surface, actually) would require a lot of efforts to support this relatively small feature. 163 | 164 | 2. External tool 165 | 166 | An external tool like a style checker may look like a proper solution for this problem, but it requires much more additional setup than one DSL parameter, which pushes adoption back. Another big problem is that such tools re-analyze whole code one more time, which leads to increased build times, big size of the tool, or incorrect results. 167 | 168 | ## Compatibility 169 | 170 | The explicit mode doesn't change the semantics of the correct code and does not affect bytecode generation, so it's safe from the standpoint of compatibility. 171 | For example, if some code was compiled once in the explicit mode and later without explicit mode, the result should be the same. 172 | 173 | However, this mode could make previously correct code compile with errors (the code that had the default visibility declarations and inferred types in public API). 174 | That effect is similar to the "Treat warning as errors" option; but the separate compiler flag allows fine-grained control (since we can't turn _particular_ warnings into errors). -------------------------------------------------------------------------------- /proposals/extensions/opening-classes-for-frameworks.md: -------------------------------------------------------------------------------- 1 | # Opening classes for frameworks 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Andrey Breslav 5 | * **Contributors**: Dmitry Jemerov, Sébastien Deleuze, Yan Zhulanow, Andy Wilkinson 6 | * **Status**: Under consideration 7 | * **Prototype**: Not started 8 | 9 | ## Feedback 10 | 11 | Discussion of this proposal is held in [this issue](TODO). 12 | 13 | ## Synopsis 14 | 15 | Kotlin has classes and their members `final` by default. This has been causing debates that are summarized in [this forum post](https://discuss.kotlinlang.org/t/a-bit-about-picking-defaults/1418). While all the reasoning behind this default remains valid, there are use cases that tend to be painful, most notable are frameworks using CGLib proxies (e.g. Spring AOP, Mockito) and Gradle that decorates task classes at runtime. 16 | 17 | We propose to enable a compilation strategy that does not make classes and their members `final` if they are marked with framework-specific annotations, such as `@Service`, `@Repository`, `@Bean`, `@Configuration`. 18 | 19 | ## References 20 | 21 | - [KT-12149](https://youtrack.jetbrains.com/issue/KT-12149) Provide a way to avoid mandatory open qualifier for CGLIB proxies 22 | - [KT-6256](https://youtrack.jetbrains.com/issue/KT-6256) Allow a class to be open by default 23 | - A. Breslav, [A bit about picking defaults](https://discuss.kotlinlang.org/t/a-bit-about-picking-defaults/1418) 24 | - [A forum thread on `final` vs `open` by default](https://discuss.kotlinlang.org/t/classes-final-by-default/166) 25 | - [~~KT-10759~~](https://youtrack.jetbrains.com/issue/KT-10759) CGLiib proxy nulls out dependencies 26 | - [~~KT-11098~~](https://youtrack.jetbrains.com/issue/KT-11098) Add quick fix for "Spring @Configuration/@Component annotated classes or @Bean annotated methods should be open" 27 | 28 | > There's a somewhat similar issue with default constructors and JPA: 29 | >- [Hibernate/JPA](http://stackoverflow.com/questions/32038177/kotlin-with-jpa-default-constructor-hell) 30 | >- [Hibernate + Guice Persist](https://discuss.kotlinlang.org/t/jpa-guice-gotchas/425) 31 | >- [JPA](https://discuss.kotlinlang.org/t/feature-request-a-modifier-annotation-for-data-classes-to-provide-a-non-arg-constructor-on-jvm/1549/4) 32 | >- [Sugar ORM](https://discuss.kotlinlang.org/t/using-sugar-orm-with-kotlin/439/4) 33 | > 34 | > And another one about [SAM conversions vs extension function types](https://youtrack.jetbrains.com/issue/KT-12848). 35 | 36 | ## Possible implementations 37 | 38 | Essentially, we need to let the Kotlin compiler know that certain framework-specific annotations mean "make this class and all of its members `open`" 39 | 40 | ### Meta-annotation 41 | 42 | One option is to provide a meta-annotation in the Kotlin Standard Library, e.g. `@kotlin.Open`, as suggested [here](https://youtrack.jetbrains.com/issue/KT-12149#comment=27-1422592). Then, framework authors would have to annotate their annotation declarations with `@kotlin.Open`, which at least adds a dependency on `kotlin-stdlib` and thus is too intrusive. 43 | 44 | ### META-INF file 45 | 46 | A framework could provide a text file with a list of such special annotations in the `META-INF` directory of its artifacts. As this does not add dependencies, this is less of a problem than the meta-annotation case. 47 | 48 | Example (`META-INF/kotlin-open-annotations.txt` - file name is subject to discussion): 49 | 50 | ``` 51 | org.springframework.context.annotation.Bean 52 | org.springframework.context.annotation.Configuration 53 | org.springframework.stereotype.Service 54 | org.springframework.stereotype.Repository 55 | ``` 56 | 57 | - **pro**: even if the framework vendor does not include such metadata with the original artifacts, one can add a JAR containing only this file on the class path, and it will work. 58 | - **con?**: will duplicate file names in the full list of entries be an issue here? 59 | - **con**: the compiler will have to scan the entire class path to collect the list of all such annotations. 60 | - **con**: an ad hoc file format for a very narrow issue 61 | - may be generalized to a proper module configuration file: default imports, module name, language level, etc. 62 | 63 | ### Compiler option 64 | 65 | A list of special annotations can be passed as a compiler option (supported in CLI and build tools). 66 | 67 | - **con**: this options would have to be specified again and again for each project. 68 | - This can be mitigated to some extent by having something like `kotlin-maven-spring` plugin that adds the appropriate configuration to the standard Kotlin plugin for Maven/Gradle. 69 | 70 | ### Compiler plugin 71 | 72 | We could make framework-specific compiler plugins that register the appropriate annotation lists. The plugins themselves would be rather simple. They can be configured with one line in a build file (Maven/Gradle). 73 | 74 | - **pro**: very local solution for a rather local problem 75 | - **pro**: no changes in the CLI, i.e. it's not a big compiler features that deserves a whole command line switch 76 | - **con**: the compiler plugin API that enables such plugins is public API nevertheless (although very generic), so it has to be maintained more or less indefinitely 77 | 78 | > This can be combined with the "Compiler option" approach: compiler plugins can contribute options. Then, there will be only one plugin (provided by us), that can use unstable internal APIs, but its command line interface will be stable. 79 | 80 | ## Rules and semantics 81 | 82 | - A class marked with an opening annotation becomes `open` unless it's explicitly marked `final`. 83 | - what about `data` classes? 84 | - Members of such classes become `open` unless explicitly marked `final` 85 | - what about auto-generated members in `data` classes, such as `copy()`, that are final? 86 | > Entity classes cannot have the [data] annotation, because that generates a final copy() method EVEN if the class is marked open. If there are any final methods at all, the magic seems to fail. (From [here](https://discuss.kotlinlang.org/t/jpa-guice-gotchas/425).) 87 | 88 | 89 | > An alternative approach would be to treat these members as `final` at compile time, but emit them as `open` in the byte code, but then many analyses that rely on finality will break, e.g. smart casts. 90 | 91 | ## Alternative approaches 92 | 93 | #### Modifier: `allopen` 94 | 95 | We could abandon the annotation based approach altogether and add a special modifier, `allopen`, to the language, that makes the class and all of its members `open` by default. 96 | 97 | - **con**: in practice, the vast majority of call sites that have `allopen` will also have a framework-specific annotation, such as `@Service`. This looks like a lot of ceremony. 98 | 99 | #### Design guidelines 100 | 101 | A program can be designed so that mocking/proxying of concrete classes is not needed (e.g. using interfaces). 102 | 103 | #### JarOpener 104 | 105 | A post-processor that flips flags in the byte code: [JarOpener](https://discuss.kotlinlang.org/t/classes-final-by-default/166/39) 106 | 107 | ## Open questions 108 | 109 | - 110 | 111 | ## Arguments against this proposal 112 | 113 | - This is not like anything we've done before: no meta-annotation mechanism has any such effect anywhere in Kotlin 114 | - This looks a bit ad hoc 115 | -------------------------------------------------------------------------------- /proposals/functional-types-with-big-arity-on-jvm.md: -------------------------------------------------------------------------------- 1 | # Functional types with big arity on JVM 2 | 3 | Discussion of this proposal is held in [this issue](https://github.com/Kotlin/KEEP/issues/107). 4 | 5 | This proposal describes a way to support functional types and lambdas that take 23 or more parameters in Kotlin/JVM. It's largely based on a previous (outdated) document with description of a pre-1.0 reform of functional types: [spec-docs/function-types.md](https://github.com/JetBrains/kotlin/blob/1.2.40/spec-docs/function-types.md). 6 | 7 | ## Motivation 8 | 9 | In Kotlin, functional types are represented as generic classes taking different number of parameters: `Function0`, `Function1`, `Function2`, ... This approach has a problem in that this list is finite, and it currently ends with `Function22`. Ideally, we'd like to support functions of arbitrary arity in Kotlin. In practice, a JVM method cannot have more that 255 parameters, so this is the limit for us as well. 10 | 11 | Related issue: [KT-13764](https://youtrack.jetbrains.com/issue/KT-13764) Support lambdas and function references for arities bigger than 22 12 | 13 | ## Proposal 14 | 15 | The proposal is to use one class internally for all functional types with arity > 22: 16 | 17 | ```kotlin 18 | package kotlin.jvm.functions 19 | 20 | @SinceKotlin("1.3") 21 | interface FunctionN : Function { 22 | operator fun invoke(vararg args: Any?): R 23 | 24 | override val arity: Int 25 | } 26 | ``` 27 | 28 | It's declared in package `kotlin.jvm.functions` where all other JVM function classes are, which is the package that is not supposed to be used in Kotlin sources but could be used in Java (see Java interop below). 29 | 30 | Any Kotlin's functional type with big arity is erased to this type on the JVM, and the compiler wraps all arguments into array at each call site of `invoke`: 31 | 32 | ```kotlin 33 | // In generated bytecode, this function is "call(Lkotlin/jvm/functions/FunctionN;)V" 34 | fun call(block: (Any, Any, ... /* 42 more */, Any) -> Any) { 35 | // Here, we load all arguments on the stack, then wrap them into array and then 36 | // invoke Lkotlin/jvm/functions/FunctionN.invoke([Ljava/lang/Object;)Ljava/lang/Object; 37 | block(Any(), Any(), ..., Any()) 38 | } 39 | ``` 40 | 41 | A lambda with big arity is compiled to a subclass of `Lambda` and `FunctionN`. At the beginning of its `invoke`, the generated bytecode checks if the length of the passed vararg array is equal to the function arity: 42 | 43 | ```kotlin 44 | val lambda = { p1: Any, p2: Any, ..., p42: Any -> 45 | // In generated bytecode of this lambda class, there are two methods: 46 | // 1) invoke(Ljava/lang/Object;Ljava/lang/Object;...Ljava/lang/Object;)V, 47 | // which contains this lambda body, generated as a normal lambda. 48 | // 2) invoke([L/java/lang/Object;)Ljava/lang/Object;, 49 | // which is a bridge implementing FunctionN.invoke, where we do 50 | // ALOAD 0 + ARRAYLENGTH + throw IAE if it's not 42, 51 | // and then unpack arguments from the array and call the first invoke 52 | ... 53 | } 54 | ``` 55 | 56 | Similar algorithm is used when generating anonymous function reference classes for functions that take 23 or more parameters. 57 | 58 | ## Type checks and casts 59 | 60 | Since Kotlin 1.0, `is`/`as` on functional types on JVM work via an internal runtime function [`TypeIntrinsics.isFunctionOfArity`](https://github.com/JetBrains/kotlin/blob/1.2.50/libraries/stdlib/jvm/runtime/kotlin/jvm/internal/TypeIntrinsics.java#L335), which checks that the object is an instance of `Function` and its `arity` value is equal to the arity on the right-hand side of `is`/`as`. This code needs no modification with regard to this proposal, it'll work automatically with lambdas and references of big arities. 61 | 62 | ## Java interop 63 | 64 | To pass a lambda with big arity from Java to Kotlin, one would have to implement both of the `FunctionN`'s methods: 65 | 66 | ```java 67 | class JavaClass { 68 | void test() { 69 | CallKt.call(new FunctionN() { 70 | @Override 71 | public int getArity() { return 42; } 72 | 73 | @Override 74 | public String invoke(Object... args) { 75 | // Checking the vararg size is a part of the contract of FunctionN.invoke 76 | if (args.length != 42) throw new IllegalArgumentException(); 77 | 78 | ... 79 | } 80 | }); 81 | } 82 | } 83 | ``` 84 | 85 | Note that the Java code is required to check the array length of `args` at the beginning of `invoke` manually. This requirement will be documented in the contract of `FunctionN.invoke`. 86 | 87 | If the `FunctionN` type is used in a signature, it will be seen as a "foreign" type and not as a true Kotlin functional type. 88 | 89 | ## Future improvements 90 | 91 | For Java interop, we could introduce an annotation `Arity` with one integer parameter. This annotation would be used in Java sources on the `FunctionN` type usage or on the corresponding declaration, to make the Kotlin compiler treat that type usage as a functional type with that arity. For example, `@Arity(42) FunctionN` in a Java signature would be treated as `(Any?, Any?, ..., Any?) -> String!` in Kotlin. This is a topic of a future proposal. 92 | 93 | -------------------------------------------------------------------------------- /proposals/generic-values-and-valueof-for-enums.md: -------------------------------------------------------------------------------- 1 | # Generic values() and valueOf() for enums 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Andrey Breslav 5 | * **Contributors**: Stanislav Erokhin, Alexander Udalov, Michael Bogdanov 6 | * **Status**: Implemented in 1.1-M03 7 | 8 | ## Feedback 9 | 10 | Discussion of this proposal is held in [this issue](https://github.com/Kotlin/KEEP/issues/61). 11 | 12 | ## Synopsis 13 | 14 | Generic operations on enums naturally involve retrieving all enum values or converting a string into a value. Example: 15 | 16 | ``` kotlin 17 | // Print options on the screen 18 | fun > renderOptions(render: (T) -> String) { 19 | val values = /* get T.values() somehow */ 20 | for (v in values) { 21 | println(" * " + render(v)) 22 | } 23 | } 24 | ``` 25 | 26 | Currently, there's no performant way of retrieving `values()`/`valueOf()` from an enum that is given as a generic parameter. This can be done through reflection, but it would be too slow for many use cases. 27 | 28 | > The initial YouTrack issue: 29 | >- *[KT-10569](https://youtrack.jetbrains.com/issue/KT-10569) Cannot iterate over values of an enum class when it is used as a generic parameter* 30 | 31 | We propose to support `enumValues()`/`enumValueOf()` for reified type parameters that extend `Enum` through a compiler intrinsic that would generate appropriate static calls to the particular enum class. 32 | 33 | ## References 34 | 35 | - [KT-10569](https://youtrack.jetbrains.com/issue/KT-10569) Cannot iterate over values of an enum class when it is used as a generic parameter 36 | - [KT-7358](https://youtrack.jetbrains.com/issue/KT-7358) Implicit enum companion object 37 | - [KT-5191](https://youtrack.jetbrains.com/issue/KT-5191) Enum.valueOf couldn't be called 38 | 39 | ## Syntax / Implementation options 40 | 41 | There are several options of expressing this in the language. Note that this is not a major feature, but rather an annoying pain point that needs to be fixed, but is not necessarily too sensitive to the elegance or generality of the solution. 42 | 43 | ### Option 1. Intrinsic `Enum.values()` 44 | 45 | We can define the following functions in the standard library: 46 | 47 | ``` kotlin 48 | public inline fun > enumValues(): Array 49 | 50 | public inline fun > enumValueOf(name: String): T 51 | ``` 52 | 53 | Both should be intrinsics and the back-end should emit static calls to the `values()`/`valueOf()` methods of the actual enum class passed for `E` instead of inlining the bodies of these functions. 54 | 55 | This only requires changes to the back-end(s). 56 | 57 | ### Option 2. Magic `E.values()` 58 | 59 | We could do some front-end magic and add synthetic `values()` and `valueOf()` to the static scope of each `>`. The back-ends should still treat such calls specially. 60 | 61 | ### Option 3. Companion object constraints 62 | 63 | If any enum had a companion object, we could make it implement a special interface: 64 | 65 | ``` kotlin 66 | public interface EnumValues> { 67 | fun values(): Array 68 | fun valueOf(name: String): E 69 | } 70 | ``` 71 | 72 | Then, by supporting constraints on companion objects ([KT-7358](https://youtrack.jetbrains.com/issue/KT-7358)) for reified parameters we could express the constraint of a type having `values()`/`valueOf()`: 73 | 74 | ``` kotlin 75 | fun renderOptions(render: (T) -> String) 76 | where companion object T : EnumValues 77 | { 78 | val values = T.values() // calls a function on the companion object of T 79 | for (v in values) { 80 | println(" * " + render(v)) 81 | } 82 | } 83 | ``` 84 | 85 | This is the most involved of the three options: 86 | - requires another language feature: companion object constraints, 87 | - requires implicit companion objects on every enum (problematic for Java enums), 88 | - requires a new interface in `kotlin-runtime`. 89 | 90 | It is also not entirely clear how `values()` and `valueOf()` can be available on a synthetic companion object for a Java enum without using reflection or byte code spinning at runtime. 91 | 92 | ## Arguments against this proposal 93 | 94 | - Reflection may actually be good enough for such use cases 95 | - Java doesn't support anything like this, and people live with it 96 | -------------------------------------------------------------------------------- /proposals/inline-properties.md: -------------------------------------------------------------------------------- 1 | # Inline for property accessors 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Andrey Breslav 5 | * **Status**: Accepted 6 | * **Prototype**: Implemented 7 | 8 | ## Feedback 9 | 10 | Discussion of this proposal is held in [this issue](https://github.com/Kotlin/KEEP/issues/34). 11 | 12 | ## Summary 13 | 14 | Support `inline` modifier for properties that don't have a backing field and accessors of such properties. 15 | 16 | ## Motivation 17 | 18 | * Enable `@InlineOnly` properties whose signatures can be changed without affecting the binary compatiblity of a library 19 | * Make reified type parameters available for property accessors 20 | 21 | ## Description 22 | 23 | We propose to allow the `inline` modifier on properties that do not have a backing field. 24 | 25 | It may be used on the sole accessor of a `val`: 26 | 27 | ``` kotlin 28 | val foo: Foo 29 | inline get() = Foo() 30 | ``` 31 | 32 | Or one or both accessors of a `var`: 33 | 34 | ``` kotlin 35 | var bar: Bar 36 | inline get() = ... 37 | inline set(v) { ... } 38 | ``` 39 | 40 | At the call site the accessors are inlined as normal functions. 41 | 42 | The `inline` modifier may also be used on the property itself: 43 | 44 | ``` kotlin 45 | inline var bar: Bar 46 | get() = ... 47 | set(v) { ... } 48 | ``` 49 | 50 | In such a case, all accessors are marked `inline` automatically. 51 | 52 | Applying `inline` to a property that has a backing field, or its accessor, results in a compile-time error. 53 | -------------------------------------------------------------------------------- /proposals/jdk-dependent-built-ins.md: -------------------------------------------------------------------------------- 1 | # JDK dependent built-in classes 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Denis Zharkov 5 | * **Contributors**: Andrey Breslav, Ilya Gorbunov 6 | * **Status**: Accepted 7 | * **Prototype**: Implemented in Kotlin 1.1 8 | * **Discussion**: https://github.com/Kotlin/KEEP/issues/30 9 | 10 | ## Goals 11 | 12 | There are a some members of JDK classes that are not reflected in corresponding Kotlin built-in classes. For example `Collection.stream`, `Throwable.fillInStackTrace`, etc. The main rationale for this is that these members depend on types that we are not ready to make available on every platform we implement Kotlin for. 13 | 14 | This makes such members unavailable in Kotlin: for both calling and overriding *(this may be not impossible but rather hard)*. 15 | 16 | > Known workarounds 17 | * We can cast to a relevant JDK class when calling a specific method (e.g. `(x as java.util.List<...>).stream()`, 18 | * Formally it's even possible to override them (without *override* keyword). But it's still impossible to make a super-call 19 | 20 | The goal of this proposal is to fix this and make the members available if they are available in the current JDK. 21 | 22 | ## Feedback 23 | 24 | Discussion of this proposal is held in [this issue](https://github.com/Kotlin/KEEP/issues/30). 25 | 26 | ## Known problematic members 27 | 28 | * Related to Stream API, in `Collection`: `stream`, `parallelStream`, `spliterator` 29 | * Map-specific: `compute`, `computeIf*`, `merge` 30 | * Common container methods: `forEach`, `removeIf` 31 | * `Throwable.fillInStackTrace` 32 | 33 | ## Possible solutions 34 | 35 | ### Extensions in stdlib 36 | 37 | To enable calling such methods it's enough to just add extensions with similar signatures to stdlib. 38 | 39 | ###### Pros 40 | * Looks like the easiest way to resolve the major part of requests 41 | * Overriding is still possible (with the no-override-keyword-hack) 42 | 43 | ###### Cons 44 | * It's necessary to maintain several stdlib jars for different targets. (*I believe it should happen at some point anyway.*) 45 | * Overriding model looks very fragile, because no signature check happens 46 | * Super-calls are not available 47 | 48 | ### Different built-ins declarations for each major JDK version 49 | 50 | One of the obvious options is to have several versions of built-ins declarations, different for each major JDK version (and one for JS?). 51 | 52 | ###### Pros 53 | * This solution seems to be much more reliable then the one about extensions 54 | * We can explicitly a choose subset of members that will appear in each built-in class 55 | * Also we can control types of those members: 56 | * nullability / mutability 57 | * use-site / declaration-site variance? 58 | 59 | ###### Cons 60 | * It's still necessary to maintain different runtime jars (*anyway it looks necessary now*) 61 | * There should also be several source versions of built-in declarations with some parts shared (*it can be achieved with same mechanism as one used in stdlib to specify that given declaration is for X target*) 62 | * It's not very flexible in the sense that each new major JDK release requires additional manual work to be done (*I believe these rare events require some attention anyway*) 63 | * If parameter `Collection.forEach` will have functional type, some additional work is required to emit right type in corresponding JVM method (`Consumer`) (*seems to be not very hard to implement, also one can explicitly declare SAM-adapter extension*) 64 | 65 | #### Add members through synthetic supertypes 66 | It's possible to achieve a similar effect through adding some synthetic supertypes containing necessary members (e.g. 67 | `CollectionWithStream` with `stream`, `parallelStream`, `spliterator`) to the built-in classes. Similar idea is already used to provide `Serializable` supertype for each declaration. But further investigation is needed to check that nothing breaks because of new non-existing classes. 68 | 69 | ### Load additional methods from JDK classes on the class-path 70 | 71 | ###### Pros 72 | * This option decreases the amount of manual work required for each JDK 73 | * A lot of things will just work as they already do for common Java code (like SAM adapters and `Collection.forEach`) 74 | * Also it's a pretty simple solution for problems with `Object.wait` and `Object.notify` 75 | * Currently it seems to be the easiest solution to implement 76 | * New JDK members appear without compiler/runtime update 77 | 78 | ###### Cons 79 | * Uncontrollable set of members in built-ins 80 | * A lot of flexible types 81 | * **Important:** When something appears in built-in classes, and then it's removed or has a signature change, it could be declared as breaking change 82 | 83 | ## Chosen solution 84 | 85 | The third solution (loading additional methods from JDK classes on the class-path) was chosen both because of it's flexibility and implementation simplicity. 86 | 87 | ### Visible/Hidden Method lists 88 | Some methods in JDK classes are undesirable in Kotlin built-ins (e.g. a lot of String methods or List.sort(), because there are Kotlin analogues with better signatures already defined). 89 | 90 | Also for Kotlin containers with mutable analogues it's unknown whether given additional method should belong to a mutable or read-only version of the interface. 91 | 92 | Thus, to provide some level of control it's proposed to maintain predefined lists in the compiler describing what to do with listed members: 93 | * *Visible list* defines the set of methods that are allowed to be visible at Kotlin call-sites, 94 | * *Hidden list* defines the set of method that are prohibited from being visible at Kotlin call-sites. At the same time such methods are still available for overriding and `super`-qualified calls 95 | * *Mutable methods list* defines the set of methods that should be added to a mutable interface. By default, such methods go to the read-only interface. 96 | 97 | All methods not listed in *Visible* nor in *Hidden* lists are available for calls, but such usages should be marked with a warning because it may become unresolved in the next language version. 98 | 99 | ### Additional member method list 100 | * Let `X` be some Kotlin built-in class 101 | * If `X` is `Any`, then no additional members should be added 102 | * Let `M` be a mutable version of `X` if it exists or `X` itself 103 | * Let `Y` be a JDK analogue for `X` (e.g. `java.util.Set` for `kotlin.collections.Set`) 104 | * Take every method that exists in member scope of `Y` and can not be override of any member in `M` 105 | * Filter out methods with not public API visibility (nor public/protected) and ones that are deprecated 106 | * Process methods in accordance with predefined *Method lists* 107 | * Annotate non-invariant type parameters occurrences in unsafe variance position with `@UnsafeVariance` annotation (e.g. second parameter of `Map.getOrDefault`) 108 | * Add resulting methods into `X`'s member scope 109 | 110 | ### Additional constructor list 111 | Rules for constructors are similar to ones for methods, but they do not consider containers' mutability *(interfaces don't have constructors)* and instead of plain overridability, both-ways overridability is used (i.e. two constructors are considered to be the same if their signatures allow them to be overrides of each other). 112 | 113 | ### Static members 114 | It's proposed not to import additional static members from JDK classes because most of them already exist in stdlib as extensions to companion objects of corresponding Kotlin classes and have refined signatures. 115 | 116 | Also if even there is no stdlib analogue, it's always possible to call it through the JDK class itself. 117 | 118 | ### Different JDK versions in dependent modules 119 | Let module `m1` uses JDK 6, while `m2` depends on `m1` and JDK8. 120 | 121 | * If there is some class declared in `m1` implementing `Collection`, `m2` should see `stream` in it's member scope 122 | * If there is some method in `m1` with return type of `Collection`, `m2` can use it's result as a receiver of `stream` again 123 | 124 | i.e. each module X should "see" other modules as they have same JDK as X does. 125 | 126 | ### Flexible type enhancement 127 | Some of additional members may have inappropriate nullability/mutability signatures with flexible types. 128 | 129 | It's proposed to maintain another hard-coded list in the compiler describing enhanced signatures for some JDK methods. 130 | 131 | ### Backward compatibility of overrides 132 | The problem is that it's impossible now to override an additional member so that it could be compiled with both language version (1.0/1.1). 133 | 134 | ``` 135 | abstract class A : Map { 136 | override fun getOrDefault(key: Any?, defaultValue: String?): String? { 137 | return super.getOrDefault(key, defaultValue) 138 | } 139 | } 140 | ``` 141 | 142 | In this example `override` keyword is necessary for JDK 8 with 1.1 language version, while 1.0 doesn't see such method in Map, thus `override` is an error for it. 143 | 144 | A similar problem arises when switching between different JDK's. 145 | 146 | Solution comes from Java: allow omitting `override` keyword when the only override comes from additional member in a built-in class. 147 | 148 | ### Open questions 149 | * Should fields with public API visibility defined in JDK classes also be added to relevant Kotlin built-in classes? 150 | -------------------------------------------------------------------------------- /proposals/js/JsName.md: -------------------------------------------------------------------------------- 1 | # Ability to customize generated name in Kotlin JS 2 | 3 | * **Type**: Kotlin JS design proposal 4 | * **Author**: Zalim Bashorov 5 | * **Contributors**: Andrey Breslav, Alexey Andreev 6 | * **Status**: Approved 7 | * **Related issue**: [KT-2752](https://youtrack.jetbrains.com/issue/KT-2752) 8 | * **Prototype**: Implemented 9 | 10 | 11 | ## Use cases / motivation 12 | 13 | - Get better names in API (without mangling). 14 | - Resolve clashing of names (e.g. for overloads). 15 | - Use better name for native declarations in Kotlin side (e.g. declare some function as operator). 16 | 17 | ## Proposed solution 18 | 19 | Introduce `JsName` annotation (like in JVM): 20 | 21 | ```kotlin 22 | @Retention(AnnotationRetention.BINARY) 23 | @Target(/* ... */) 24 | annotation class JsName(name: String) 25 | ``` 26 | 27 | Use **binary retention** since it should be available from deserialized descriptors. 28 | 29 | **Allow the annotation for following targets:** 30 | 31 | + `FUNCTION` (constructors are not included) 32 | 33 | + `PROPERTY` 34 |
35 | _**Frontend**: prohibit on extension properties, allow on their accessors._ 36 | 37 | + `CLASS` (class, interface or object, annotation class is also included) 38 |
39 | It's useful for native declarations, e.g. when name in Kotlin looks ugly or name already used somewhere. 40 |
41 | *__Question:__ should be prohibited for non native declarations?* 42 |
43 | *__Decision:__ allow on any declaration.* 44 | 45 | + `CONSTRUCTOR` (primary or secondary constructor) 46 |
47 | To generate better name for secondary constructors. 48 |
49 | *__Frontend:__ The annotation should be prohibited for primary constructors, because applying it to the primary constructor will have the some effect as applying to the class.* 50 | 51 | 52 | **Prohibit the annotation for following targets:** 53 | - `ANNOTATION_CLASS` (Annotation class only) 54 | - `TYPE_PARAMETER` (Generic type parameter) 55 | - `FIELD` (Field, including property's backing field) 56 | - `LOCAL_VARIABLE` 57 | - `TYPE` (Type usage) 58 | - `EXPRESSION` 59 | - `FILE` 60 | 61 | **Questionable targets:** 62 | + `VALUE_PARAMETER` (parameter of a function or a constructor) 63 |
64 | *__Decision:__ prohibit parameters as target of the annotation.* 65 |
66 |
67 | Can be useful to provide better interop with some frameworks, e.g. angularjs 1.x, where name of parameters used for injection and some frequently used dependencies contains `$`. 68 |
69 | **Notes:** 70 | - this technic (using name of function parameters) is not so popular in JS world, at least I know only about angularjs 1.x where it used; 71 | - it can be simply break by minifiers; 72 | - angular 2 uses another way for DI; 73 | - but it's simple to implement. 74 | 75 | *__Question:__ how it should work for overrides?* 76 | * allow only on final 77 | * prohibit on overrides and reuse names from overridden declaration 78 | * how, then, resolve conflicts? 79 | 80 | + `PROPERTY_GETTER` and `PROPERTY_SETTER` 81 |
82 | *__Decision:__ allow accessors as target of the annotation.* 83 |
84 |
85 | Can be useful for native declarations to provide better api in Kotlin. 86 | Additionally it can be used to force using functions for accessors to avoid problems with some minifiers 87 | (e.g. closure-compiler) which treat access to properties as side effect free. 88 |
89 | *__Frontend:__ the annotation can not be applied to only one of accessors.* 90 |
91 | *__Frontend:__ the annotation can not be simultaneously applied to the property and its accessors.* 92 |
93 | *__Question:__ should it be prohibited for non-native declarations?* 94 |
95 | *__Decision:__ allow on any declarations* 96 | 97 |
98 | *__Backend:__ properties whose accessors has this annotation no longer are treated as JS properties, 99 | instead they are interpreted as bunch of accessors (like in JVM), so backend:* 100 | - *doesn't generate JS property for them;* 101 | - *translates accessors as functions using name from the annotation;* 102 | - *translates an accessing to such property as call to accessor function using name from the annotation.* 103 | 104 | 105 | ## How it should work for overrides? 106 | 107 | Actually, the problems actual even we prohibit using `JsName` on overrides, 108 | because the compiler doesn't mangle the name of native declarations, 109 | so we can simply get name clash when to inherit from two natives. 110 | 111 | And doesn't make sense to prohibit to use this annotation on non-final declarations. 112 | 113 | So propose to **prohibit using JsName on overrides**, but **report about name clashes** on non-native declarations. 114 | _**Decision:** approved_ 115 | 116 | **Case 1: declarations with different names from different parents with the same requested name (by JsName)** 117 | _(map many names to the one)_ 118 | 119 | 120 | _Note: A, B, C can be native._ 121 | 122 | Example 1: 123 | ```kotlin 124 | interface A { 125 | @JsName("boo") 126 | fun bar() 127 | } 128 | 129 | 130 | interface B { 131 | @JsName("boo") 132 | fun baz() 133 | } 134 | 135 | class C : A, B { 136 | override fun bar() {} 137 | override fun baz() {} 138 | } 139 | ``` 140 | 141 | Example 2: 142 | ```kotlin 143 | interface A { 144 | @JsName("boo2") 145 | fun bar2() {} 146 | } 147 | 148 | 149 | interface B { 150 | @JsName("boo2") 151 | fun baz2() {} 152 | } 153 | 154 | class C : A, B 155 | ``` 156 | 157 | Proposed solution: allow name clashes on native declarations and prohibit on non-native declarations. 158 | _**Decision:** approved_ 159 | 160 | **Case 2: declarations with the same name from different parents with different requested name (by JsName):** 161 | _(map the one name to many)_ 162 | 163 | _Note: A, B, C can be native._ 164 | 165 | Example 1: 166 | ```kotlin 167 | interface A { 168 | @JsName("fooA") 169 | fun foo() 170 | } 171 | 172 | 173 | interface B { 174 | @JsName("fooB") 175 | fun foo() 176 | } 177 | 178 | class C : A, B { 179 | override fun foo() {} 180 | } 181 | ``` 182 | 183 | Example 2: 184 | ```kotlin 185 | interface A { 186 | @JsName("fooA") 187 | fun foo2() {} 188 | } 189 | 190 | 191 | interface B { 192 | @JsName("fooB") 193 | fun foo2() 194 | } 195 | 196 | class C : A, B 197 | ``` 198 | 199 | Solution 1: prohibit to have more than one name candidate for all declarations including native, 200 | because it can confuse and I don't know any real usecases. 201 | 202 | Solution 2: 203 | * allow to have more than one name candidate for all or only native declarations. 204 |
205 | In this case generated code should contain all name candidates which referenced to the same function to be compatible with all implemented interfaces. 206 | 207 | _**Decision:** solution 2 chosen_ 208 | 209 | ## Common 210 | 211 | *__Frontend:__ the annotation can not be combined with override.* 212 | 213 | *__Frontend:__ consider new names when process clashes.* 214 | -------------------------------------------------------------------------------- /proposals/js/_Glossary.md: -------------------------------------------------------------------------------- 1 | # Common terms used in Kotlin JS 2 | 3 | **"native declaration"** -- declaration with `native` annotation or declaration inside another native declaration. 4 | For such declarations compiler doesn't generate anything and in some cases, they processed in a special way. 5 | -------------------------------------------------------------------------------- /proposals/jvm-field-annotation-in-interface-companion.md: -------------------------------------------------------------------------------- 1 | # @JvmField for interface companion properties 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Mike Bogdanov 5 | * **Status**: Implemented in Kotlin 1.3 6 | * **Discussion**: [KEEP-152](https://github.com/Kotlin/KEEP/issues/152) 7 | 8 | ## Summary 9 | 10 | Allow to use `@JvmField` annotation on interface companion properties similar to class companion ones. 11 | 12 | ## Description 13 | 14 | `@JvmField` annotation on interface companion properties has same effect and similar restrictions as annotation on class companion members: 15 | * generates static field in interface with initialization in interface 16 | * adding or removing `@JvmField` annotation is binary incompatible change cause it's changes fields owner 17 | * not applicable for `const`, `lateinit` and delegated properties 18 | * property should not have any custom accessors 19 | * property can't override anything 20 | * **applicable only if all companion properties are `public final val` annotated with `@JvmField`** (additional restriction) 21 | 22 | 23 | ``` kotlin 24 | interface Foo { 25 | companion object { 26 | @JvmField 27 | val foo: String = "bar" 28 | } 29 | } 30 | ``` 31 | 32 | ## Open questions 33 | 34 | Maybe weak additional condition ("all companion properties are `public final val` annotated with `@JvmField`") 35 | to allow use `const` properties withing `@JvmField` ones. 36 | In such case `const` properties should also be moved to interface 37 | (NB: now additional declaration copy is created in interface for `const` property in companion) 38 | 39 | 40 | ## Related issues 41 | 42 | * [KT-15807](https://youtrack.jetbrains.com/issue/KT-15807) @JvmField is not applicable to interface companion properties 43 | 44 | -------------------------------------------------------------------------------- /proposals/jvm-records.md: -------------------------------------------------------------------------------- 1 | # JVM records support 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Ilya Gorbunov 5 | * **Status**: Under consideration 6 | * **Discussion**: [KEEP-233](https://github.com/Kotlin/KEEP/issues/233) 7 | * **Related issues**: [KT-44121](https://youtrack.jetbrains.com/issue/KT-44121) 8 | 9 | ## Introduction 10 | 11 | JDK 16 introduces a special type of classes called [records](https://openjdk.java.net/jeps/395). Records allow declaring 12 | nominal tuples in a concise way in Java. While records remove a vast amount of boilerplate on a declaration site of such 13 | tuples (similar to data classes in Kotlin), for consumers, records are not that different from plain Java classes 14 | with a manually implemented constructor, component accessors, `equals`/`hashCode`/`toString` methods. 15 | That means that even if Kotlin did nothing to support JVM records, they would be nevertheless usable from Kotlin 16 | due to the normal Kotlin-Java interoperability. 17 | 18 | However, it still makes sense to support JVM records in a special way in Kotlin. In essence, records are similar to 19 | Kotlin data classes, so their components should be visible as Kotlin properties. 20 | 21 | The support of Java records in Kotlin can be broken down into two aspects: 22 | - the first is how records declared in Java should be seen in Kotlin, 23 | - the second is how to declare a record in Kotlin. 24 | 25 | ## Java records seen in Kotlin 26 | 27 | In Java records, component accessor functions have the same names as the corresponding components. Without special support 28 | Kotlin would see them only as functions. 29 | 30 | ### Synthetic properties for component accessors 31 | 32 | Kotlin is able to recognize [getter and setter methods](https://kotlinlang.org/docs/reference/java-interop.html#getters-and-setters) 33 | in Java classes and provide synthetic properties for them if they follow the convention: 34 | - `getSomething()` method is seen as a synthetic property `something` 35 | - `isSomething()` method returning `boolean` is seen as a synthetic property `isSomething` 36 | 37 | So in order to see record component accessors as properties, Kotlin adds another convention specifically for Java records 38 | in addition to the two above: 39 | - `something()` method is seen as a synthetic property `something` 40 | 41 | #### Property accessor naming convention conflicts 42 | 43 | When a record has both methods `something()` and `getSomething()`, where the former is an automatically generated or 44 | manually declared accessor and the latter is a manually declared method, we have a conflict of two synthetic property 45 | conventions. While we believe that in practice both methods will most likely have the same implementation, we still have 46 | to decide what to do in this situation. 47 | 48 | - If `getSomething` is an override of a Kotlin interface with the property `something`, the record should have the 49 | _member_ property `something` inherited from the interface with the accessor method `getSomething()`. 50 | - If `getSomething` is an override of a Java interface method, or a just a method declared in the record, 51 | we prefer the synthetic property derived from the `getSomething` accessor. 52 | Note that this is different from the current situation when a plain Java class has both `getIsSomething()` and `isSomething()` 53 | accessors. 54 | 55 | ## Authoring records in Kotlin 56 | 57 | There's not much use in declaring JVM records in Kotlin besides two use cases: 58 | - migrating an existing Java record to Kotlin and preserving its ABI; 59 | - generating a record class attribute with record component info for a Kotlin class to be read later 60 | by a potential framework relying on Java reflection to introspect records. 61 | 62 | In order to author a record class in Kotlin, we provide a new annotation: `@JvmRecord`, which can be placed on a class 63 | to compile it as a record. This JVM-specific annotation enables generating: 64 | - the record components corresponding to the class properties in the class file, 65 | - the property accessor methods named according to the Java record naming convention, 66 | - `equals`/`hashCode`/`toString` implementations when they are not provided explicitly or by the class being a data class. 67 | 68 | Note that applying `JvmRecord` to an existing class is not a binary compatible change: it changes 69 | the naming convention of the class property accessors. 70 | 71 | 72 | ### Conditions for a class to meet to be eligible for `@JvmRecord` 73 | 74 | - The class shall be in a module that targets JVM 16 bytecode (or 15 if `-Xjvm-enable-preview` compiler option is enabled). 75 | - The class cannot inherit any other class explicitly (including `Any`) because all records implicitly inherit `java.lang.Record`. 76 | - There must be a clear relation between the primary constructor parameters and the class properties with backing fields. 77 | Currently, we have such clear relation in data classes and in those plain Kotlin classes where all 78 | primary constructor parameters declare `val` properties. 79 | Note that the prototype implementation restricts the annotation applicability further only on _data_ classes. 80 | - The class cannot declare any additional state, i.e. properties with backing fields, 81 | except those initialized from the corresponding primary constructor parameters. 82 | - The class cannot declare any mutable state, i.e. mutable properties with backing fields. 83 | - The class cannot be local. 84 | - The class primary constructor must be as visible as the class itself. 85 | 86 | ### Property accessor method names 87 | 88 | By default, in a Kotlin class annotated with `@JvmRecord`, property accessor names should follow the Java record 89 | component accessor method naming convention, i.e. they should have the same name as the corresponding properties. 90 | 91 | #### Overriding Kotlin interface properties 92 | 93 | If a Kotlin class annotated with `@JvmRecord` implements a Kotlin interface overriding the interface properties 94 | with the corresponding component properties, the class should generate additional accessors for these properties 95 | bridging the accessor methods from the interface. 96 | 97 | #### JvmName on property accessors 98 | 99 | 105 | 106 | In Kotlin, `@JvmName` annotation applied on a property accessor allows changing its name visible for Java. 107 | Thus, applying it on record property accessors is prohibited because record component accessor methods should 108 | follow the strict naming convention. 109 | 110 | ### toString implementation of a JvmRecord class 111 | 112 | While the exact format of automatically generated `toString` implementation in a Java record is not specified, 113 | it produces a result very similar to that in Kotlin data classes with the only distinction in the parentheses used 114 | to surround class properties/record components. 115 | 116 | It may be valuable to preserve `toString` format when migrating Java record to Kotlin, thus when `@JvmRecord` is applied 117 | on a _plain_ Kotlin class without an explicit `toString` implementation, it gets the Java record `toString` format. 118 | However, if `@JvmRecord` is placed on a data class, the format of a data class `toString` is used. 119 | 120 | ### `@JvmRecord` restrictions in multiplatform projects 121 | 122 | `@JvmRecord` is a JVM-specific annotation, though it is available in the common standard library as a so-called 123 | optional expectation annotation. This means that this annotation can have no actual implementation in some platforms, 124 | namely, in all platforms except JVM in this case. 125 | 126 | However, since this annotation affects the generation of `equals`/`hashCode`/`toString` methods if they are not provided 127 | explicitly, just ignoring it in the other platforms would lead to a different equality/toString behavior compared to 128 | that in JVM. To avoid this, `@JvmRecord` brings an additional restriction on a class in non-JVM platforms: 129 | - the class must provide implementations of `equals`/`hashCode`/`toString` either explicitly, 130 | or have them implicitly generated if the class is a _data_ class. 131 | 132 | ## Prototype 133 | 134 | The prototype implementation of the JVM record support is provided in Kotlin 1.4.30. 135 | 136 | At the start, the `@JvmRecord` annotation will be applicable only to _data_ classes. 137 | 138 | ## Java-to-Kotlin conversion 139 | 140 | Since migrating existing Java records to Kotlin is one of the use cases of authoring record classes in Kotlin, 141 | it's also important that the Kotlin IDE plugin provided a smart Java-to-Kotlin conversion for Java record classes. 142 | For now, considering the limitations of the prototype, J2K should transform a Java record into a Kotlin data class 143 | annotated with `@JvmRecord` when it is possible for the converted class code to meet the limitations of a 144 | `@JvmRecord`-annotated class. In case when it is not possible, J2K should produce a plain Kotlin class (without `@JvmRecord`) 145 | with a comment warning about the lost "recordness" of the converted class. 146 | 147 | ## Future improvements 148 | 149 | ### Allowing `@JvmRecord` on non-data Kotlin classes 150 | 151 | The prototype allows `JvmRecord` annotation placed only on data classes. We could relax this restriction provided that 152 | there's still a clear relation between the class's properties and primary constructor parameters. A class could satisfy 153 | this restriction if either: 154 | - it has the primary constructor where all parameters declare `val` properties; 155 | - or it has the primary constructor where all parameters have the same names and types and are following in the same order 156 | as the class properties with backing fields. 157 | 158 | ### Canonical constructor parameter names 159 | 160 | Usually, Kotlin does not trust parameter names in Java methods because they are not a part of the method contract 161 | and can be missing in the compiled bytecode of the method. So Kotlin prohibits calling Java methods with named parameters. 162 | In record declarations, however, component names and order are significant. 163 | Therefore, Kotlin could treat record canonical constructor parameter names as significant too and allow invoking 164 | canonical constructors with named parameters. Note that in this case, parameter names of such a constructor 165 | are disregarded even if present in the bytecode and are always derived from the corresponding component names. 166 | -------------------------------------------------------------------------------- /proposals/jvm-static-annotation-in-interface-companion.md: -------------------------------------------------------------------------------- 1 | # @JvmStatic for interface companion members 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Mike Bogdanov 5 | * **Status**: Implemented in Kotlin 1.3 6 | * **Discussion**: [KEEP-150](https://github.com/Kotlin/KEEP/issues/150) 7 | 8 | ## Summary 9 | 10 | Allow to use `@JvmStatic` annotation on interface companion members similar to class companion ones. 11 | 12 | ## Description 13 | 14 | `@JvmStatic` annotation on interface companion members has same effect and similar restrictions as annotation on class companion members: 15 | * generates static method in interface. This static method will delegate to the companion member 16 | * can't be used on `const` and `@JvmField` properties 17 | * additionally requires '-jvm-target 1.8' compilation option 18 | 19 | 20 | ``` kotlin 21 | interface Foo { 22 | companion object { 23 | @JvmStatic 24 | fun foo() {} 25 | 26 | @JvmStatic 27 | val foo: String 28 | get() = "bar" 29 | } 30 | } 31 | ``` 32 | 33 | ## Related issues 34 | 35 | * [KT-6301](https://youtrack.jetbrains.com/issue/KT-6301) Support JvmStatic annotation on interface companion object members 36 | 37 | -------------------------------------------------------------------------------- /proposals/lateinit-property-isinitialized-intrinsic.md: -------------------------------------------------------------------------------- 1 | # Lateinit property isInitialized intrinsic 2 | 3 | Goal: provide a way to check whether a `lateinit` property was assigned. 4 | 5 | ## Motivation 6 | 7 | Original issue: https://youtrack.jetbrains.com/issue/KT-9327. 8 | 9 | A prominent use case is tests, for example JUnit. A lateinit property may or may not be initialized in `setUp`, however `tearDown` must perform the cleanup only if the property was initialized, or otherwise it would have to catch `UninitializedPropertyAccessException`. 10 | 11 | ``` kotlin 12 | class Test { 13 | lateinit var file: File 14 | 15 | @Before fun setUp() { 16 | file = createFile() 17 | } 18 | 19 | @After fun tearDown() { 20 | if (... file has been initialized ...) { 21 | file.delete() 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | ## Description 28 | 29 | We propose to add a new declaration to the standard library: an extension property `isInitialized`. It would be available _only on a property reference expression_ that references a lateinit property, accessible at the call site. It's inline and intrinsic, i.e. the corresponding bytecode is generated manually by the compiler at each call site, because it's not possible to implement it in Kotlin. 30 | 31 | ``` kotlin 32 | package kotlin 33 | 34 | import kotlin.internal.InlineOnly 35 | import kotlin.internal.AccessibleLateinitPropertyLiteral 36 | import kotlin.reflect.KProperty0 37 | 38 | /** 39 | * Returns `true` if this lateinit property has been assigned a value, and `false` otherwise. 40 | */ 41 | @SinceKotlin("1.2") 42 | @InlineOnly 43 | inline val @receiver:AccessibleLateinitPropertyLiteral KProperty0<*>.isInitialized: Boolean 44 | get() = throw NotImplementedError("Implementation is intrinsic") 45 | ``` 46 | 47 | `AccessibleLateinitPropertyLiteral` is an internal annotation (accessible only in standard library sources) defined as follows: 48 | 49 | ``` kotlin 50 | package kotlin.internal 51 | 52 | /** 53 | * The value of this parameter should be a property reference expression (`this::foo`), referencing a `lateinit` property, 54 | * the backing field of which is accessible at the point where the corresponding argument is passed. 55 | */ 56 | @Target(AnnotationTarget.VALUE_PARAMETER) 57 | @Retention(AnnotationRetention.BINARY) 58 | @SinceKotlin("1.2") 59 | internal annotation class AccessibleLateinitPropertyLiteral 60 | ``` 61 | 62 | To call `isInitialized`, simply pass a bound reference to the property in question as the receiver: 63 | 64 | ``` kotlin 65 | if (this::environment.isInitialized) { 66 | this::environment.configure() 67 | } 68 | ``` 69 | 70 | ## Resolution 71 | 72 | `isInitialized` is a normal declaration that is resolved according to the standard Kotlin rules. To prevent it from being called on any `KProperty0` instances except symbolic references, we implement a new call checker (call checkers are run only after the resolution is complete and successful). This checker is going to check that an argument passed to a parameter annotated with `AccessibleLateinitPropertyLiteral` is indeed a property literal (after being deparenthesized) and its backing field is accessible at the call site. 73 | 74 | ``` kotlin 75 | class Test(val name: String) { 76 | lateinit var file: File 77 | 78 | fun test() { 79 | this::file.isInitialized // OK 80 | (this::file).isInitialized // OK 81 | 82 | val otherTest = Test() 83 | otherTest::file.isInitialized // OK, backing field is accessible, even if it's another instance 84 | 85 | this::name.isInitialized // Error, referenced property is not lateinit 86 | 87 | val q: KProperty0<*> = getProperty(...) 88 | q.isInitialized // Error, receiver must be a property literal 89 | 90 | val p = this::file 91 | p.isInitialized // Error, receiver must be a property literal 92 | } 93 | } 94 | 95 | class Other { 96 | fun test() { 97 | Test()::file.isInitialized // Error, backing field is not accessible here 98 | } 99 | } 100 | ``` 101 | 102 | Note that the `AccessibleLateinitPropertyLiteral` annotation is needed mostly for the users who are looking at this declaration in standard library sources. We could hard-code the support of exactly `isInitialized` (and maybe `deinitialize` in the future) from the standard library into the call checker, however it would be difficult for a user to understand why it's not possible to pass any `KProperty0` instance, not just a reference. 103 | 104 | ## Code generation 105 | 106 | Using property's getter to check `isAccessible` is not possible because it throws an `UninitializedPropertyAccessException` if the property is not initialized. So, the compiler must generate direct read access to the backing field's property. The requirement "must be a reference to a property, the backing field of which should be accessible" is thus needed because the compiler must know _statically_ which property is referenced, check that it's a lateinit property, and ensure that it's possible to generate access to the backing field. 107 | 108 | The generated bytecode itself is very simple. Because `isInitialized` is an intrinsic, we're able to avoid the generation of the anonymous class for the property reference and use the `GETFIELD` instruction directly. 109 | 110 | ``` kotlin 111 | class Test { 112 | lateinit var file: File 113 | 114 | fun test() { 115 | if (this::file.isInitialized) { // ALOAD 0, GETFIELD Test.file, IFNULL ... 116 | ... 117 | } 118 | } 119 | } 120 | ``` 121 | 122 | ### Backing field accessibility 123 | 124 | We'd like to allow calling `isInitialized` on as many properties and from as many different contexts as possible. However, without certain limitations we would have problems with source and binary compatibility. 125 | 126 | The main limitation is that we do not allow calling `isInitialized` on a lateinit property declared in another class, lexically unrelated to the class or file of the call site. It's not that we don't know what bytecode to generate. If the property (and thus its backing field) is public, we could generate `GETFIELD` exactly in the same way as for the property in the containing class. However, doing so would make removing the `lateinit` modifier on a property, which is today merely an implementation detail, a source-breaking and binary-breaking change. We don't want to make the fact that the property is `lateinit` a part of its API, so we disallow usages from other classes. 127 | 128 | ``` kotlin 129 | class Test { 130 | lateinit var file: File 131 | } 132 | 133 | class Other { 134 | fun test() { 135 | Test()::file.isInitialized // Error! 136 | } 137 | } 138 | ``` 139 | 140 | Note that we could allow usages from other classes _in the same file_ but for simplicity, and to avoid errors when moving classes between files, we disallow such usages at the moment. 141 | 142 | Note that we _do_ allow usages from nested/inner classes of the property's declaring class, as well as from any lambdas and local classes inside members of the declaring class. 143 | 144 | ``` kotlin 145 | class Test { 146 | lateinit var file: File 147 | 148 | inner class Inner { 149 | fun test(t: Test) { 150 | this@Test::file.isInitialized // OK 151 | run { 152 | t::file.isInitialized // OK 153 | } 154 | } 155 | } 156 | } 157 | ``` 158 | 159 | In case the property is private and is accessed from a nested/local class or lambda, the JVM back-end must generate a synthetic accessor for the backing field: 160 | 161 | ``` kotlin 162 | class Test { 163 | private lateinit var file: File 164 | 165 | fun test() { 166 | run { 167 | if (this::file.isInitialized) { // ALOAD 0, INVOKESTATIC Test.access$getFile, IFNULL ... 168 | ... 169 | } 170 | } 171 | } 172 | } 173 | ``` 174 | 175 | ### Usages in inline functions 176 | 177 | Usages in inline functions are risky because the generated bytecode references the backing field and thus exposes it to all clients who can see the inline function. If the property is made non-lateinit (which, as discussed earlier, should be a source & binary compatible change), already inlined bytecode would no longer work. Therefore we propose to disallow usages of `isInitialized` in inline functions for now. 178 | 179 | ``` kotlin 180 | class Test { 181 | lateinit var file: File 182 | 183 | inline fun getFileName(): String? { 184 | if (this::file.isInitialized) { // Error! 185 | return file.name 186 | } 187 | return null 188 | } 189 | } 190 | ``` 191 | 192 | ## Tooling support 193 | 194 | Code completion in the IDE must only propose `isInitialized` if the call is going to be allowed by the compiler, i.e. on an accessible lateinit property reference. 195 | 196 | ## Known issues 197 | 198 | * The solution is admittedly very ad-hoc. The `AccessibleLateinitPropertyLiteral` annotation serves only one particular purpose which is hardly generalizable to be useful to solve any other problems. 199 | * Because of the weird nature of the proposed call checker, trivial refactorings like "Extract variable" or "Use .let" could break the code here in an unexpected way: 200 | ``` kotlin 201 | if (this::file.isInitialized) ... // OK 202 | 203 | // Extract variable 204 | 205 | val property = this::file 206 | if (property.isInitialized) ... // Error! 207 | 208 | // Use .let 209 | 210 | if (this::file.let { it.isInitialized && it.get() == ...}) ... // Error! 211 | ``` 212 | 213 | ## Future advancements 214 | 215 | * Similarly to `isInitialized`, we could provide the `deinitialize` intrinsic for resetting the lateinit property value back to `null`. There doesn't seem to be immediate value in providing it though, so we postpone this until more use cases arise. 216 | 217 | -------------------------------------------------------------------------------- /proposals/local-and-top-level-lateinit-vars.md: -------------------------------------------------------------------------------- 1 | # Local and top-level lateinit vars 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Dmitry Petrov 5 | * **Contributors**: Alexander Udalov 6 | * **Status**: Implemented in 1.2 7 | * **Prototype**: Implemented 8 | 9 | Discussion: https://github.com/Kotlin/KEEP/issues/86 10 | 11 | ## Problem description 12 | 13 | Classes (including objects and enum entries) can contain `lateinit` properties, 14 | which provide postponed initialization semantics. 15 | If a property is read before it is initialized, an exception is thrown. 16 | 17 | Similar functionality is useful not only for class properties, 18 | but also for top-level properties and local variables. 19 | 20 | In case of local variables, it also allows initializing local variables in local 21 | functions and lambdas (which works as a makeshift replacement for an effect system): 22 | ```kotlin 23 | fun foo() { 24 | lateinit var x: Bar 25 | synchronized { 26 | x = bar() 27 | } 28 | // ... 29 | } 30 | ``` 31 | 32 | ## Design details 33 | 34 | All relevant restrictions related to lateinit class member properties apply. 35 | `lateinit` modifier is not applicable to: 36 | * read-only properties (`val`s) 37 | * properties of nullable types (including generic types with nullable upper bound) 38 | * properties of primitive types 39 | * delegated properties 40 | * properties with initializer 41 | 42 | On JVM, `lateinit` top-level properties expose their backing field 43 | with the same visibility as a property setter. 44 | 45 | In case of `lateinit` local vars, optimizing compiler can eliminate redundant 46 | initialization checks (as a part of redundant null check elimination pass). 47 | -------------------------------------------------------------------------------- /proposals/local-delegated-properties.md: -------------------------------------------------------------------------------- 1 | # Local delegated properties 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Michael Bogdanov 5 | * **Contributors**: Dotlin, Michael Bogdanov 6 | * **Status**: Accepted 7 | * **Prototype**: Implemented in Kotlin 1.1 8 | 9 | ## Feedback 10 | 11 | Discussion of this proposal is held in [this issue](https://github.com/Kotlin/KEEP/issues/25). 12 | 13 | ## Summary 14 | 15 | Support delegated properties for local scopes (local delegated properties). 16 | 17 | ## Description 18 | 19 | Allow to use delegates for local variables similar to general delegated properties: 20 | 21 | ``` 22 | import kotlin.reflect.KProperty 23 | 24 | class Delegate { 25 | operator fun getValue(t: Any?, p: KProperty<*>): Int = 1 26 | } 27 | 28 | fun box(): String { 29 | val prop: Int by Delegate() 30 | return if (prop == 1) "OK" else "fail" 31 | } 32 | ``` 33 | 34 | ## Open questions 35 | 36 | - How property metadata should be linked to local delegated properties: statically or dynamically? 37 | 38 | ``` 39 | fun test() { 40 | val prop: Int by Delegate() //Is property metadata created on each invocation of 'test' function or just once? 41 | println(prop) 42 | } 43 | 44 | fun test2(){ 45 | for (i in 1..2) { 46 | val prop: Int by Delegate() //Is 'prop' metadata same on each loop iteration? 47 | println(prop) 48 | } 49 | } 50 | ``` 51 | 52 | - Should property metadata be changed upon function inlining? 53 | ``` 54 | inline fun test() { 55 | val prop: Int by Delegate() //Is metadata for prop variable same or not after inlining into 'main' function? 56 | println(prop) 57 | } 58 | 59 | fun main(args: Array) { 60 | test() 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /proposals/named-arguments-in-their-own-position.md: -------------------------------------------------------------------------------- 1 | # Named arguments in their own position 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Denis Zharkov 5 | * **Contributors**: Mikhail Zarechenskiy 6 | * **Status**: Submitted 7 | * **Prototype**: Implemented in 1.3.60 8 | * **Discussion**: [KEEP-193](https://github.com/Kotlin/KEEP/issues/193) 9 | * **Tracker**: [KT-7745](https://youtrack.jetbrains.com/issue/KT-7745) 10 | 11 | ## Summary 12 | 13 | Currently, [named arguments](https://kotlinlang.org/docs/reference/functions.html#named-arguments) have a specific limitation: 14 | when a function is called with both positional and named arguments, all the positional arguments should be placed before 15 | the first named one. For example, the call `f(1, y = 2)` is allowed, but `f(x = 1, 2)` is not. 16 | 17 | But in some cases, such limitation might be inconvenient. 18 | 19 | ## Motivation 20 | 21 | ### Documenting literal arguments 22 | The major known use-case when one may want to mix named and positioned arguments is documentation: sometimes it's useful 23 | to specify in the source explicitly to what parameter exactly this argument matches. Especially, it's important when 24 | the argument is appeared as some plain literal: 25 | ```kotlin 26 | someFunction(true, someOtherArgumentThatAppearsObviousOnCallSite) 27 | someFunction(runInTheSameThread=true, someOtherArgumentThatAppearsObviousOnCallSite) 28 | ``` 29 | 30 | The second call version seems to be much more clear than the first one: it becomes obvious on the call-site 31 | that computation would be run in the same thread. 32 | 33 | ## Rules and semantics 34 | - Proposed semantics is the following: if there was a successfully resolved call in 1.3 it's allowed to add a name 35 | to any positioned non-vararg argument. 36 | - In other words, if there were positioned non-vararg arguments `a_1, ..., a_k` in a successfully resolved call `f(a_1, .., a_k, ...)` 37 | then it's allowed to specify names for any subset of those `a_1, ..., a_k` arguments. 38 | 39 | ## Examples 40 | 41 | ### Simple 42 | ```kotlin 43 | fun foo( 44 | p1: Int, 45 | p2: String, 46 | p3: Double 47 | ) {} 48 | 49 | fun main() { 50 | foo(p1 = 1, "2", 3.0) // OK 51 | foo(1, p2 = "2", 3.0) // OK 52 | foo(1, "2", p3 = 3.0) // OK 53 | 54 | foo(p1 = 1, p2 = "2", 3.0) // OK 55 | 56 | foo(p2 = "2", p1 = 1, 3.0) // Error: p1 and p2 are not on their position in the list 57 | foo(1, p3 = 2.0, "") // Error, p3 is not on its position 58 | } 59 | ``` 60 | 61 | ### Varargs 62 | ```kotlin 63 | fun foo1( 64 | vararg p1: Int, 65 | p2: String, 66 | p3: Double 67 | ) {} 68 | 69 | fun main() { 70 | foo1(1, 2, p2 = "3", 4.0) // Is not allowed because first arguments match to a vararg parameter 71 | foo1(p2 = "3", 4.0) // Is not allowed because there's implicit empty vararg argument in the beginning 72 | } 73 | ``` 74 | 75 | ## Remaining questions/issues 76 | - None 77 | -------------------------------------------------------------------------------- /proposals/repeatable-annotations.md: -------------------------------------------------------------------------------- 1 | # Repeatable annotations 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Alexander Udalov 5 | * **Status**: Accepted 6 | * **Prototype**: Implemented 7 | * **Discussion**: [KEEP-257](https://github.com/Kotlin/KEEP/issues/257) 8 | * **Related issues**: [KT-12794](https://youtrack.jetbrains.com/issue/KT-12794) [KT-47928](https://youtrack.jetbrains.com/issue/KT-47928) [KT-47971](https://youtrack.jetbrains.com/issue/KT-47971) 9 | 10 | The goal of this proposal is to extend the existing Kotlin feature of repeatable annotations to allow binary- and runtime-retained repeatable annotations, while making it fully interoperable with repeatable annotations in Java. 11 | 12 | ## Background 13 | 14 | Repeatable annotation is the one that might be applied multiple times to the same element. 15 | 16 | Kotlin 1.5 supports repeatable annotations with retention `SOURCE`. Declaring such annotation only requires annotating it with `@kotlin.annotation.Repeatable` meta-annotation: 17 | 18 | ```kotlin 19 | @Repeatable 20 | annotation class A(val value: Int) 21 | 22 | @A(0) 23 | @A(1) 24 | @A(42) 25 | fun element() {} 26 | ``` 27 | 28 | Unfortunately, Kotlin repeatable annotations are incompatible with the same feature in Java. In Java, repeatable annotations are declared with `@java.lang.annotation.Repeatable` meta-annotation which takes the “container” class (see [docs](https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html)) which declares an array of annotation values: 29 | 30 | ```java 31 | // Java 32 | 33 | @Repeatable(AContainer.class) 34 | @interface A { 35 | int value(); 36 | } 37 | 38 | @interface AContainer { 39 | A[] value(); 40 | } 41 | ``` 42 | 43 | Note that the explicit container annotation is always needed in Java, even if the annotation has retention `SOURCE`. 44 | 45 | ## Problem 46 | 47 | Since Kotlin’s `Repeatable` does not declare a container annotation, it’s not possible to use Kotlin repeatable annotations in Java. Moreover, the lack of Java interop here is one of the main reasons that up until this point, Kotlin repeatable annotations could only be declared with retention `SOURCE`. 48 | 49 | We could just replicate the Java design in Kotlin by adding `val container: KClass` to `kotlin.annotation.Repeatable`. However, it’s problematic at least for two reasons: 50 | 51 | 1. It would be a breaking change for the existing Kotlin code which declares repeatable annotations with retention `SOURCE`. 52 | 2. Perhaps more importantly, the Java design exposes an implementation detail, namely how the annotations are stored in the bytecode and/or represented in the Java language, to be compatible with bytecode before repeatable annotations were supported in Java. This presents unnecessary boilerplate, and does not align well with the concept of a multiplatform language that is Kotlin, whose language features are supposed to be platform-independent. 53 | 54 | Thus we arrive at the following basic requirements: 55 | 56 | * Kotlin repeatable annotations should be repeatable from the point of view of Java, which means that in the bytecode, they should be declared as `@java.lang.annotation.Repeatable` with some container annotation. 57 | * Yet we don’t want the Java-esque design where the container annotation is declared explicitly in the source code for every new repeatable annotation. 58 | * However, instructing the compiler to always generate the container annotation automatically is not flexible enough for cases when you want to convert existing code from Java to Kotlin, keeping it ABI-compatible, since you need a way to provide a custom name for the container annotation. 59 | * Also, we'd like do avoid breaking changes if possible. 60 | 61 | ## Proposal 62 | 63 | The proposal that solves all of this is as follows: 64 | 65 | * Annotating an annotation with `@kotlin.annotation.Repeatable` makes it repeatable both in Kotlin and in Java. For Java, the compiler generates `@java.lang.annotation.Repeatable` with an automatically generated **implicit** container class named **`Container`** declared inside the annotation. 66 | * If you need to specify a **custom name** for the container annotation, you can override this behavior by **explicitly annotating** the annotation with `@kotlin.jvm.JvmRepeatable(Container::class)`. The compiler will not generate an implicit container class in this case. 67 | * `kotlin.jvm.JvmRepeatable` is just a typealias for `java.lang.annotation.Repeatable`. 68 | 69 | In addition to this, the compiler will also **treat all Java-repeatable annotations as Kotlin-repeatable**. 70 | 71 | ## Examples 72 | 73 | 1) ```kotlin 74 | @Repeatable 75 | annotation class Tag(val name: String) 76 | ``` 77 | 78 | Here, Kotlin automatically generates an **implicit** container annotation class `Tag.Container`, and marks `@Tag` as `@java.lang.annotation.Repeatable` in the bytecode, so that it’s repeatable in Java as well. Repeating usages of `@Tag` are generated in the JVM bytecode as values in `@Tag.Container`. For example: 79 | 80 | ```kotlin 81 | // JVM bytecode: @Tag.Container(value = {@Tag("lorem"), @Tag("ipsum")}) 82 | @Tag("lorem") @Tag("ipsum") 83 | fun test() = ... 84 | ``` 85 | 86 | The container class can be accessed from Java sources as `Tag.Container`. 87 | 88 | 2) ```kotlin 89 | @Repeatable 90 | @JvmRepeatable(Tags::class) 91 | annotation class Tag(val name: String) 92 | 93 | annotation class Tags(val value: Array) 94 | ``` 95 | 96 | Here, **explicit** container class is provided, so implicit class is not generated. The annotation is Java-repeatable because it’s explicitly annotated as such, with the container class `Tags`, which is used to store repeating instances in the bytecode: 97 | 98 | ```kotlin 99 | // JVM bytecode: @Tags(value = {@Tag("lorem"), @Tag("ipsum")}) 100 | @Tag("lorem") @Tag("ipsum") 101 | fun test() = ... 102 | ``` 103 | 104 | 3) ```kotlin 105 | @JvmRepeatable(Tags::class) 106 | annotation class Tag(val name: String) 107 | 108 | annotation class Tags(val value: Array) 109 | ``` 110 | 111 | Here, `Tag` is annotated as Java-repeatable, but not as Kotlin-repeatable. Since all Java-repeatable annotations are automatically Kotlin-repeatable, this behaves as in example 2. 112 | 113 | ## Details 114 | 115 | Marking an annotation repeatable in Java results in additional constraints for the annotation container class. The same constraints will be checked for the Kotlin annotation if it’s annotated with `@JvmRepeatable` ([KT-47928](https://youtrack.jetbrains.com/issue/KT-47928)): 116 | 117 | 1. The container class has to have a property `value` of an array type of the annotation, and all other properties (if any) must have default values specified. 118 | 2. The **retention** of the container class must be **greater or equal** than that of the annotation class (assuming `SOURCE < BINARY < RUNTIME`). 119 | 3. The **target** set of the container class must be a **subset** of the annotation class' target set. 120 | 121 | The compiler will report an error if any of these constraints is not met. 122 | 123 | Also, the compiler will report an error if a non-`SOURCE`-retained annotation is repeated when JVM target bytecode version 1.6 is used. 124 | 125 | In case of an implicit container class, it’s generated with the required property `value`, and both the same retention and target as the annotation class. Also, for reasons explained in the next section, it’s annotated with an internal annotation `@kotlin.jvm.internal.RepeatableContainer`: 126 | 127 | ```kotlin 128 | @Repeatable 129 | @Target(CLASS, FUNCTION) 130 | @Retention(BINARY) 131 | annotation class Tag(val name: String) { 132 | // Automatically generated by the compiler: 133 | // 134 | // @Target(CLASS, FUNCTION) 135 | // @Retention(BINARY) 136 | // @kotlin.jvm.internal.RepeatableContainer 137 | // public annotation class Container(val value: Array) 138 | } 139 | ``` 140 | 141 | Another error is introduced in case the container annotation is applied manually at the same time as the contained annotation, if the latter is repeated: 142 | 143 | ```kotlin 144 | @Tags(["lorem"]) // error! 145 | @Tag("ipsum") @Tag("dolor") 146 | fun test() = ... 147 | ``` 148 | 149 | Note that there will be no error if the contained annotation (`@Tag` in this example) is applied not more than once, because such code was allowed since Kotlin 1.0. 150 | 151 | Another error is going to be reported if an annotation class annotated with `@kotlin.annotation.Repeatable` declares a nested class named `Container` ([KT-47971](https://youtrack.jetbrains.com/issue/KT-47971)). 152 | 153 | ## Reflection 154 | 155 | The following changes in `kotlin-reflect` are needed: 156 | 157 | * Existing extension function `KAnnotatedElement.findAnnotation` will return the **first** instance of a repeating annotation if it’s applied multiple times. 158 | * A new extension function `KAnnotatedElement.findAnnotations` will be added. 159 | * For annotations applied multiple times, it returns the list of all values. 160 | * For annotations applied only once, it returns the list of that one value (regardless of whether the annotation is declared as repeatable or not). 161 | * There will be two declarations: 162 | * `inline fun KAnnotatedElement.findAnnotations(): List` 163 | * `fun KAnnotatedElement.findAnnotations(klass: KClass): List` 164 | * Existing member function `KAnnotatedElement.annotations` will behave as follows: 165 | * For Java repeatable annotations, as well as for Kotlin repeatable annotations with *explicit* container, it works as `getAnnotations` in Java reflection: returns the container annotation type. Manual unpacking/flattening of its value is required to get all the repeated entries. 166 | * For Kotlin repeatable annotations with *implicit* container, it will **automatically flatten** the values and return repeated annotation entries as they are declared in the source code. 167 | * The reason for this behavior is that we don’t want to expose the implicit container class, which is an implementation detail from the Kotlin's point of view 168 | * Checking that each annotation needs to be unwrapped is costly, so we’re going to optimize implicit container detection via name (its name is always `"Container"`) and whether it’s annotated via `@kotlin.jvm.internal.RepeatableContainer` 169 | 170 | ## Timeline 171 | 172 | The feature is going to be available starting from Kotlin 1.6.0-M1. 173 | -------------------------------------------------------------------------------- /proposals/scope-control-for-implicit-receivers.md: -------------------------------------------------------------------------------- 1 | # Scope control for implicit receivers 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Andrey Breslav 5 | * **Contributors**: Ilya Ryzhenkov, Denis Zharkov 6 | * **Status**: Implemented in 1.1-M03 7 | * **Discussion**: [KEEP-57](https://github.com/Kotlin/KEEP/issues/57) 8 | 9 | ## Problem description 10 | 11 | When we nest builders of different types into one another, members of the outermost implicit receiver are available in the innermost lambda (unless they are shadowed by something in inner lambdas): 12 | 13 | ``` kotlin 14 | table { 15 | tr { 16 | tr {} // PROBLEM: Table.tr() is available here, 17 | // because the implicit receiever of `table {}` is in scope 18 | } 19 | } 20 | ``` 21 | 22 | Sometimes this effect is useful (the outermost receiver may provide some "global" services relevant to all necessary scopes, e.g. some context-like data), but often times it's problematic. 23 | 24 | ## References 25 | 26 | - [KT-11551](https://youtrack.jetbrains.com/issue/KT-11551) limited scope for dsl writers 27 | 28 | 29 | 30 | ## Available workarounds 31 | 32 | Currently, the only way to mitigate this is to define a shadowing function for the inner receivers and mark it `@Deprecated(level = ERROR)`: 33 | 34 | ``` kotlin 35 | @Deprecated("Wrong scope", level = DeprecationLevel.ERROR) 36 | fun Tr.tr(body: Tr.() -> Unit) { } 37 | 38 | 39 | table { 40 | tr { 41 | tr { } // Error: Using 'tr(Tr.() -> Unit): Unit' is an error. Wrong scope 42 | } 43 | } 44 | ``` 45 | 46 | This approach has the following major disadvantages: 47 | - it requires a lot of boilerplate to define all the deprecated functions from all possible outer levels, 48 | - one can't anticipate all cases of DSL composition, so there's no way to know the complete set of functions to screen against, 49 | - there's no way to know in advance all possible extensions defined for outer receivers, so screening for them is also impossible. 50 | 51 | ## Proposed solutions 52 | 53 | ### DslMarker (the one currently implemented) 54 | 55 | We introduce `DslMarker` annotation with the following definition: 56 | ```kotlin 57 | @Target(ANNOTATION_CLASS) 58 | @Retention(RUNTIME) 59 | @MustBeDocumented 60 | annotation class DslMarker 61 | ``` 62 | 63 | An annotation class `Ann` is called *a DSL marker* if it is annotated with the `@DslMarker` annotation. 64 | 65 | The general rule: 66 | * an implicit receiver may *belong to a DSL `@Ann`* if it's marked with a corresponding DSL marker annotation 67 | * two implicit receivers of the same DSL are not accessible in the same scope 68 | * the closest one wins 69 | * other available receivers are resolved as usual, but if the resulting resolved call binds to such a receiver, it's a compilation error 70 | 71 | Marking rules: an implicit receiver is considered marked with `@Ann` if 72 | * its type is marked, or 73 | * its type's classifier is marked 74 | * or any of its superclasses/superinterfaces 75 | 76 | Additional notes 77 | * Receivers can be accessed through `this@label` regardless of their marking 78 | * Multiple markers on the same receivers: 79 | * If receivers tower looks like (2, 1+2, 1), we consider both 2 and 1+2 unavailable 80 | 81 | ### ScreenFrom (obsolete) 82 | 83 | We propose to control availability of outer receivers inside a builder lambda through an annotation on the lambda parameter: 84 | 85 | ``` kotlin 86 | fun Table.tr(@ScreenFrom(HtmlTag::class) body: Tr.() -> Unit) { 87 | // ... 88 | } 89 | ``` 90 | 91 | The `@ScreenFrom` annotation (name to be discussed) marks any lambda (even a plain lambda with no receiver), and its argument regulates which receivers to screen from: 92 | 93 | ``` kotlin 94 | annotation class ScreenFrom(vararg val screenedReceivers: KClass<*>) 95 | ``` 96 | 97 | The `screenedReceivers` parameter contains a set of classes (should better be types, but we don't have a representation for those in an annotation ATM), and outer receivers of those classes (and their subclasses) are not available inside the annotated parameter. 98 | 99 | > **Question**: should this work uniformly for all implicit receivers (including ones defined by class, function or companion object), or be specialized only for receivers defined by lambdas? 100 | 101 | An empty set of receivers may mean either 102 | - screen from all (equivalent to `@ScreenFrom(Any::class)`) 103 | - screen from nothing. 104 | 105 | #### Terminology and Naming 106 | 107 | The proposed technique requires a name. Possible options include: 108 | - scope control for implicit receivers 109 | - receiver/DSL isolation 110 | - scope screening 111 | - \ 112 | 113 | Possible names for the annotation: 114 | - ScreenFrom 115 | - ScreenReceivers 116 | - Isolated 117 | - IsolatedScope 118 | - IsolateReceiver 119 | - IsolateFrom 120 | 121 | #### Open questions 122 | 123 | - Classes vs types in the annotation arguments: classes may be too imprecise. E.g. when all receivers are of type `Tag`, `something` is the only thing that we can differentiate for, and classes can't express that. 124 | 125 | > This is an important question, actually. We may well support types as arguments to annotations at some point, e.g. `@Ann(Foo)` (syntax is questionable). So, if we implement this through classes now, we should have a clear transition path for introducing types later. 126 | 127 | #### Arguments against this proposal 128 | 129 | - Adding an annotation that affects name resolution severely (special syntax/modifier may be considered as an alternative) 130 | - We can consider implementing this as a "language extension", e.g. a compiler plugin 131 | - It doesn't cover cases like "screen from the immediate outer, but not the rest of the receivers" 132 | -------------------------------------------------------------------------------- /proposals/sealed-class-inheritance.md: -------------------------------------------------------------------------------- 1 | # Sealed class inheritance 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Stanislav Erokhin 5 | * **Contributors**: Andrey Breslav, Alexander Udalov 6 | * **Status**: Implemented since Kotlin 1.1 7 | 8 | ## Feedback 9 | 10 | Discussion of this proposal is held in [this issue](https://github.com/Kotlin/KEEP/issues/29). 11 | 12 | ## Summary 13 | In kotlin 1.0 all direct subclasses for a sealed class should be declared inside it. 14 | For example: 15 | ```kotlin 16 | sealed class A { 17 | class B: A() 18 | } 19 | ``` 20 | 21 | For some cases such limitation is inconvenient (see use cases below). 22 | 23 | Proposal: allow top-level subclasses for a top-level sealed class in the same file. 24 | 25 | ## Motivation / use cases 26 | 27 | - Nicer names for subclasses 28 | - It's painful to create complex sealed class hierarchy -- nesting is too deep 29 | - >7 votes on [KT-11573](https://youtrack.jetbrains.com/issue/KT-11573): Support sealed class inheritors in the same file 30 | 31 | ## Implementation details 32 | 33 | ### Compiler checks 34 | 35 | For a non top-level sealed class all subclasses should be declared inside it. 36 | So, for such classes nothing changes. 37 | 38 | Let us describe changes for top-level sealed classes. 39 | Suppose that we have a top-level class `A`. 40 | For every class `B` which has a class `A` among its supertypes, we should check: 41 | 42 | - if `B` is a top-level class, then we should check that `A` and `B` are declared in same file; 43 | - otherwise we should check that `B` is declared inside `A`. 44 | 45 | Examples: 46 | ```kotlin 47 | // FILE: 1.kt 48 | sealed class A { 49 | class B : A() { // B is declared inside A -- ok 50 | class C: A() // C is declared inside A -- ok 51 | } 52 | } 53 | 54 | class D : A() { // D and A are declared in same file -- ok 55 | 56 | class E : A() // E is declared outside A -- error 57 | } 58 | 59 | // FILE: 2.kt 60 | class F: A() // F and A are declared in different files -- error 61 | ``` 62 | 63 | ### Exhaustive `when` check 64 | 65 | Suppose we have `when` with parameter `a` where `a` is an instance of a sealed class `A`. 66 | Example: 67 | ```kotlin 68 | fun foo(a: A) = when(a) { 69 | is B -> 1 70 | is C -> 2 71 | } 72 | ``` 73 | In such code the compiler should check that `when` is exhaustive, i.e. all branches are presented. 74 | To do this, we want to collect all direct subclasses of `A`. 75 | So we should collect all classes inside it and, if class `A` is top-level, collect all classes in the same package. 76 | After this we should choose from them only direct subclasses of class `A`. 77 | 78 | As we see above, all direct subclasses of sealed classes will be declared in same file with corresponding sealed class. 79 | Because of this, it is impossible to add direct subclass of class `A` without recompilation of class `A`. 80 | 81 | ### Bytecode generation 82 | 83 | In bytecode we should generate special synthetic constructors for class A, which will be called from direct subclasses constructors. Actually, the same sythetic constructors are generated for sealed classes in kotlin 1.0, so we should reuse corresponding algorithm. 84 | 85 | **Future improvements:** 86 | 87 | - Store information about direct subclasses in binary metadata for corresponding sealed class. [KT-12795](https://youtrack.jetbrains.com/issue/KT-12795) 88 | 89 | ## Open questions 90 | 91 | - Should we allow non top-level subclasses? 92 | ```kotlin 93 | sealed class A 94 | 95 | class B { 96 | class C: A() 97 | } 98 | ``` 99 | 100 | - Should we allow subclasses of a sealed class on the same level? 101 | ```kotlin 102 | class A { 103 | sealed B 104 | class C: B() 105 | } 106 | ``` 107 | 108 | - Should we allow subclasses of a sealed class in other files? 109 | - Can we provide a way to restrict inheritors explicitly? 110 | ``` 111 | sealed(B, C) class A 112 | 113 | class B : A() 114 | class C : A() 115 | ``` 116 | -------------------------------------------------------------------------------- /proposals/stdlib/TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Proposal template for new API in the Standard Library 2 | 3 | This document provides a template that can be used to compose a proposal about new API in the Standard Library. 4 | 5 | # Title 6 | 7 | * **Type**: Standard Library API proposal 8 | * **Author**: author name 9 | * **Contributors**: (optional) contributor names, if any 10 | * **Status**: Submitted 11 | * **Prototype**: Not started / In progress / Implemented 12 | 13 | 14 | ## Summary 15 | 16 | Provide a brief description of the API proposed. 17 | 18 | ## Similar API review 19 | 20 | * Is there a similar functionality in the standard library? 21 | * How the same/similar concept is implemented in other languages/frameworks? 22 | 23 | ## Use cases 24 | 25 | * Provide several *real-life* use cases (either links to public repositories or ad-hoc examples). 26 | 27 | ## Alternatives 28 | 29 | * How verbose would be these use cases without the API proposed? 30 | 31 | ## Dependencies 32 | 33 | What are the dependencies of the proposed API: 34 | 35 | * a subset of Kotlin Standard Library available on all supported platforms. 36 | * JDK-specific dependencies, specify minimum JDK version. 37 | * JS-specific dependencies. 38 | 39 | ## Placement 40 | 41 | * Standard Library or one of kotlinx extension libraries 42 | * package(s): should it be placed in one of packages imported by default? 43 | 44 | ## Reference implementation 45 | 46 | * Provide the reference implementation and test cases. 47 | In case if the API should be specialized for each primitive, only one reference implementation is enough. 48 | * Provide the answers for the questions from the [Appendix](#appendix-questions-to-consider) in case they are not trivial. 49 | 50 | ## Unresolved questions 51 | 52 | * List unresolved questions if any. 53 | * Provide options to solve them. 54 | 55 | ## Future advancements 56 | 57 | * What are the possible and most likely extension points? 58 | 59 | 60 | ------- 61 | 62 | # Appendix: Questions to consider 63 | These questions are not a part of the proposal, 64 | but be prepared to provide the answers if they aren't trivial. 65 | 66 | ## Naming 67 | 68 | * Is it clear from name what API is for? 69 | * Is it named consistently with other API with the similar purpose? 70 | * Consider explorability of API via completion. 71 | Generally we discourage introducing extensions imported by default for unconstrained generic type or `Any` type, as it pollutes the completion. 72 | 73 | Inspiring article on naming: http://blog.stephenwolfram.com/2010/10/the-poetry-of-function-naming/ 74 | 75 | ## Contracts 76 | 77 | * What are the failure conditions and how are they handled? 78 | * Whether the contracts (preconditions, invariants, exception handling) are consistent and are what they may be expected from the similar features. 79 | 80 | ## Compatibility impact 81 | 82 | * How the proposal affects: 83 | - source compatibility, 84 | - binary compatibility (JVM), 85 | - serialization compatibility (JVM)? 86 | * Does it obsolete some other API? What deprecations and migrations are required? 87 | 88 | ## Shape 89 | 90 | For new functions consider alternatives: 91 | 92 | * top-level or extension or member function 93 | * function or property 94 | 95 | ## Additional considerations for collection operations 96 | 97 | ### Receiver types 98 | 99 | Consider if the operation could be provided not only for collections, 100 | but for other collection-like receivers such as: 101 | 102 | * arrays 103 | * sequences 104 | * strings and char sequences 105 | * maps 106 | * ranges 107 | 108 | It is helpful to determine what are the collection requirements: 109 | 110 | * any iterable or sequence 111 | * has size 112 | * has fast indexed access 113 | * has fast `contains` operation 114 | * allows to mutate its elements 115 | * allows to add/remove elements 116 | 117 | ### Return type 118 | 119 | * Is the operation lazy or eager? Choose between `Sequence` and `List` 120 | * What is the return type for each receiver type? 121 | * Does the operation preserve the shape of the receiver? 122 | I.e. returning `Sequence` for sequences and `List` for iterables. 123 | 124 | -------------------------------------------------------------------------------- /proposals/stdlib/abstract-collections.md: -------------------------------------------------------------------------------- 1 | # Read-only and mutable abstract collections 2 | 3 | * **Type**: Standard Library API proposal 4 | * **Author**: Ilya Gorbunov 5 | * **Status**: Implemented in Kotlin 1.1 6 | * **Prototype**: Implemented 7 | * **Discussion**: [KEEP-53](https://github.com/Kotlin/KEEP/issues/53) 8 | 9 | 10 | ## Summary 11 | 12 | Provide two distinct hierarchies of abstract collections: one for implementing read-only/immutable collections, 13 | and other for implementing mutable collections. 14 | 15 | ## Description 16 | 17 | Currently JDK provides a set of abstract classes to inherit collection implementations from: 18 | `AbstractCollection`, `AbstractList`, `AbstractSet`, `AbstractMap` located in `java.util` package. 19 | 20 | These collections are suitable for implementing both read-only and mutable collections, 21 | but that is achieved with the help of the following compromises: 22 | 23 | - for convenience of implementing read-only collections they have their mutation methods not abstract, 24 | but rather implemented as throwing an exception. This poses a risk of forgetting to override a mutation method 25 | when implementing a mutable collection, which would result in getting an exception in runtime. 26 | - abstract collection provide facility for concurrent modification tracking, but it's barely demanded by 27 | *read-only* collections. 28 | 29 | Also a collection inherited from JDK abstract collection suffers from the following drawbacks: 30 | 31 | - it is always a subtype of `MutableCollection` in Kotlin, so it cannot be true read-only such that 32 | `coll is MutableCollection<*> == false` 33 | - it has platform types (i.e. `String!`) appearing here and there: as parameter and return types. 34 | 35 | 36 | This proposal is to introduce the following classes in `kotlin.collections` package. 37 | 38 | Abstract read-only collections: 39 | 40 | - `AbstractCollection : Collection` 41 | - `AbstractList : List` 42 | - `AbstractSet : Set` 43 | - `AbstractMap : Map` 44 | 45 | Abstract mutable collections: 46 | 47 | - `AbstractMutableCollection : MutableCollection` 48 | - `AbstractMutableList : MutableList` 49 | - `AbstractMutableSet : MutableSet` 50 | - `AbstractMutableMap : MutableMap` 51 | 52 | The mutable abstract collections inherit all their implementation from JDK abstract collections, 53 | but having abstract overrides for those mutation methods, that throw `UnsupportedOperationException`, 54 | thus requiring an inheritor to override and implement them deliberately. 55 | 56 | In JS standard library all these classes have their own implementations. 57 | 58 | ## Similar API review 59 | 60 | * `java.util.Abstract*`-classes in JDK 61 | 62 | ## Use cases 63 | 64 | * Implementing read-only collections, such as `PrimitiveArray.asList()`, `List.asReversed()`, `groups` and `groupValues` properties in regex `MatchResult`. 65 | * Implementing [immutable collections](https://github.com/Kotlin/kotlinx.collections.immutable/blob/master/proposal.md). 66 | * Implementing mutable collections, such as `MutableList.asReversed()`. 67 | 68 | ## Alternatives 69 | 70 | * Just use JDK abstract collections 71 | * con: listed in the description. 72 | * con: doesn't unify with JS stdlib collection classes. 73 | * pro: less classes/methods in runtime. 74 | * Just implement collection interfaces and do not use base abstract classes. 75 | * con: have to write annoying boilerplate 76 | * less methods in runtime, but at a price of more methods in implementing classes. 77 | 78 | 79 | ## Dependencies 80 | 81 | What are the dependencies of the proposed API: 82 | 83 | * a subset of Kotlin Standard Library available on all supported platforms. 84 | * on JVM: JDK abstract collections, available in JDK 1.6+ 85 | 86 | ## Placement 87 | 88 | * Standard Library 89 | * `kotlin.collections` package 90 | 91 | ## Reference implementation 92 | 93 | Implementations can be found at: 94 | 95 | - for JVM: https://github.com/JetBrains/kotlin/tree/master/libraries/stdlib/src/kotlin/collections 96 | - for JS: https://github.com/JetBrains/kotlin/tree/master/js/js.libraries/src/core/collections 97 | 98 | ## Questions 99 | 100 | * Will the proposed change be source compatible (since classes with the same name as in JDK are introduced)? 101 | * JDK abstract collections are located in `java.util` package, thus it's mandatory to import them explicitly. 102 | These explicit imports take precedence over imported by default `kotlin.collections` package, 103 | thus previously used JDK abstract collection will still refer to the same `java.util` classes. 104 | -------------------------------------------------------------------------------- /proposals/stdlib/bignumber-operations.md: -------------------------------------------------------------------------------- 1 | 2 | # Extending Kotlin API for BigInteger and BigDecimal 3 | 4 | * **Type**: Standard Library API proposal 5 | * **Author**: Daniil Vodopian 6 | * **Shepherd**: Ilya Gorbunov 7 | * **Status**: Implemented in Kotlin 1.2 8 | * **Prototype**: Implemented 9 | * **Discussion**: [KEEP-49](https://github.com/Kotlin/KEEP/issues/49) 10 | 11 | 12 | ## Summary 13 | 14 | Overload mathematical operations and infix functions to work with BigInteger and BigDecimal. 15 | 16 | ## Similar API review 17 | 18 | * Python's `long` type: https://docs.python.org/2/library/stdtypes.html#numeric-types-int-float-long-complex 19 | 20 | ## Use cases and motivation 21 | 22 | The motivation is to provide a complete and symmetrical set of operators for long arithmetics provided by JDK, namely `BigInteger` for integer computations and `BigDecimal` for floating point computations. 23 | 24 | A "complete" set of operations in this context means that all operators on a basic type are available on the corresponding big type. Operations for `BigInteger` are modelled after `Long`, and operations on `BigDecimal` are modelled after `Double`. The goal is to improve the Java API and not to introduce any additional semantics. 25 | 26 | Kotlin stdlib already [contains](https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/util/BigNumbers.kt) 12 of 35 proposed functions, but that set is neither complete nor symmetrical. That determines the placement of the additional functions. 27 | 28 | The proposal does not include mixed operations between `BigInteger` and `BigDecimal` since we did not find any evidence of their use. 29 | 30 | ## Alternatives 31 | 32 | * The JDK API for operations on `BigInteger` and `BigDecimal` 33 | * Converting basic numeric types to and from "big" types like `1.toBigInteger()` or `2.toBig()` or possibly `3.big` 34 | 35 | ## Dependencies 36 | 37 | * JDK6 `BigInteger` and `BigDecimal` 38 | 39 | ## Placement 40 | 41 | * Standard Library 42 | * [BigInteger](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/java.math.-big-integer/) and [BigDecimal](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/java.math.-big-decimal/) 43 | extensions are placed into `kotlin` package, since there is already a part of the prosed API 44 | * String-to-number conversions are placed into `kotlin.text` package 45 | 46 | ## Reference implementation 47 | 48 | Some of the following functions are already implemented in the stdlib, but listed here to provide the whole picture. Those functions are marked with `/* implemented */`. 49 | 50 | >Implementation notes: 51 | > 52 | > - Boxing of primitives should not be a concern 53 | > - Conversion between `BigInteger` and `BigDouble` has no cost except for the obvious creating of a new object 54 | > - `Number` is an open class, so generic functions accepting `Number` may clash with something else 55 | > - default rounding mode is `HALF_EVEN` ([KT-10462] (https://youtrack.jetbrains.com/issue/KT-10462)) 56 | > - `toInt`, `toLong`, etc are already implemented on `Number` 57 | > 58 | > (JDK) Note: For values other than float and double NaN and ±Infinity, this constructor is compatible with the values returned by `Float.toString(float)` and `Double.toString(double)`. This is generally the preferred way to convert a float or double into a BigDecimal, as it doesn't suffer from the unpredictability of the `BigDecimal(double)` constructor. 59 | > 60 | > (JDK) The unsigned right shift operator (>>>) [on BigInteger] is omitted, as this operation makes little sense in combination with the "infinite word size" abstraction provided by this class. 61 | 62 | #### BigInteger: 63 | 64 | BigInteger.plus(BigInteger) /* implemented */ 65 | BigInteger.minus(BigInteger) /* implemented */ 66 | BigInteger.times(BigInteger) /* implemented */ 67 | BigInteger.div(BigInteger) /* implemented */ 68 | BigInteger.rem(BigInteger) 69 | BigInteger.unaryMinus() /* implemented */ 70 | 71 | BigInteger.inv() //uses `BigInteger#not` 72 | BigInteger.and(BigInteger) 73 | BigInteger.or(BigInteger) 74 | BigInteger.xor(BigInteger) 75 | BigInteger.shl(Int) 76 | BigInteger.shr(Int) 77 | 78 | String.toBigInteger() 79 | String.toBigInteger(radix: Int) 80 | String.toBigIntegerOrNull() 81 | String.toBigIntegerOrNull(radix: Int) 82 | 83 | Int.toBigInteger() 84 | Long.toBigInteger() 85 | 86 | BigInteger.toBigDecimal() 87 | BigInteger.toBigDecimal(scale, MathContext) 88 | // `Float` and `Double` would lose information 89 | 90 | ####BigDecimal: 91 | 92 | BigDecimal.plus(BigDecimal) /* implemented */ 93 | BigDecimal.minus(BigDecimal) /* implemented */ 94 | BigDecimal.times(BigDecimal) /* implemented */ 95 | BigDecimal.div(BigDecimal) /* implemented */ //uses BigDecimal#divide(divisor, RoundingMode.HALF_EVEN) 96 | BigDecimal.rem(BigDecimal) /* implemented */ 97 | BigDecimal.unaryMinus() /* implemented */ 98 | 99 | Int.toBigDecimal() 100 | Long.toBigDecimal() 101 | Float.toBigDecimal() 102 | Double.toBigDecimal() 103 | 104 | String.toBigDecimal() 105 | String.toBigDecimalOrNull() 106 | String.toBigDecimal(MathContext) 107 | String.toBigDecimalOrNull(MathContext) 108 | 109 | // BigDecimal.toBigInteger() /* implemented in JDK */ 110 | 111 | #### Do not have direct analogy in JDK: 112 | 113 | BigInteger.inc() 114 | BigInteger.dec() 115 | 116 | BigDecimal.inc() 117 | BigDecimal.dec() 118 | 119 | ## Future advancements 120 | 121 | ### Universal comparison 122 | 123 | Implementing `compareTo` between `BigInteger` and `BigDecimal`. That will not affect interface `Comparable<>`. Requires only 2 additional function. 124 | 125 | ### Mixed one-sided operations 126 | 127 | Implement mixed one-sided productive operations between "big" types and the basic ones (same logic as with `String.plus(Int)`). Encourages the usage of the "big" types, may be useful for scientific applications. The goal is to write 99% of formulas without explicit conversions. This may be a part of `kotlinx`. 128 | 129 | val a = BigInteger.valueOf(100) 130 | val b: BigInteger = a + 30 // Convenient! 131 | 30 + a //Error 132 | 133 | Reference implementation requires (at least) 40 methods: 134 | 135 | BigInteger.plus() 136 | BigInteger.minus() 137 | BigInteger.times() 138 | BigInteger.div() 139 | BigInteger.rem() 140 | 141 | BigDecimal.plus(Number) 142 | BigDecimal.minus(Number) 143 | BigDecimal.times(Number) 144 | BigDecimal.div(Number) 145 | BigDecimal.rem(Number) 146 | 147 | where `` is one of the 6 basic types. 148 | -------------------------------------------------------------------------------- /proposals/stdlib/char-int-conversions.md: -------------------------------------------------------------------------------- 1 | # Straighten Char-to-code and Char-to-digit conversions out 2 | 3 | * **Type**: Standard Library API proposal 4 | * **Authors**: Ilya Gorbunov, Abduqodiri Qurbonzoda 5 | * **Status**: Implemented in Kotlin 1.5 6 | * **Prototype**: Implemented 7 | * **Discussion**: [KEEP-227](https://github.com/Kotlin/KEEP/issues/227) 8 | * **Related issues**: [KT-23451](https://youtrack.jetbrains.com/issue/KT-23451) 9 | 10 | 11 | ## Summary 12 | 13 | Deprecate the existing `Char`<=>`Number` conversion functions and introduce new ones to avoid incorrect usages caused 14 | by confusion from the conversion function names. 15 | 16 | ## Existing API review 17 | 18 | Currently, `Char` type has the following conversion functions in the standard library: 19 | 20 | * `fun Char.toByte(): Byte` 21 | * `fun Char.toShort(): Short` 22 | * `fun Char.toInt(): Int` 23 | * `fun Char.toLong(): Long` 24 | * `fun Char.toFloat(): Float` 25 | * `fun Char.toDouble(): Double` 26 | 27 | The functions above return the integer value of the `Char` code (the UTF-16 code unit) converted to the specified 28 | numeric type with either widening, or narrowing conversion. 29 | For the particular numeric type `N`, the value returned from `Char.toN()` is essentially equal to `Char.toInt().toN()`. 30 | 31 | Also, there are inverse operations to get the `Char` corresponding to the specified UTF-16 code unit: 32 | * `abstract fun Number.toChar(): Char` 33 | * `fun Byte.toChar(): Char` 34 | * `fun Short.toChar(): Char` 35 | * `fun Int.toChar(): Char` 36 | * `fun Long.toChar(): Char` 37 | * `fun Float.toChar(): Char` 38 | * `fun Double.toChar(): Char` 39 | 40 | The value returned from `N.toChar()` is essentially equal to `N.toInt().toChar()` for the particular numeric type `N`. 41 | Note that `Int.toChar()` uses the least significant 16 bits of the receiver `Int` value to represent the resulting `Char`. 42 | 43 | ## Motivation 44 | 45 | The conversions above are often found confusing. People calling `Char.toInt()` usually expect to get the digit value of char, 46 | similar to how `String.toInt()` works. Reverse conversions like `Double.toChar()` usually make no sense, 47 | and it would be more clear if the intent was expressed explicitly by converting the number to `Int` first 48 | and then getting the character corresponding to that `Int` code. 49 | 50 | Some examples of the confusion: 51 | 52 | * https://discuss.kotlinlang.org/t/convert-char-to-byte-gives-wrong-result/11548 53 | * https://stackoverflow.com/questions/47592167/how-do-i-convert-a-char-to-int 54 | * https://stackoverflow.com/questions/51961220/what-happens-with-toint 55 | * https://stackoverflow.com/questions/52393028/how-to-convert-character-to-its-integer-value-in-kotlin 56 | * https://stackoverflow.com/questions/57420203/how-to-convert-digit-to-character-in-kotlin 57 | * https://stackoverflow.com/questions/57515225/why-it-kotlin-giving-me-the-wrong-int-value-when-converting-from-a-string 58 | * https://stackoverflow.com/questions/61712411/converting-big-number-into-string-and-then-splitting-into-single-digits-results 59 | * https://blog.jdriven.com/2019/10/converting-char-to-int-in-kotlin/ 60 | 61 | To alleviate the confusion, we would like to deprecate the conversion functions above 62 | and introduce new ones with the names that make it clear what the function purpose is. 63 | 64 | ## Description 65 | 66 | For each `N` type where `N` is `Int`, `Short`, `Long`, `Byte`, `Double`, `Float` 67 | we are going to: 68 | 69 | * Deprecate `Char.toN()` functions 70 | * Deprecate `N.toChar()` functions 71 | 72 | We also need to deprecate `Number.toChar()` function, change its modality from `abstract` to `open`, and provide 73 | the default implementation of this function, `toInt().toChar()`. 74 | 75 | > **Update:** Deprecation of `Int.toChar()` and `Number.toChar()` is postponed to future versions. 76 | 77 | * Introduce functions to get the integer code of a `Char` and to construct a `Char` from the given code. 78 | 79 | ```kotlin 80 | /** 81 | * Creates a Char with the specified [code], or throws an exception if the [code] is out of `Char.MIN_VALUE.code..Char.MAX_VALUE.code`. 82 | * 83 | * If the program that calls this function is written in a way that only valid [code] is passed as the argument, 84 | * using the overload that takes a [UShort] argument is preferable (`Char(intValue.toUShort())`). 85 | * That overload doesn't check validity of the argument, and may improve program performance when the function is called routinely inside a loop. 86 | */ 87 | fun Char(code: Int): Char 88 | 89 | /** 90 | * Creates a Char with the specified [code]. 91 | */ 92 | fun Char(code: UShort): Char 93 | 94 | /** 95 | * Returns the code of this Char. 96 | * 97 | * Code of a Char is the value it was constructed with, and the UTF-16 code unit corresponding to this Char. 98 | */ 99 | val Char.code: Int 100 | ``` 101 | These functions will be proposed as replacements for the deprecated conversions above. 102 | For example: 103 | ```kotlin 104 | val char: Char = ... 105 | char.toInt() -> char.code 106 | char.toShort() -> char.code.toShort() 107 | ``` 108 | 109 | - Introduce functions to convert a `Char` to the numeric value of the digit it represents: 110 | 111 | ```kotlin 112 | /** 113 | * Returns the numeric value of the digit that this Char represents in the specified [radix]. 114 | * Throws an exception if the [radix] is not in the range `2..36` or if this Char is not a valid digit in the specified [radix]. 115 | * 116 | * A Char is considered to represent a digit in the specified [radix] if at least one of the following is true: 117 | * - [isDigit] is `true` for the Char and the Unicode decimal digit value of the character is less than the specified [radix]. In this case the decimal digit value is returned. 118 | * - The Char is one of the uppercase Latin letters 'A' through 'Z' and its [code] is less than `radix + 'A'.code - 10`. In this case, `this.code - 'A'.code + 10` is returned. 119 | * - The Char is one of the lowercase Latin letters 'a' through 'z' and its [code] is less than `radix + 'a'.code - 10`. In this case, `this.code - 'a'.code + 10` is returned. 120 | */ 121 | fun Char.digitToInt(radix: Int): Int 122 | 123 | /** 124 | * Returns the numeric value of the digit that this Char represents in the specified [radix], or `null` if this Char is not a valid digit in the specified [radix]. 125 | * Throws an exception if the [radix] is not in the range `2..36`. 126 | * 127 | * A Char is considered to represent a digit in the specified [radix] if at least one of the following is true: 128 | * - [isDigit] is `true` for the Char and the Unicode decimal digit value of the character is less than the specified [radix]. In this case the decimal digit value is returned. 129 | * - The Char is one of the uppercase Latin letters 'A' through 'Z' and its [code] is less than `radix + 'A'.code - 10`. In this case, `this.code - 'A'.code + 10` is returned. 130 | * - The Char is one of the lowercase Latin letters 'a' through 'z' and its [code] is less than `radix + 'a'.code - 10`. In this case, `this.code - 'a'.code + 10` is returned. 131 | */ 132 | fun Char.digitToIntOrNull(radix: Int): Int? 133 | ``` 134 | 135 | `isDigit` is considered to be `true` for a `Char` if the Unicode general category of the `Char` is "Nd" (`CharCategory.DECIMAL_DIGIT_NUMBER`). 136 | 137 | - Introduce an extension function for `Int` to covert the non-negative single digit it represents 138 | to the corresponding `Char` representation. 139 | 140 | ```kotlin 141 | 142 | /** 143 | * Returns the Char that represents this numeric digit value in the specified [radix]. 144 | * Throws an exception if the [radix] is not in the range `2..36` or if this value is not less than the specified [radix]. 145 | * 146 | * If this value is less than `10`, the decimal digit Char with code `'0'.code + this` is returned. 147 | * Otherwise, the uppercase Latin letter with code `'A'.code + this - 10` is returned. 148 | */ 149 | fun Int.digitToChar(radix: Int): Char 150 | ``` 151 | 152 | Similarly to `String.toInt/toIntOrNull()` functions, we will introduce overloads with no arguments as well defaulting `radix` to 10: 153 | - `fun Char.digitToInt(): Int` 154 | - `fun Char.digitToIntOrNull(): Int?` 155 | - `fun Int.digitToChar(): Char` 156 | 157 | ## Dependencies 158 | 159 | - The correct implementation of `Char.digitToInt` relies on knowing all digit ranges among the supported 160 | `Char` values, see the issues [KT-30652](https://youtrack.jetbrains.com/issue/KT-30652) and [KT-39177](https://youtrack.jetbrains.com/issue/KT-39177). 161 | 162 | - Deprecating `Number.toChar()` and changing its modality will require a special support in the compiler. 163 | 164 | ## Placement 165 | 166 | - module `kotlin-stdlib` 167 | - packages 168 | - `kotlin` for `fun Char(code: Int): Char`, `fun Char(code: UShort): Char` and `val Char.code: Int` 169 | - `kotlin.text` for digit-to-int and digit-to-char conversion functions 170 | 171 | ## Reference implementation 172 | 173 | The reference implementation is provided in the pull request [PR #3969](https://github.com/JetBrains/kotlin/pull/3969). 174 | 175 | ## Naming 176 | 177 | Alternative naming suggestions are welcome. 178 | 179 | ## Compatibility impact 180 | 181 | The introduced functions will be marked with `@ExperimentalStdlibApi` until the next major release of Kotlin, 1.5. 182 | With the release of Kotlin 1.5 the new functions will become stable, and the old Number <-> Char functions will become deprecated. 183 | 184 | Previously compiled programs and libraries that used deprecated functions will still be able to run with Kotlin 1.5 and further. 185 | -------------------------------------------------------------------------------- /proposals/stdlib/durations-and-clocks.md: -------------------------------------------------------------------------------- 1 | The proposal was renamed. See the actual version in [durations-and-time-measurement.md](durations-and-time-measurement.md). -------------------------------------------------------------------------------- /proposals/stdlib/group-and-fold.md: -------------------------------------------------------------------------------- 1 | # Group by key and fold each group simultaneously 2 | 3 | * **Type**: Standard Library API proposal 4 | * **Author**: Ilya Gorbunov 5 | * **Status**: Implemented in Kotlin 1.1 6 | * **Prototype**: Implemented 7 | * **Discussion**: [KEEP-23](https://github.com/Kotlin/KEEP/issues/23) 8 | 9 | ## Summary 10 | 11 | Introduce a function similar to `groupBy`, but folding values of each group on the fly. 12 | 13 | ## Similar API review 14 | 15 | * Ceylon: [`Iterable.summarize`](http://modules.ceylon-lang.org/repo/1/ceylon/language/1.2.0/module-doc/api/Iterable.type.html#summarize). 16 | 17 | ## Description 18 | 19 | The following extension for `Iterable` and iterable-like receivers is proposed: 20 | 21 | ```kotlin 22 | public inline fun Iterable.groupingBy( 23 | crossinline keySelector: (T) -> K 24 | ): Grouping 25 | ``` 26 | 27 | where `Grouping` is an interface defined as following: 28 | 29 | ```kotlin 30 | // A wrapper around a source of elements which could be iterated 31 | // with the `keySelector: (T) -> K` function attached to it. 32 | interface Grouping { 33 | fun sourceIterator(): Iterator 34 | fun keyOf(element: T): K 35 | } 36 | ``` 37 | 38 | Provide the following extensions for `Grouping`: 39 | 40 | ```kotlin 41 | // Generic aggregation (fold or reduce) 42 | // The most generic form of aggregation, that other overloads 43 | // delegate their implementation to. 44 | public inline fun Grouping.aggregate( 45 | operation: (key: K, value: R?, element: T, first: Boolean) -> R 46 | ): Map 47 | 48 | public inline fun > Grouping.aggregateTo( 49 | destination: M, 50 | operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R 51 | ): M 52 | 53 | 54 | // Key-parametrized fold 55 | // Here the initial value and the operation depend on the key of a group. 56 | public inline fun Grouping.fold( 57 | initialValueSelector: (key: K, element: T) -> R, 58 | operation: (key: K, accumulator: R, element: T) -> R 59 | ): Map 60 | 61 | public inline fun > Grouping.foldTo( 62 | destination: M, 63 | initialValueSelector: (key: K, element: T) -> R, 64 | operation: (key: K, accumulator: R, element: T) -> R 65 | ): M 66 | 67 | 68 | // Simplified fold 69 | // The `initialValue` is a constant and the operation do not depend on the group key. 70 | public inline fun Grouping.fold( 71 | initialValue: R, 72 | operation: (accumulator: R, element: T) -> R 73 | ): Map 74 | 75 | public inline fun > Grouping.foldTo( 76 | destination: M, 77 | initialValue: R, 78 | operation: (accumulator: R, element: T) -> R 79 | ): M 80 | 81 | 82 | // Reduce 83 | public inline fun Grouping.reduce( 84 | operation: (key: K, accumulator: S, element: T) -> S 85 | ): Map 86 | 87 | public inline fun > Grouping.reduceTo( 88 | destination: M, 89 | operation: (key: K, accumulator: S, element: T) -> S 90 | ): M 91 | 92 | 93 | // Count 94 | public fun Grouping.eachCount(): Map 95 | 96 | public fun > Grouping.eachCountTo(destination: M): M 97 | 98 | 99 | // SumBy 100 | public inline fun Grouping.eachSumOf( 101 | valueSelector: (T) -> Int 102 | ): Map 103 | 104 | public inline fun > Grouping.eachSumOfTo( 105 | destination: M, 106 | valueSelector: (T) -> Int 107 | ): M 108 | 109 | ``` 110 | 111 | ## Use cases 112 | 113 | The most common use case is doing some aggregation, broken down by some key: 114 | 115 | 1. given a text, count frequencies of words/characters; 116 | 117 | ```kotlin 118 | val frequencies = words.groupingBy { it }.eachCount() 119 | ``` 120 | 121 | 2. given orders in all stores, sum total value of orders by store; 122 | ```kotlin 123 | val storeTotals = 124 | orders.groupingBy { it.store } 125 | .eachSumOf { order -> order.total } 126 | ``` 127 | 128 | 3. given orders of all clients, find an order with the maximum total for each client. 129 | ```kotlin 130 | val bestClientOrders = 131 | orders.groupingBy { it.client } 132 | .reduce { k, maxValueOrder, order -> maxOfBy(order, maxValueOrder) { it.total } } 133 | ``` 134 | 135 | ## Alternatives 136 | 137 | * Just use `groupBy` and then `mapValues` on a resulting map. 138 | * Pro: many operations are supported on a group of values, not just `fold`. 139 | * Con: intermediate map of lists is created. 140 | 141 | Example: 142 | 143 | ```kotlin 144 | val frequencies: Map = 145 | words.groupBy { it }.mapValues { it.value.size } 146 | ``` 147 | 148 | * Use Rx.observables. 149 | * Pro: many operations supported 150 | * Con: observable transformations overhead, 151 | asymptotically less than that of `mapValues`. 152 | 153 | ```kotlin 154 | val frequencies: Map = 155 | Observable.from(values) 156 | .groupBy { it } 157 | .flatMap { g -> g.count().map { g.key to it } } 158 | .toBlocking() 159 | .toIterable() 160 | .toMap() 161 | ``` 162 | 163 | * [Previous version of this proposal](https://github.com/Kotlin/KEEP/blob/f1cdce73b5c1983d9380d632d2fcdd73c6253c23/proposals/stdlib/group-and-fold.md) 164 | intended to introduce fully inlined operations, such as `groupFoldBy(keySelector, initialValue, operation)`. 165 | * Pro: do not require a wrapper `Grouping` object to be allocated. 166 | * Pro: may require less intermediate boxing in case of receivers with primitive elements, such as primitive arrays and char sequences. 167 | * Con: having several functional parameters makes an invocation awkward. 168 | * Con: duplicating all the operations for each receiver type implies high method count. 169 | 170 | A benchmark was conducted to study the performance impact of not inlining `keySelector` function. 171 | That impact [was shown](https://github.com/ilya-g/kotlinx.collections.experimental/blob/master/kotlinx-collections-experimental/benchmarks/src/main/kotlin/results.txt) 172 | to be negligible for receivers with object elements, 173 | and on the other side noticeable for receivers with primitive elements. 174 | However, the latter is hardly to be the use case covered by this operation. 175 | 176 | ## Dependencies 177 | 178 | Only a subset of Kotlin Standard Library available on all supported platforms is required. 179 | 180 | ## Placement 181 | 182 | - module: `kotlin-stdlib` 183 | - packages: `kotlin.collections`, `kotlin.sequences`, `kotlin.text` 184 | 185 | ## Reference implementation 186 | 187 | The prototypes are implemented in the repository [kotlinx.collections.experimental](https://github.com/ilya-g/kotlinx.collections.experimental/tree/master/kotlinx-collections-experimental/src/main/kotlin/kotlinx.collections.experimental/grouping). 188 | 189 | The final implementation has been merged into the standard library, here is the [commit set](https://github.com/JetBrains/kotlin/compare/4816474~6...4816474). 190 | 191 | ### Receivers 192 | 193 | It is possible to provide `groupingBy` operation for each collection-like receiver, such as 194 | `Iterable`, `Sequence`, `Array`, `CharSequence`, `(Primitive)Array`, 195 | however for primitive arrays this operation does not make much sense. 196 | 197 | ### Return type 198 | 199 | The operation returns `Map`, where `K` is the type of the key and `R` is the type of the accumulator. 200 | 201 | ## Questions 202 | 203 | 1. Naming options: 204 | * `groupingBy` or just `grouping` 205 | 206 | There is a risk of confusing `groupingBy` and `groupBy`. 207 | 208 | * `count`/`sumBy` can be misinterpreted as operations on the whole collection, rather on each group. 209 | * **resolution**: `countEach`, `sumEachBy` 210 | 211 | * `Grouping` is often referenced in docs as "grouping source", so maybe we should name it `GroupingSource`? 212 | 213 | 2. Which general forms of `fold`/`reduce`/`aggregate` should we provide? 214 | * Method count is increased 215 | * **resolution**: since there's a single receiver for all operations, the impact on method count is not that big. 216 | * Having them as overloads hurts completion during gradual typing. 217 | 3. Should we provide `To`-overloads (like `groupByTo`) with a mutable map as a target parameter? 218 | * **resolution**: there are no easy alternatives to achieve the same results without these overloads, 219 | so we should provide them. 220 | 221 | 4. Having primitive fold accumulators stored in a map introduces a lot of boxing. 222 | * **resolution**: provide optimized implementations of `countEach` and `sumEachBy`. 223 | Note that, there's no such optimization for `countEachTo` and `sumEachByTo`. 224 | 225 | 5. Should we provide `sumEachByLong` and `sumEachByDouble` operations, 226 | or wait until [KT-11265](https://youtrack.jetbrains.com/issue/KT-11265) is resolved, 227 | so we could have them as overloads of `eachSumOf`? 228 | * **resolution**: provide them as overloads of `eachSumOf`, 229 | but do not provide `eachSumOf` itself until KT-11265 is fixed. 230 | 231 | ## Future advancements 232 | 233 | * If we do not provide some forms, evaluate whether they could be introduced later. 234 | * Converting collection operations to folds can be error-prone, maybe we should provide 235 | some standard reducer functions, such as Count, Sum etc. 236 | * In Java 8 it is possible to engage standard Collectors API by providing the operation 237 | `Grouping.collectEach(Collector)` 238 | -------------------------------------------------------------------------------- /proposals/stdlib/locale-agnostic-case-conversions.md: -------------------------------------------------------------------------------- 1 | # Locale-agnostic case conversions by default 2 | 3 | * **Type**: Standard Library API proposal 4 | * **Author**: Abduqodiri Qurbonzoda 5 | * **Contributors**: Ilya Gorbunov 6 | * **Status**: Implemented in Kotlin 1.5 7 | * **Prototype**: Implemented 8 | * **Discussion**: [KEEP-223](https://github.com/Kotlin/KEEP/issues/223) 9 | * **Relates issues**: [KT-40292](https://youtrack.jetbrains.com/issue/KT-40292), [KT-43023](https://youtrack.jetbrains.com/issue/KT-43023) 10 | 11 | ## Summary 12 | 13 | Make case conversion functions rely on the invariant locale by default. 14 | 15 | ## Current API review 16 | 17 | Currently, the standard library provides the following common extension functions: 18 | 19 | * `fun String.toLowerCase(): String` 20 | * `fun String.toUpperCase(): String` 21 | * `fun String.capitalize(): String` 22 | * `fun String.decapitalize(): String` 23 | 24 | The functions above convert letters of the receiver `String` using the rules of: 25 | 26 | - the default system locale in Kotlin/JVM; 27 | - the invariant locale in other Kotlin platforms. 28 | 29 | Kotlin/JVM additionally provides overloads that allow specifying the locale explicitly: 30 | 31 | * `fun String.toLowerCase(locale: Locale): String` 32 | * `fun String.toUpperCase(locale: Locale): String` 33 | * `fun String.capitalize(locale: Locale): String` 34 | * `fun String.decapitalize(locale: Locale): String` 35 | 36 | `Char` has the following locale-agnostic case conversion functions: 37 | 38 | * `fun Char.toLowerCase(): Char` - Common 39 | * `fun Char.toUpperCase(): Char` - Common 40 | * `fun Char.toTitleCase(): Char` - Kotlin/JVM 41 | 42 | ## Motivation 43 | 44 | Our researches show that people often use locale-sensitive functions mentioned above without realizing the fact that their code behaves differently 45 | in different geographies/platform locale settings. 46 | 47 | [The caution from Java team](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#toUpperCase()) highlights significance of the issue: 48 | >Note: This method is locale sensitive, and may produce unexpected results if used for strings that are intended to be interpreted locale independently. 49 | >Examples are programming language identifiers, protocol keys, and HTML tags. For instance, `"title".toUpperCase()` in a Turkish locale returns `"TİTLE"`, 50 | >where `'İ'` (`'\u0130'`) is the `LATIN CAPITAL LETTER I WITH DOT ABOVE` character. To obtain correct results for locale insensitive strings, use `toUpperCase(Locale.ROOT)`. 51 | 52 | Bug report related to the caution: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6208680 53 | 54 | However, documentation doesn't always prevent misuse. See bug reports related to this issue in big projects: 55 | * https://issues.apache.org/jira/browse/SPARK-20156 56 | * https://github.com/gradle/gradle/issues/1506 57 | 58 | Related blog posts: 59 | * https://lotusnotus.com/lotusnotus_en.nsf/dx/dotless-i-tolowercase-and-touppercase-functions-use-responsibly.htm 60 | * https://javapapers.com/core-java/javas-tolowercase-has-got-a-surprise-for-you/ 61 | 62 | To overcome the issue we would like to deprecate the current API and introduce new locale-agnostic functions. 63 | 64 | ## Description 65 | 66 | First, we introduce new case conversion functions that do not depend implicitly on the default locale: 67 | 68 | ```kotlin 69 | // using the invariant locale 70 | fun String.uppercase(): String 71 | fun String.lowercase(): String 72 | 73 | // using the invariant locale, possible multi-char result 74 | fun Char.lowercase(): String 75 | fun Char.uppercase(): String 76 | fun Char.titlecase(): String 77 | 78 | // using the invariant locale, char to single char conversion 79 | fun Char.lowercaseChar(): Char 80 | fun Char.uppercaseChar(): Char 81 | fun Char.titlecaseChar(): Char 82 | ``` 83 | 84 | In Kotlin/JVM we additionally introduce overloads with an explicit `Locale` parameter. 85 | 86 | ```kotlin 87 | // using the specified locale 88 | fun String.uppercase(Locale): String 89 | fun String.lowercase(Locale): String 90 | 91 | // using the specified locale, possible multi-char result 92 | fun Char.lowercase(Locale): String 93 | fun Char.uppercase(Locale): String 94 | fun Char.titlecase(Locale): String 95 | ``` 96 | 97 | Then we deprecate the existing `toUpperCase/toLowerCase` functions with the following replacements: 98 | ```kotlin 99 | Char.toUpperCase() -> Char.uppercaseChar() 100 | Char.toLowerCase() -> Char.lowercaseChar() 101 | Char.toTitleCase() -> Char.titlecaseChar() 102 | 103 | // in Kotlin/JVM 104 | String.toUpperCase() -> String.uppercase(Locale.getDefault()) 105 | String.toLowerCase() -> String.lowercase(Locale.getDefault()) 106 | String.toUpperCase(locale) -> String.uppercase(locale) 107 | String.toLowerCase(locale) -> String.lowercase(locale) 108 | // in other targets 109 | String.toUpperCase() -> String.uppercase() 110 | String.toLowerCase() -> String.lowercase() 111 | ``` 112 | 113 | For the functions `capitalize` and `decapitalize`, we're going to provide a more orthogonal replacement. 114 | First, we introduce a function that allows to transform the first char of a string with the given function 115 | that returns either new `Char`, or new `CharSequence`: 116 | ```kotlin 117 | fun String.replaceFirstChar(transform: (Char) -> Char): String 118 | fun String.replaceFirstChar(transform: (Char) -> CharSequence): String 119 | ``` 120 | 121 | Then we can combine the function above with the char case transforming functions to achieve the following replacements: 122 | ```kotlin 123 | // in all targets 124 | String.capitalize() -> String.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it } 125 | String.decapitalize() -> String.replaceFirstChar { it.lowercase() } 126 | 127 | // in JVM 128 | String.capitalize() -> String.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it } 129 | String.capitalize(locale) -> String.replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it } 130 | String.decapitalize() -> String.replaceFirstChar { it.lowercase(Locale.getDefault()) } 131 | String.decapitalize(locale) -> String.replaceFirstChar { it.lowercase(locale) } 132 | ``` 133 | 134 | The replacements are verbose, but preserve the behavior as close as possible. The resulting code can be simplified further 135 | if a more simple behavior is desired, for example to `String.replaceFirstChar { it.uppercaseChar() }`. 136 | 137 | An additional benefit we get by renaming the functions `toUpperCase/toLowerCase` is that they become more consistent 138 | with the existing Kotlin naming conventions. 139 | In Kotlin, functions starting with the preposition `to` typically convert the receiver to an instance of another type, 140 | which is not the case with the case conversion functions. 141 | 142 | ## Dependencies 143 | 144 | Implementing `Char.titlecase` function in platforms other than Kotlin/JVM would require Unicode title case mapping tables. 145 | 146 | ## Placement 147 | 148 | - module `kotlin-stdlib` 149 | - package `kotlin.text` 150 | 151 | ## Reference implementation 152 | 153 | The reference implementation is provided in the pull request [PR #3780](https://github.com/JetBrains/kotlin/pull/3780). 154 | 155 | ## Naming 156 | 157 | Alternative naming suggestions are discussable. 158 | 159 | ## Compatibility impact 160 | 161 | The introduced functions will be marked with `@ExperimentalStdlibApi` until the next major release of Kotlin, 1.5. 162 | With release of Kotlin 1.5 the new functions will become stable, and the old functions will become deprecated. 163 | 164 | Previously compiled programs and libraries that used the deprecated functions will still be able to run with Kotlin 1.5 and further. 165 | 166 | ### Receiver types 167 | 168 | The operations could be introduced for `StringBuilder` too, but that is out of scope of this proposal. 169 | -------------------------------------------------------------------------------- /proposals/stdlib/locale-agnostic-string-conversions.md: -------------------------------------------------------------------------------- 1 | The proposal was renamed. See the actual version in [locale-agnostic-case-conversions.md](locale-agnostic-case-conversions.md). -------------------------------------------------------------------------------- /proposals/stdlib/map-copying.md: -------------------------------------------------------------------------------- 1 | # Extensions to copy map contents 2 | 3 | * **Type**: Standard Library API proposal 4 | * **Author**: Ilya Gorbunov 5 | * **Status**: Implemented in Kotlin 1.1 6 | * **Prototype**: Implemented 7 | * **Target tickets**: [KT-9108](https://youtrack.jetbrains.com/issue/KT-9108) 8 | * **Discussion**: [KEEP-13](https://github.com/Kotlin/KEEP/issues/13) 9 | 10 | ## Summary 11 | 12 | Standard Library provides several ways to copy collections such as `List` or `Set`, but doesn't do so for `Maps`. 13 | 14 | ## Similar API review 15 | 16 | The following table summarizes which operations are available for Lists, Sets and Maps in the Standard Library. 17 | 18 | | Operation | List | Set | Map | 19 | | -------------------- | ------- | -------- | ------- | 20 | | create from elements | listOf | setOf | mapOf | 21 | | create mutable from elements | mutableListOf | mutableSetOf | mutableMapOf | 22 | | create special from elements | arrayListOf | hashSetOf linkedSetOf sortedSetOf | hashMapOf linkedMapOf sortedMapOf | 23 | | create from iterable of elements | toList | toSet | toMap | 24 | | create mutable from iterable of elements | toMutableList | toMutableSet | - | 25 | | create special from iterable of elements | - | toHashSet toSortedSet | - | 26 | | fill target from iterable of elements | toCollection(MutableList) | toCollection(MutableSet) | toMap(MutableMap) | 27 | | copy of self | toList | toSet | PROPOSED: _toMap_ | 28 | | mutable copy of self | toMutableList | toMutableSet | PROPOSED: _toMutableMap_ | 29 | | special copy of self | - | toHashSet toSortedSet | toSortedMap | 30 | | fill target from self | toCollection(MutableList) | toCollection(MutableSet) | PROPOSED: _toMap(MutableMap)_ | 31 | 32 | The elements to create map from here are key-value pairs `Pair`. 33 | 34 | ## Description 35 | 36 | It is proposed to introduce extensions for map which make various copies of the map contents: 37 | - `Map.toMap(): Map` - read only copy of map 38 | - `Map.toMutableMap(): MutableMap` - mutable copy of map 39 | - `Map.toMap(M <: MutableMap): M` - copy map to specified mutable map and return it 40 | 41 | ## Use cases 42 | 43 | 1. Defensive read-only copy of map 44 | ```kotlin 45 | class ImmutablePropertyBag(map: Map) { 46 | private val mapCopy = map.toMap() 47 | 48 | val setting1: String by mapCopy 49 | val setting2: Int by mapCopy 50 | } 51 | ``` 52 | 2. Making a copy of map to populate it with additional entries later 53 | ```kotlin 54 | fun updateMappings(map: MutableMap): Unit { ... } 55 | 56 | val map: Map = ... 57 | val remapped = map.toMutableMap().apply { updateMappings(this) } 58 | ``` 59 | 60 | 3. Making specialized copy of a map 61 | ```kotlin 62 | val map: Map = ... 63 | val treeMap = map.toMap(TreeMap()) 64 | ``` 65 | 66 | ## Alternatives 67 | 68 | 1. Use concrete map implementation constructors 69 | ```kotlin 70 | val copy = HashMap(map) 71 | val treeMap = TreeMap(map) 72 | ``` 73 | 74 | Advantages: 75 | 76 | - allows to presize the map being created to hold all the content of the original map. 77 | 78 | Disadvantages: 79 | 80 | - in case 1 produces more specific map than required; 81 | - requires to decide on concrete implementation in advance; 82 | - not chaining to the end of operation chain. 83 | 84 | 2. Convert map to list of pairs and then back to map 85 | ```kotlin 86 | val copy = map.toList().toMap() 87 | val mutableCopy = map.toList().toMap(mutableMapOf()) 88 | val treeMap = map.toList().toMap(TreeMap()) 89 | ``` 90 | 91 | Disadvantages: 92 | 93 | - requires to create intermediate list of pairs, can cause significant GC pressure. 94 | 95 | 3. To populate destination map, use its `destination.putAll(this)` method instead of `this.toMap(destination)`. 96 | 97 | Disadvantages: 98 | 99 | - `putAll` returns `Unit`, so it doesn't chain well. 100 | 101 | ## Dependencies 102 | 103 | A subset of Kotlin Standard Library available on all supported platforms. 104 | 105 | ## Placement 106 | 107 | - module `kotlin-stdlib` 108 | - package `kotlin.collections` 109 | 110 | ## Reference implementation 111 | 112 | The final implementation has been merged into the standard library, here is the [commit set](https://github.com/JetBrains/kotlin/compare/e35a214~2...e35a214). 113 | 114 | ## Future advancements 115 | 116 | - Functions to create mutable or special map from iterable of elements. 117 | - Functions to create maps from iterable of `Map.Entry`. 118 | -------------------------------------------------------------------------------- /proposals/stdlib/occurrences-of.md: -------------------------------------------------------------------------------- 1 | # Extensions to find all occurrences of a pattern in a CharSequence 2 | 3 | * **Type**: Standard Library API proposal 4 | * **Author**: Christian Brüggemann 5 | * **Status**: Declined 6 | * **Prototype**: Implemented 7 | * **Discussion**: [KEEP-20](https://github.com/Kotlin/KEEP/issues/20) 8 | 9 | 10 | 11 | ## Summary 12 | 13 | An extension method `CharSequence.occurrencesOf(pattern: CharSequence, ignoreCase: Boolean, matchOverlapping: Boolean): Sequence` is proposed. 14 | 15 | It returns a lazy sequence of all indices where the non-regex literal pattern is found in the receiver, based on a linear-time algorithm. 16 | 17 | If `ignoreCase` is set to `true`, the method treats all characters case-insensitively, meaning that two characters are considered equal if `Char.equals(other = x, ignoreCase = true)` returns true. 18 | 19 | If `matchOverlapping` is set to `false`, only non-overlapping occurrences are matched, meaning that when an occurrence is found, the method does not look for occurrences of the pattern straddling the occurrence that was found just now. Otherwise, they are matched as well. 20 | 21 | ## Similar API review 22 | 23 | * Repeated calls to `indexOf` with incremented startIndex with superlinear runtime, especially if overlapping occurrences are searched for. 24 | * Similarly, a regex pattern can be used to achieve this with the same properties as repeated calls to `indexOf`. 25 | 26 | ## Use cases 27 | 28 | This is a very fundamental method that can be used in a wide-range of specific use cases. Many of them can be grouped into the following categories: 29 | 30 | 1. Search for words/phrases in big documents, web pages, etc., be it in a web search engine, a text editor or even a word processor. A search function is built into many programs, which could benefit from this method. 31 | 2. Examples in bioinformatics: Find subsequences of DNA, proteins that are known to be important. 32 | 3. Natural language processing: Find out in which context a word is used. 33 | 34 | ## Alternatives 35 | 36 | * Use regular expressions: 37 | ```kotlin 38 | val occurrences = Regex.fromLiteral(pattern).findAll(text).map { it.range.start } 39 | ``` 40 | 41 | * With the solutions presented in the *Similar API review*, a loop would be needed: 42 | ```kotlin 43 | // given text: String and pattern: String 44 | val result = mutableListOf() 45 | var index = 0 46 | while (true) { 47 | index = text.indexOf(pattern, index) 48 | if (index < 0) break 49 | result.add(index) 50 | index += pattern.length 51 | } 52 | ``` 53 | * or a manual sequence implementation: 54 | ```kotlin 55 | val result = Sequence { 56 | object : AbstractIterator() { 57 | var index = 0 58 | override fun computeNext() { 59 | index = text.indexOf(pattern, index) 60 | if (index < 0) 61 | done() 62 | 63 | setNext(index) 64 | index += pattern.length 65 | } 66 | } 67 | } 68 | ``` 69 | 70 | However, this proposal is not about current solutions being too verbose, but too slow. 71 | 72 | 73 | ## Dependencies 74 | 75 | * CharSequence 76 | * IntArray 77 | * Sequence 78 | 79 | ## Placement 80 | 81 | - module: `kotlin-stdlib` 82 | - package: `kotlin.text` 83 | 84 | ## Reference implementation 85 | 86 | The API is already completely [implemented](https://github.com/JetBrains/kotlin/pull/821). The implementation uses early returns for trivial solutions and otherwise uses the Knuth-Morris-Pratt algorithm, which runs in `O(n)` where `n` is the length of the text that is analyzed for occurrences of the pattern. 87 | Tests are also already implemented. 88 | 89 | ## Unresolved questions 90 | 91 | * Is the operation really convenient for the use cases given? 92 | 93 | ## Future advancements 94 | 95 | In the very initial proposal, the method did not have support for finding only non-overlapping occurrences and was case-sensitive. Both of these issues have been eliminated and as such, the method is already very versatile and should not be subject to further extensions. 96 | 97 | Depending on how efficient `CharSequence.indexOf` currently is, the Knuth-Morris-Pratt part could be useful for that method as well. In the future, this could be evaluated with benchmarks. At a glance, it looks like its [implementation](https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/text/Strings.kt#L832) is naive. 98 | -------------------------------------------------------------------------------- /proposals/stdlib/on-each.md: -------------------------------------------------------------------------------- 1 | # Add function for peeking into Sequences and Iterables 2 | 3 | * **Type**: Standard Library API proposal 4 | * **Author**: Christian Brüggemann 5 | * **Status**: Implemented in Kotlin 1.1 6 | * **Prototype**: Implemented 7 | * **Related issues**: [KT-8220](https://youtrack.jetbrains.com/issue/KT-8220) 8 | * **Discussion**: [KEEP-47](https://github.com/Kotlin/KEEP/issues/47) 9 | 10 | 11 | ## Summary 12 | 13 | The goal is to introduce a function called `onEach`, which allows the user to perform an action on each element of a `Sequence` or an `Iterable` without breaking the functional chain like `forEach` does. 14 | 15 | ## Description 16 | 17 | When working with `Sequences` or `Iterables`, it can be useful to take a look at what items are passed through the stream. This is especially useful for debugging. Effectively, `onEach` would work like this: 18 | 19 | ```kotlin 20 | fun Sequence.onEach(f: (T) -> Unit): Sequence = map { f(it); it } 21 | // the following signatures are similified, look for actual ones in the prototype 22 | inline fun Iterable.onEach(f: (T) -> Unit): Iterable = apply { forEach(f) } 23 | inline fun Map.onEach(action: (Map.Entry) -> Unit) 24 | inline fun CharSequence.onEach(action: (Char) -> Unit) 25 | ``` 26 | 27 | However, `onEach` could also be implemented without relying on `map` by implementing a custom `Sequence`/`Iterator` which allows for peeking. 28 | 29 | ### Example usage 30 | 31 | ``` 32 | val sum = listOf(1, 2, 3).asSequence().onEach { println("Adding $it") }.sum() 33 | ``` 34 | 35 | ### Naming 36 | 37 | As described in the related [YouTrack issue](https://youtrack.jetbrains.com/issue/KT-8220#tab=Comments), the name `peek` could be confused with `Queue.peek`. Thus, the following alternatives was considered to mitigate this: 38 | 39 | * onEach 40 | * peekEach 41 | * doOnEach 42 | 43 | Overall, `onEach` was chosen to reflect similarity with `forEach`. 44 | 45 | ## Similar API review 46 | 47 | * Java `Streams` have `peek` ([docs](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#peek-java.util.function.Consumer-)) as well. 48 | * RxJava contains a method called `doOnNext` ([docs](http://reactivex.io/documentation/operators/do.html)) 49 | 50 | ## Use cases 51 | For debugging (the strongest use case): 52 | ```kotlin 53 | inputDir.walk() 54 | .filter { it.isFile && it.name.endsWith(".txt") } 55 | .peek { println("Moving $it to $outputDir") } 56 | .forEach { moveFile(it, File(outputDir, it.toRelativeString(inputDir))) } 57 | ``` 58 | For various intermediate operations, real-world example from a distributed hashtable: 59 | ```kotlin 60 | internalMap.entrySet().stream() 61 | .filter(entryBelongsToNeighbor(newNeighbor)) 62 | .peek(entry -> newNeighbor.send(new AckPutMessage<>(this, entry.getKey(), entry.getValue()))) 63 | .map(Map.Entry::getKey) 64 | .forEach(key -> System.out.println("Moving key " + toProperKey(key) + " to neighbor " + newNeighbor.id())); 65 | ``` 66 | 67 | * http://www.leveluplunch.com/java/examples/stream-intermediate-operations-example/ 68 | * The lambda passed to `peek` is a good place to set breakpoints 69 | * Perhaps more due to equivalent methods in Java 8 and RxJava, though it is hard to find examples where `peek` was not used for debugging (for instance `println` or using a logging method). Here are some from RxJava (`doOnNext`): 70 | * https://searchcode.com/file/116104738/main/src/cgeo/geocaching/sensors/Sensors.java#l-74 71 | * https://searchcode.com/file/115079675/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java#l-59 72 | * https://searchcode.com/file/115119425/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java#l-3661 73 | * https://searchcode.com/file/116021346/couchbase2/src/main/java/com/yahoo/ycsb/db/couchbase2/Couchbase2Client.java#l-621 74 | * https://searchcode.com/file/116104915/main/src/cgeo/geocaching/utils/RxUtils.java#l-36 75 | 76 | ## Alternatives 77 | 78 | - use `apply { forEach { } }` for collections, and roughly `map { apply { } }` for sequences. 79 | 80 | ## Dependencies 81 | 82 | Only the stdlib. 83 | 84 | ## Placement 85 | 86 | * Standard Library 87 | * package: kotlin.sequences in file [Sequences.kt](https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/collections/Sequences.kt) 88 | 89 | ## Reference implementation 90 | 91 | The final implementation has been merged into the standard library, here is the [commit set](https://github.com/JetBrains/kotlin/compare/dc57d69~2...dc57d69). 92 | 93 | ## Unresolved questions 94 | 95 | * Which name should be picked? 96 | - settled on `onEach` 97 | * Should `onEach` delegate to `map`/`forEach` or be implemented using a custom `Sequence`/`Iterator`? 98 | - `onEach` for sequences should delegate to `map` to reuse its possible optimizations. 99 | -------------------------------------------------------------------------------- /proposals/stdlib/runningFold-and-runningReduce.md: -------------------------------------------------------------------------------- 1 | # Successive results of accumulating functions 2 | 3 | * **Type**: Standard Library API proposal 4 | * **Author**: Abduqodiri Qurbonzoda 5 | * **Contributors**: Ilya Gorbunov 6 | * **Status**: Implemented in Kotlin 1.4.0 7 | * **Prototype**: Implemented 8 | * **Related issues**: [KT-7657](https://youtrack.jetbrains.com/issue/KT-7657) 9 | * **Discussion**: [KEEP-207](https://github.com/Kotlin/KEEP/issues/207) 10 | 11 | 12 | ## Summary 13 | 14 | The goal is to introduce extension functions that `fold` or `reduce` collection and 15 | return all intermediate accumulator values. Indexed variants are also introduced. 16 | 17 | ## Motivation / Use cases 18 | 19 | * https://discuss.kotlinlang.org/t/is-there-a-way-to-accumulate-and-map-in-the-same-operation/1492 20 | * https://discuss.kotlinlang.org/t/reductions-cumulative-sum/8364 21 | * https://discuss.kotlinlang.org/t/does-kotlin-have-something-similar-to-rxjava-scan/275 22 | 23 | * Running total (cumulative sum) 24 | ``` 25 | val runningTotal = numbers.runningFold(0) { acc, element -> acc + element } 26 | ``` 27 | * Running minimum 28 | ``` 29 | val runningMinimum = numbers.runningReduce { acc, element -> minOf(acc, element) } 30 | ``` 31 | * Snapshots after every operation on data 32 | ``` 33 | val initialText = "text in an editor" 34 | val textSnapshots = editOperations.runningFold(initialText) { text, edit -> edit(text) } 35 | ``` 36 | 37 | ## Similar API review 38 | 39 | * Stdlib currently implements `fold` and `reduce` extension function for `Iterable`, `Sequence`, `Grouping`, 40 | `CharSequence`, `Array`, `(Primitive)Array` and `(Unsigned)Array`. 41 | - `public inline fun Iterable.fold(initial: R, operation: (acc: R, element: T) -> R): R` 42 | - `public inline fun Iterable.reduce(operation: (acc: S, element: T) -> S): S` 43 | * Coroutines Flow has `scan` ([docs](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/scan.html)) and `scanReduce` ([docs](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/scan-reduce.html)) methods 44 | * RxJava contains method named `scan` ([docs](http://reactivex.io/documentation/operators/scan.html)) 45 | 46 | ## Description 47 | 48 | It is proposed to: 49 | * Introduce `runningFold` and `runningReduce` functions for all the collections that have `fold` and `reduce` functions, except `Grouping`: 50 | - `public inline fun Iterable.runningFold(initial: R, operation: (acc: R, T) -> R): List` 51 | - `public inline fun Iterable.runningReduce(operation: (acc: S, T) -> S): List` 52 | 53 | * Introduce indexed variants of `runningFold` and `runningReduce`: 54 | - `public inline fun Iterable.runningFoldIndexed(initial: R, operation: (index: Int, acc: R, T) -> R): List` 55 | - `public inline fun Iterable.runningReduceIndexed(operation: (index: Int, acc: S, T) -> S): List` 56 | 57 | Most of the frameworks (e.g. RxJava) and programming languages (e.g. Scala) that provide a function 58 | to fold collection and return intermediate reductions have named it `scan`. 59 | To make the `runningFold` discoverable for users coming from that frameworks 60 | we will also introduce `scan` and `scanIndexed` functions, equivalent to `runningFold` and `runningFoldIndexed` respectively. 61 | It will be helpful for users who got used to `scan` naming as well: 62 | - `public inline fun Iterable.scan(initial: R, operation: (acc: R, T) -> R): List` 63 | - `public inline fun Iterable.scanIndexed(initial: R, operation: (index: Int, acc: R, T) -> R): List` 64 | 65 | ## Alternatives 66 | 67 | * Use `fold` 68 | ``` 69 | val runningTotal = mutableListOf(0) 70 | val total = numbers.fold(0) { acc, element -> 71 | val nextAcc = acc + element 72 | runningTotal.add(nextAcc) 73 | nextAcc 74 | } 75 | ``` 76 | 77 | * Use `for` loop 78 | ``` 79 | var text = "text in an editor" 80 | val textSnapshots = mutableListOf(text) 81 | for ((index, edit) in editOperations.withIndex()) { 82 | text = edit(text) 83 | textSnapshots.add(text) 84 | 85 | Logger.logEdit(edit, index, text) 86 | } 87 | ``` 88 | 89 | ## Dependencies 90 | 91 | A subset of Kotlin Standard Library available on all supported platforms. 92 | 93 | ## Placement 94 | 95 | * module: `kotlin-stdlib` 96 | * packages: 97 | - `kotlin.sequences` for `Sequence` extension functions 98 | - `kotlin.text` for `CharSequence` extension functions 99 | - `kotlin.collections` for other receivers 100 | - all packages are imported by default 101 | 102 | ## Reference implementation 103 | 104 | Open review: https://upsource.jetbrains.com/kotlin/review/KOTLIN-CR-3763 105 | 106 | ## Future advancements 107 | 108 | * shorthand for cumulative sum (`runningSum`) 109 | * accumulate from right to left (`runningFoldRight`/`runningReduceRight`) 110 | * append intermediate results to the given collection (`runningFoldTo`/`runningReduceTo`) 111 | 112 | ## Naming 113 | 114 | * Function that reduces collection and returns intermediate reductions is called `scan` in most frameworks, 115 | regardless of using initial accumulator. In Kotlin to be consistent with existing `fold` and `reduce` functions 116 | we need to name variants of `scan` with and without initial value differently. 117 | 118 | ## Contracts 119 | 120 | * `runningFold`/`scan` return a list containing the `initial` argument when receiver is empty 121 | * `runningReduce` returns an empty list when receiver is empty 122 | * `last()` value of the returned list/sequence is equivalent to calling non-scanning counterpart, `fold`/`reduce` 123 | 124 | ### Receiver types 125 | 126 | The operations are provided for collections with `fold` and `reduce` operations, except for `Grouping`, 127 | i.e. `Iterable`, `Sequence`, `CharSequence`, `Array`, `(Primitive)Array` and `(Unsigned)Array`. 128 | 129 | ### Return type 130 | 131 | - the operation is lazy for sequence and thus returns a sequence 132 | - the operation is eager for iterables and returns a list 133 | - the return type can be changed by wrapping the receiver with `asSequence()`/`asIterable()` functions 134 | -------------------------------------------------------------------------------- /proposals/stdlib/scan-and-scanReduce.md: -------------------------------------------------------------------------------- 1 | The proposal was renamed. See the actual version in [runningFold-and-runningReduce.md](runningFold-and-runningReduce.md). -------------------------------------------------------------------------------- /proposals/stdlib/string-to-number.md: -------------------------------------------------------------------------------- 1 | # Exceptionless string to number conversions 2 | 3 | * **Type**: Standard Library API proposal 4 | * **Author**: Daniil Vodopian 5 | * **Contributors**: Ilya Gorbunov 6 | * **Status**: Implemented in Kotlin 1.1 7 | * **Prototype**: Implemented 8 | * **Related issues**: [KT-7930](https://youtrack.jetbrains.com/issue/KT-7930) 9 | * **Indirectly related issues**: [KT-8286](https://youtrack.jetbrains.com/issue/KT-8286), [KT-9374](https://youtrack.jetbrains.com/issue/KT-9374) 10 | * **Discussion**: [KEEP-19](https://github.com/Kotlin/KEEP/issues/19) 11 | 12 | ## Summary 13 | 14 | * Extension functions for `String` to convert it to various numeric types, such as `Int`, `Long`, `Double`, etc, 15 | without throwing an exception when the string doesn't represent a correct number. 16 | 17 | ## Similar API review 18 | 19 | * Standard Library: `String.toInt`, `String.toLong` etc — throw exception when a string is not a number. 20 | * C#: [`int.TryParse`](https://msdn.microsoft.com/en-us/library/f02979c7(v=vs.110).aspx), 21 | [`Double.TryParse`](https://msdn.microsoft.com/en-us/library/994c0zb1(v=vs.110).aspx), etc 22 | 23 | ## Use cases and motivation 24 | 25 | The goal is to have a way to convert a string to a number without throwing exceptions in situations 26 | where incorrectly formatted number is an expected, rather than an exceptional thing. 27 | Throwing and catching exceptions just to implement control flow could affect performance badly. 28 | 29 | - default value 30 | ```kotlin 31 | val x = str.toIntOrNull() ?: 42 32 | ``` 33 | 34 | - custom exception 35 | ```kotlin 36 | val x = str.toIntOrNull() ?: 37 | throw IllegalArgumentException("str is expected to be a valid number, but was '$str'") 38 | ``` 39 | 40 | - parsing a stream of formatted data 41 | ```kotlin 42 | val numbers = lines.map { it.toIntOrNull() } 43 | ``` 44 | 45 | ## Description 46 | 47 | It is proposed to provide following functions, one for each primitive numeric type: 48 | 49 | * `String.toByteOrNull(): Byte?` 50 | * `String.toShortOrNull(): Short?` 51 | * `String.toIntOrNull(): Int?` 52 | * `String.toLongOrNull(): Long?` 53 | * `String.toFloatOrNull(): Float?` 54 | * `String.toDoubleOrNull(): Double?` 55 | 56 | Integer parsing is reimplemented in order not to throw an exception on incorrect number, but to return `null` instead. 57 | 58 | Floating point numbers are parsed with JDK functions `java.lang.Float.parseFloat` and `java.lang.Double.parseDouble`, 59 | but an additional regex validation is done beforehand, which hopefully does not pass incorrect floating point numbers, 60 | and does not reject correct ones. 61 | 62 | ## Alternatives 63 | 64 | * It is possible to use the existing `String.to_Number_` functions inside a try-catch block, 65 | but it could hurt performance on a hot path: 66 | 67 | ``` 68 | val result = try { string.toInt() } catch (e: NumberFormatException) { null } 69 | ``` 70 | 71 | ## Dependencies 72 | 73 | JDK-specific dependencies, available in JDK6: 74 | 75 | * `java.lang.Character` 76 | 77 | ## Placement 78 | 79 | - module `kotlin-stdlib` 80 | - package `kotlin.text` 81 | 82 | ## Reference implementation 83 | 84 | The reference implementation is provided in the pull request [PR #839](https://github.com/JetBrains/kotlin/pull/839). 85 | 86 | 87 | ## Unresolved questions 88 | 89 | * Consider naming alternatives: 90 | * `String.tryToInt()` 91 | * `String.tryParseInt()` 92 | * Difference between JDK6 and JDK8 in allowing the leading `+` sign. 93 | * This is acceptable difference. 94 | * Returning nullable number introduces boxing which may be wasteful. 95 | * Investigate how to introduce `base`/`radix` parameter for string-to-integral type conversion functions [KT-8286](https://youtrack.jetbrains.com/issue/KT-8286): 96 | * Should it be another parameter or an overload with a different name (like `toIntBase(16)`)? 97 | - it will be an additional parameter `radix` 98 | * Should it be a single overload with an optional parameter or two overloads? 99 | - given that a method with default arguments gets compiled to two methods in bytecode, two overloads with no default arguments 100 | will result in the same number of methods, but providing more convenient way to call them from Java. 101 | 102 | ## Future advancements 103 | 104 | * Provide string-to-number conversions in the specified base. 105 | * Provide the same API in Kotlin JS Standard Library. 106 | 107 | -------------------------------------------------------------------------------- /proposals/stdlib/window-sliding.md: -------------------------------------------------------------------------------- 1 | # Sliding window extensions for collections 2 | 3 | * **Type**: Standard Library API proposal 4 | * **Authors**: Sergey Mashkov, Ilya Gorbunov 5 | * **Status**: Implemented in Kotlin 1.2 6 | * **Prototype**: Implemented 7 | * **Related issues**: [KT-10021](https://youtrack.jetbrains.com/issue/KT-10021), [KT-9151](https://youtrack.jetbrains.com/issue/KT-9151), [KT-11026](https://youtrack.jetbrains.com/issue/KT-11026) 8 | * **Discussion**: [KEEP-11](https://github.com/Kotlin/KEEP/issues/11) 9 | 10 | ## Summary 11 | 12 | Introduce extension functions to support: 13 | - partitioning collections into blocks of the given size, 14 | - taking a window of the given size and moving it along the collection with the given step. 15 | 16 | ## Similar API review 17 | 18 | - Ruby: [each_slice](http://ruby-doc.org/core-2.2.3/Enumerable.html#method-i-each_slice) (partitioning) 19 | - Scala: [sliding](http://www.scala-lang.org/api/2.11.8/index.html#scala.collection.IterableLike@sliding%28size:Int,step:Int%29:Iterator[Repr]) (partitioning, windowing) 20 | - RxJava: [buffer](http://reactivex.io/documentation/operators/buffer.html) (partitioning) 21 | - F#: [windowed](https://msdn.microsoft.com/visualfsharpdocs/conceptual/seq.windowed['t]-function-[fsharp]) (windowing) 22 | - Clojure: [partition](https://clojuredocs.org/clojure.core/partition) (partitioning, windowing) 23 | - Klutter: [batch and lazyBatch](https://github.com/kohesive/klutter/blob/master/core/src/main/kotlin/uy/klutter/core/collections/CollectionsBatching.kt) (partitioning) 24 | - StreamEx: [StreamEx.ofSubLists](https://github.com/amaembo/streamex/blob/f5bd4c3ba79aa0de87ea834e87ac1040a67fa5d8/src/main/java/one/util/streamex/StreamEx.java#L2677) (partitioning, windowing) 25 | - ProtonPack: [windowed](https://github.com/poetix/protonpack/blob/master/src/main/java/com/codepoetics/protonpack/StreamUtils.java#L210) (partitioning, windowing) 26 | 27 | ## Description 28 | 29 | ```kotlin 30 | 31 | // the operation that partitions source into blocks of the given size 32 | 33 | fun Iterable.chunked(size: Int): List> 34 | fun Sequence.chunked(size: Int): Sequence> 35 | fun CharSequence.chunked(size: Int): List 36 | fun CharSequence.chunkedSequence(size: Int): Sequence 37 | 38 | // the operation that partitions source into blocks of the given size 39 | // and applies the immediate transform on an each block 40 | 41 | fun Iterable.chunked(size: Int, transform: (List) -> R): List 42 | fun Sequence.chunked(size: Int, transform: (List) -> R): Sequence 43 | fun CharSequence.chunked(size: Int, transform: (CharSequence) -> R): List 44 | fun CharSequence.chunkedSequence(size: Int, transform: (CharSequence) -> R): Sequence 45 | 46 | // the operation that takes a window of the given size and moves it along with the given step 47 | 48 | fun Iterable.windowed(size: Int, step: Int): List> 49 | fun Sequence.windowed(size: Int, step: Int): Sequence> 50 | fun CharSequence.windowed(size: Int, step: Int): List 51 | fun CharSequence.windowedSequence(size: Int, step: Int): Sequence 52 | 53 | // the operation that takes a window of the given size and moves it along with the given step 54 | // and applies the immediate transform on an each window 55 | 56 | fun Iterable.windowed(size: Int, step: Int, transform: (List) -> R): List 57 | fun Sequence.windowed(size: Int, step: Int, transform: (List) -> R): Sequence 58 | fun CharSequence.windowed(size: Int, step: Int, transform: (CharSequence) -> R): List 59 | fun CharSequence.windowedSequence(size: Int, step: Int, transform: (CharSequence) -> R): Sequence 60 | 61 | // pairwise operation that is a special case of window of size 2 and step 1 62 | fun Iterable.pairwise(): List> 63 | fun Sequence.pairwise(): Sequence> 64 | fun CharSequence.pairwise(): List> 65 | 66 | // pairwise operation which applies the immediate transform on an each pair 67 | 68 | fun Iterable.pairwise(transform: (a: T, b: T) -> R): List 69 | fun Sequence.pairwise(transform: (a: T, b: T) -> R): Sequence 70 | fun CharSequence.pairwise(transform: (a: Char, b: Char) -> R): List 71 | ``` 72 | 73 | ## Use cases 74 | 75 | - buffering/batching 76 | 77 | ```kotlin 78 | fun MessageService.sendAll(messages: List) { 79 | for (batch in messages.asSequence().chunked(this.batchSize)) { 80 | this.sendBatch(batch) 81 | } 82 | // or equivalent (though smelling like `.map { side_effect }` operation) 83 | messages.chunked(this.batchSize) { this.sendBatch(it) } 84 | } 85 | ``` 86 | 87 | - computing moving average (this may require dropping partial windows) 88 | 89 | ```kotlin 90 | val averaged = values.windowed(size = 5, step = 1) { it.average() } 91 | ``` 92 | 93 | - sequence sampling 94 | 95 | ```kotlin 96 | // downsample by factor 2 97 | val sampled = values.windowed(size = 1, step = 2) { it.single() } 98 | 99 | // take every tenth value and average it with two neighbours 100 | val decimated = values.windowed(size = 3, step = 10) { it.average() } 101 | ``` 102 | 103 | - find some product of each two adjacent elements 104 | 105 | ```kotlin 106 | fun List.deltas(): List = pairwise { a, b -> b - a } 107 | ``` 108 | 109 | ## Alternatives 110 | 111 | - use imperative loops 112 | * cons: the implementation is not so simple so it may cause a lot of very similar but slightly different implementations. 113 | 114 | - keep it in a separate library 115 | * cons: too small functionality for a separate library, nobody would like to add it just because of one function 116 | 117 | ## Dependencies 118 | 119 | A subset of Kotlin Standard Library available on all supported platforms. 120 | 121 | ## Placement 122 | 123 | - module: `kotlin-stdlib` 124 | - package: `kotlin.collections` 125 | 126 | ## Reference implementation 127 | 128 | ### Receiver types 129 | 130 | The operations are provided for `Iterables`, `Sequences` and `CharSequences` only. 131 | 132 | If there's a need to invoke operation on an array, the array can be wrapped either to sequence or to list and then 133 | the operation is invoked on that wrapped sequence or list. 134 | 135 | ### Return type 136 | 137 | - the operation is lazy for sequence and thus returns a sequence of lists/pairs 138 | - the operation is eager for iterables and returns a list or lists/pairs 139 | - the return type can be changed by wrapping the receiver with `asSequence()`/`asIterable()` functions 140 | - for `CharSequence` both eager/lazy variants are provided, similar to `split`/`splitToSequence` 141 | 142 | ### Applying immediate transform 143 | 144 | The `transform` function parameter passed to `windowed` or `chunked` is applied to each window or chunk immediately. 145 | The `List`/`CharSequence` passed to `transform` is ephemeral, i.e. it's only valid inside that function and 146 | should not escape from it. This allows not to materialize the window as a snapshot and provide a view instead under some 147 | circumstances. 148 | 149 | On the other hand the `List/Sequence` of `List/String` returned from the overloads without `transform` are fully 150 | materialized, i.e. each `List/String` is a copy of some subsequence of the original sequence. 151 | 152 | ### Implementation 153 | 154 | See reference implementation in branch [stdlib/window](https://github.com/JetBrains/kotlin/compare/stdlib/window~9...stdlib/window) 155 | 156 | ## Unresolved questions 157 | 158 | 1. what to do with the last window having less than the specified `size` elements: 159 | - keep partial window(s) — required for cases like batch processing 160 | - drop partial window(s) — for cases like moving average 161 | * Trailing batches could be filtered out with `filterNot { it.size < windowSize }` operation. 162 | - pad partial windows to required size 163 | * Could be achieved with `map { it.padEnd(size, paddingElement) }`, but it requires 164 | introducing `padStart`/`padEnd` for collections. 165 | - fail when last window(s) is partial 166 | - **resolution**: 167 | - default to keeping partial windows, other modes could be achieved on top of that. 168 | - evaluate usage feedback from eap releases. 169 | 2. Whether or not to allow window size of 0 170 | * neither of analogs allows 171 | * (con) strange corner case — makes not much of sense to obtain a series of zero-sized windows. 172 | * (pro) valid operation and it's possible to implement. 173 | * (pro) reduce possible crash cases as crash generally is more dangerous 174 | - **resolution**: do not allow 175 | 3. should we provide `windowedBackward` for indexed collections? What are the use cases? 176 | - **resolution**: do not provide until use cases are clear in demand 177 | 4. should we provide `windowIndices` and `windowBackwardIndices` as well? 178 | - **resolution**: do not provide until use cases are clear in demand 179 | 5. do we need `slidingWindow2` and `slidingWindow3` as described in [KT-10021](https://youtrack.jetbrains.com/issue/KT-10021) with the following signatures: 180 | * `slidingWindow2([step], operation: (T, T) -> Unit)` 181 | * `slidingWindow3([step], operation: (T, T, T) -> Unit)` 182 | - **resolution**: introduce `slidingWindow2` in form of `pairwise((T, T) -> R)` and `pairwise(): Sequence>` 183 | 6. should operations taking a function be inlined? 184 | - for `windowed` and `chunked` this results in a big amount of bytecode inlined and prevents runtime specializations based on the type of the receiver 185 | - for `pairwise` this seems feasible 186 | 187 | 188 | ## Future advancements 189 | 190 | - function that provides floating window size and step so it could look like `windowSliding(initialSeekPredicate, takeWhilePredicate, dropPredicate)` but it is definitely too generic and would not be so frequently used. 191 | 192 | 193 | -------------------------------------------------------------------------------- /proposals/underscore-for-unused-parameters.md: -------------------------------------------------------------------------------- 1 | # Underscore for unused parameters 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Denis Zharkov 5 | * **Contributors**: Andrey Breslav, Roman Elizarov, Stanislav Erokhin 6 | * **Status**: Implemented 7 | * **Discussion**: [KEEP-55](https://github.com/Kotlin/KEEP/issues/55) 8 | 9 | ## Goals 10 | 11 | In some cases we are bound to declare all the parameters in function, lambda or 12 | destructuring declaration while some of them remain unused. 13 | 14 | We need a way to omit names for such parameters. 15 | 16 | ## Use cases 17 | 18 | For example we have an observable delegated property: 19 | ``` kotlin 20 | var name: String by Delegates.observable("no name") { 21 | kProperty, oldValue, newValue -> println("$oldValue") 22 | } 23 | ``` 24 | 25 | Sometimes we just don't need the exact instance of `KProperty` or even the `oldValue`, 26 | but we have to declare them because it's required by the given function's signature. 27 | 28 | Another example is destructuring declarations - occasionally we want to skip some 29 | of the components: 30 | ``` kotlin 31 | val (w, x, y, z) = listOf(1, 2, 3, 4) 32 | print(x + z) // 'w' and 'y' remain unused 33 | ``` 34 | 35 | ## Solution 36 | 37 | The idea is to allow using one underscore character (`_`) as a name for parameter 38 | of a lambda or an expression function or as a name of destructuring entry. 39 | 40 | It must be allowed to declare such parameters many times in the same place. 41 | 42 | Examples: 43 | ``` kotlin 44 | var name: String by Delegates.observable("no name") { 45 | _, oldValue, _ -> println("$oldValue") 46 | } 47 | 48 | val (_, x, _, z) = listOf(1, 2, 3, 4) 49 | print(x + z) 50 | 51 | val functionExpression: (Int) -> Int = fun(_: Int) = 1 52 | ``` 53 | 54 | Note that it's still possible to declare type of these variables: 55 | ``` kotlin 56 | var name: String by Delegates.observable("no name") { 57 | _: KProperty<*>, oldValue, _ -> println("$oldValue") 58 | } 59 | ``` 60 | These types must be treated as usual 61 | 62 | ## Scopes 63 | 64 | Of course such declarations don't affects the declaring scope, e.g. they can't 65 | be referenced in the next statements. 66 | 67 | Note, that in Kotlin 1.0 it is forbidden to have a declaration with identifier token containing 68 | only underscores (`_`), at the same time one can declare underscore-name using backticks: 69 | ``` kotlin 70 | fun foo(`_`: Int) = _ // _ after the equal sign is resolved to the first parameter of 'foo' 71 | ``` 72 | 73 | ## 'componentX' calls 74 | 75 | In case of destructuring declarations skipped components must be checked as usual, 76 | but relevant `componentX` calls mustn't be present in run-time. 77 | 78 | ## "Unused declaration" warning 79 | For all declarations that may have their name skipped (replaced by `_`) warning 80 | should be reported if they are effectively unused. 81 | 82 | Also an IDE quickfix is expected that turns unused declaration's name to `_`. 83 | 84 | ## Other languages 85 | - In Haskell underscore character has meaning of a wildcard in [pattern matching](https://en.wikibooks.org/wiki/Haskell/Pattern_matching) 86 | - For C# `_` in lambdas there is just an [idiom](https://charlieflowers.wordpress.com/2009/04/02/nice-c-idiom-for-parameterless-lambdas/) 87 | without special treatment in the language 88 | - It seems that the same semantic may be applied in future versions of Java. 89 | See [related message](http://mail.openjdk.java.net/pipermail/lambda-dev/2013-July/010670.html). 90 | 91 | ## To be done in the future 92 | The same trick could also be applied to the named functions. 93 | 94 | One of the points why unused parameters are necessary for non-overrides is 95 | that they may be useful for method references: 96 | ``` kotlin 97 | var name: String by Delegates.observable("no name", ::onChange) 98 | 99 | fun onChange(x: KProperty<*>, oldValue: String, newValue: String) { 100 | println(oldValue) 101 | } 102 | ``` 103 | 104 | Of course it could be useful for overrides that are obligated to declare 105 | parameters because they're present in the overridden declaration, while 106 | they may be completely irrelevant to the specific implementation. 107 | 108 | But there are some questions to answer about what names must be used for such 109 | anonymous parameters both in Kotlin call sites and in JVM signature. 110 | 111 | ## References 112 | * [KT-3824](https://youtrack.jetbrains.com/issue/KT-3824) Underscore in lambda for unused parameters 113 | * [KT-2783](https://youtrack.jetbrains.com/issue/KT-2783) Allow to skip some components in a multi-declaration 114 | -------------------------------------------------------------------------------- /proposals/underscores-in-numeric-literals.md: -------------------------------------------------------------------------------- 1 | # Underscores in Numeric Literals 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Kirill Rakhman 5 | * **Status**: Implemented 6 | * **Discussion**: https://github.com/Kotlin/KEEP/issues/59 7 | 8 | ## Synopsis 9 | 10 | Support underscores in numeric literals for separating digit groups. 11 | 12 | ## Motivation 13 | 14 | Numeric values with a high number of digits are hard to read which is why it is proposed to support underscores as digit group separators. This results in easier to read code and doesn't affect the semantics at all. 15 | 16 | ### Comparison 17 | 18 | Current: 19 | ```kotlin 20 | val ONE_MILLION = 1000000 21 | ``` 22 | 23 | With underscores: 24 | ```kotlin 25 | val ONE_MILLION = 1_000_000 26 | ``` 27 | 28 | Other examples: 29 | ```kotlin 30 | val creditCardNumber = 1234_5678_9012_3456L 31 | val socialSecurityNumber = 999_99_9999L 32 | val pi = 3.14_15F 33 | val hexBytes = 0xFF_EC_DE_5E 34 | val hexWords = 0xCAFE_BABE 35 | val maxval = 0x7fff_ffff_ffff_ffffL 36 | val bytes = 0b11010010_01101001_10010100_10010010; 37 | ``` 38 | 39 | ## Implementation 40 | 41 | The grammar needs to be adapted to allow underscores in `Int`, `Long`, `Float` and `Double` literals. The following rules are copied from the [Java implementation](http://docs.oracle.com/javase/7/docs/technotes/guides/language/underscores-literals.html) since Java 7: 42 | 43 | You can place underscores only between digits; you cannot place underscores in the following places: 44 | 45 | - At the beginning or end of a number 46 | - Adjacent to a decimal point in a floating point literal 47 | - Prior to an F or L suffix 48 | - In positions where a string of digits is expected 49 | 50 | Examples: 51 | 52 | ```kotlin 53 | val pi1 = 3_.1415F // Invalid cannot put underscores adjacent to a decimal poval 54 | val pi2 = 3._1415F // Invalid cannot put underscores adjacent to a decimal poval 55 | val socialSecurityNumber1 56 | = 999_99_9999_L // Invalid cannot put underscores prior to an L suffix 57 | 58 | val x1 = _52 // This is an identifier, not a numeric literal 59 | val x2 = 5_2 // OK (decimal literal) 60 | val x3 = 52_ // Invalid cannot put underscores at the end of a literal 61 | val x4 = 5_______2 // OK (decimal literal) 62 | 63 | val x5 = 0_x52 // Invalid cannot put underscores in the 0x radix prefix 64 | val x6 = 0x_52 // Invalid cannot put underscores at the beginning of a number 65 | val x7 = 0x5_2 // OK (hexadecimal literal) 66 | val x8 = 0x52_ // Invalid cannot put underscores at the end of a number 67 | ``` 68 | 69 | ## Comparison to other languages 70 | 71 | - Java is the role model for this proposal. 72 | - For C#, [a proposal](https://github.com/dotnet/roslyn/issues/216) exists and according to the [C# 7 Work List of Features](https://github.com/dotnet/roslyn/issues/2136) it is in the group "Some Interest" 73 | - [Ruby](https://docs.ruby-lang.org/en/2.0.0/syntax/literals_rdoc.html) has the same(?) rules as Java 74 | - For Python, [PEP 515](https://www.python.org/dev/peps/pep-0515/) exists and is accepted. Unlike Java, underscores are allowed to the right of the `b` in a binary literal. 75 | 76 | ## References 77 | 78 | [KT-2964](https://youtrack.jetbrains.com/issue/KT-2964) Underscores in integer literals 79 | -------------------------------------------------------------------------------- /proposals/val-in-when-subject.md: -------------------------------------------------------------------------------- 1 | # Local variable declaration in 'when' subject 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Dmitry Petrov 5 | * **Contributors**: 6 | * **Status**: 7 | * **Prototype**: In progress 8 | 9 | Discussion: https://github.com/Kotlin/KEEP/issues/120 10 | 11 | ## Problem description 12 | 13 | Local variable declaration in `when` subject allows to introduce a meaningful name with a proper scope 14 | that can be used to reference the subject value. 15 | Also, it enables smart casts on the subject value in corresponding branches. 16 | 17 | In Kotlin 1.2, explicit value declaration for when subject would be: 18 | ```kotlin 19 | val x = foo() 20 | when (x) { 21 | is String -> println(x.length) 22 | is Number -> println(x.toInt()) 23 | } 24 | ``` 25 | This, however, places `x` in the outer scope, and makes code somewhat harder to follow 26 | (is `x` used just in `when`, or also somewhere else in the following code?). 27 | 28 | ## Design details 29 | 30 | The following restrictions apply to local variable declarations in `when` subject: 31 | * Must be a `val` 32 | * Must have an initializer 33 | * Custom accessors are not allowed 34 | * Local delegated properties are not allowed 35 | 36 | Examples: 37 | ```kotlin 38 | when (val x = foo()) { ... } // Ok 39 | 40 | when (val x: Number = foo()) { ... } // Ok, explicit type annotation can be provided 41 | 42 | when (var x = foo()) { ... } // Error, must be a val 43 | 44 | when (val x: Number) { ... } // Error, must have an initializer 45 | 46 | when (val x get() = ...) { ... } // Error, custom accessors are not allowed 47 | // (this code actually doesn't parse in Kotlin 1.2, which is ok) 48 | 49 | when (val x by bar()) { ... } // Error, local delegated properties are not allowed in 'when' subject 50 | ``` 51 | 52 | ## Open questions 53 | 54 | ### No 'val' keyword 55 | 56 | Option suggested during discussions: omit `val` keyword for subject variable in `when` (which would be similar to loop 57 | variable in `for`): 58 | ```kotlin 59 | when (x = foo()) { ... } 60 | when (y: String = bar()) { ... } 61 | ``` 62 | 63 | This might make code somewhat harder to read, because `x = foo()` in the example above looks like an expression (or even 64 | like an assignment statement). 65 | 66 | ### Explicit type annotations & exhaustiveness 67 | 68 | Consider the following code: 69 | ```kotlin 70 | enum class Color { RED, GREEN, BLUE } 71 | 72 | fun foo(x: Color) = 73 | when (val xx: Color? = x) { 74 | Color.RED -> "Roses are red" 75 | Color.GREEN -> "Grass is green" 76 | Color.BLUE -> "Violets are blue" 77 | } 78 | ``` 79 | 80 | Equivalent code without subject variable is currently considered ill-formed, even though we statically know that `xx` is 81 | not null: 82 | ```kotlin 83 | fun foo(x: Color): String { 84 | val xx: Color? = x 85 | return when (xx) { // Error: 'when' expression must be exhaustive 86 | Color.RED -> "Roses are red" 87 | Color.GREEN -> "Grass is green" 88 | Color.BLUE -> "Violets are blue" 89 | } 90 | } 91 | ``` 92 | 93 | So, in the current prototype, for the sake of consistency, the code in example above is also considered ill-formed. 94 | 95 | 96 | ### Destructuring declarations 97 | 98 | It's possible to introduce destructuring declarations in `when` subject as follows: 99 | 100 | Destructuring declaration in `when` subject is well-formed if the aforementioned restrictions are satisfied, 101 | and it's well-formed as a standalone destructuring declaration. E.g.: 102 | ```kotlin 103 | val p: Pair = ... 104 | 105 | when (val (p1, p2) = p) { ... } // Ok 106 | 107 | when (val (p1, p2, p3) = p) { ... } // Error: missing 'component3()' function 108 | } 109 | ``` 110 | 111 | Here `when (val (p1, p2) = p) { ... }` is equivalent to (modulo scopes): 112 | ```kotlin 113 | val (p1, p2) = p 114 | when (p) { ... } 115 | ``` 116 | 117 | However, this code looks somewhat confusing, and this feature can be introduced later without breaking changes, if it's 118 | considered valuable. 119 | 120 | Note that smart casts on destructured value currently have no effect on the corresponding components. E.g.: 121 | ```kotlin 122 | interface NumPair { 123 | operator fun component1(): Number 124 | operator fun component2(): Number 125 | } 126 | 127 | interface IntPair : NumPair { 128 | override fun component1(): Int 129 | override fun component2(): Int 130 | } 131 | 132 | fun useInt(x: Int) {} 133 | 134 | fun test(p: NumPair) { 135 | val (p1, _) = p 136 | if (p is IntPair) { 137 | useInt(p1) // Error 138 | } 139 | } 140 | ``` 141 | 142 | --------------------------------------------------------------------------------