From 867de35f22b9b69c3bda5f0c6681fa46831231a3 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Tue, 9 Aug 2016 22:00:43 +0300 Subject: [PATCH 1/7] Update LHS resolution algorithm with a special case for objects (#5) Reword the whole algorithm, to (hopefully) improve readability --- proposals/bound-callable-references.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/proposals/bound-callable-references.md b/proposals/bound-callable-references.md index adfdff0d3..861f1edd2 100644 --- a/proposals/bound-callable-references.md +++ b/proposals/bound-callable-references.md @@ -72,8 +72,8 @@ The LHS may now be interpreted as an expression, or a type, or both. SimpleName::foo // expression (variable SimpleName) or type (class SimpleName) Qualified.Name::foo // expression or type - Nullable?::foo // type (see section Nullable references below) - Generic::foo // type + Nullable?::foo // type (see section "Nullable references" below) + Generic::foo // type (see section "Calls to generic properties" below) Generic()::foo // expression this::foo // expression @@ -82,13 +82,17 @@ The LHS may now be interpreted as an expression, or a type, or both. (ParenNullable)?::foo // type ``` -The semantics are different when the LHS is interpreted as an expression or a type, so we establish a priority of one over another when both interpretations are applicable. -The algorithm is the following: +The semantics are different when the LHS is interpreted as an expression or a type, so we introduce the following algorithm to choose the resulting interpretation when both are applicable: -1. Try interpreting the LHS as an **expression** with the usual resolution algorithm for qualified expressions. - If the result represents a companion object of some class, continue to p.2. - Otherwise continue resolution of the member in the scope of the expression's type. -2. Resolve the unbound reference with the existing algorithm. +1. Type-check the LHS as an **expression**. + - If the result represents a _companion object of some class_ specified with the short syntax (via the short/qualified name of the containing class), discard the result and continue to p.2. + - If the result represents an _object_ (either non-companion, or companion specified with the full syntax: `org.foo.Bar.Companion` or just `Bar.Companion`), remember the result and continue to p.2. + - Otherwise the resolution of the LHS is complete and the result is the type-checked expression. Note that the resolution of the LHS is finished at this point even if errors were reported. +2. Resolve the LHS as a **type**. + - If the resulting type refers to the same object that was obtained in the first step, complete the resolution with the result obtained in the first step. In other words, the result is the object type-checked as expression, and the object instance will be bound to the reference. + - Otherwise the result is the resolved type and the reference is unbound. + +> If there were no `object`s or `companion object`s in Kotlin, the algorithm would be very simple: first try resolving as expression, then as type. However, this would not be backwards compatible for a very common case: in an expression like `Obj::foo`, without any changes to the source code an object `Obj` might now win the resolution where previously (in Kotlin 1.0) a completely different class `Obj` had been winning. This is possible when you have a class named `Obj` and an object `Obj` in another package, coming from a star import. Examples: ``` From add2b2d3e71e0e9c2eb95546674d80c5f07658a3 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Tue, 9 Aug 2016 22:08:02 +0300 Subject: [PATCH 2/7] Minor clarifications about function type and function/property references (#5) --- proposals/bound-callable-references.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proposals/bound-callable-references.md b/proposals/bound-callable-references.md index 861f1edd2..93a3b67aa 100644 --- a/proposals/bound-callable-references.md +++ b/proposals/bound-callable-references.md @@ -33,6 +33,10 @@ fun sortedByComparator(strings: List, comparator: Comparator) = strings.sortedBy(comparator::compare) ``` +The function type of a bound reference differs from the type of the corresponding *unbound* reference in that it has arity lower by 1, and doesn't have the first type argument (the type of the instance or extension receiver parameter). + +Both function references and property references can be bound. + Bound class literal is an expression which is evaluated to the runtime representation of the class of an object, similarly provided as an expression before `::`. Its semantics are similar to Java's `java.lang.Object#getClass`, except that its type is `kotlin.reflect.KClass<...>`, not `java.lang.Class<...>`. Example: ``` val x: Widget = ... From ce3fd7f8219fa8f922dcd278516f29facf9e2c7d Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 10 Aug 2016 16:07:10 +0300 Subject: [PATCH 3/7] Clarify class literals for primitive types (#5) --- proposals/bound-callable-references.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proposals/bound-callable-references.md b/proposals/bound-callable-references.md index 93a3b67aa..0f7b1327d 100644 --- a/proposals/bound-callable-references.md +++ b/proposals/bound-callable-references.md @@ -220,7 +220,8 @@ class A { } ``` -Generated bytecode for `::class` should be similar to `.javaClass.kotlin`. +Generated bytecode for `::class` should be similar to `.javaClass.kotlin`. Note that for expressions of primitive types, the corresponding `KClass` instance at runtime should be backed by the primitive `Class` object, not the wrapper class. For example, `42::class.java` should return the `int` class, not `java.lang.Integer`. + An intrinsic for `::class.java`, meaning `.javaClass`, would be useful. ### Reflection From fc82924c3e51a2271978d0c4a85d34aec421ef84 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 10 Aug 2016 18:39:51 +0300 Subject: [PATCH 4/7] Substitution of arguments with stars in class literals (#5) --- proposals/bound-callable-references.md | 61 +++++++++++++++++++++----- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/proposals/bound-callable-references.md b/proposals/bound-callable-references.md index 0f7b1327d..e018604fe 100644 --- a/proposals/bound-callable-references.md +++ b/proposals/bound-callable-references.md @@ -69,9 +69,9 @@ Double colon expressions are postfix expressions, so `::`'s priority is maximal Note that now any expression can be followed by question marks (`?`) and then `::`. So, if we see `?` after an expression, we must perform a lookahead until the first non-`?`, and if it's `::`, parse this as a double colon expression. -### Resolution +### Resolution: left-hand side of callable reference -The LHS may now be interpreted as an expression, or a type, or both. +The LHS of a callable reference expression may now be interpreted as an expression, or a type, or both. ``` SimpleName::foo // expression (variable SimpleName) or type (class SimpleName) Qualified.Name::foo // expression or type @@ -131,20 +131,61 @@ fun test() { } ``` -Resolution of a LHS of a class literal expression is performed exactly the same, -so that `C::class` means the class of C and `(C)::class` means the class of C.Companion. - -It is an error if the LHS expression has nullable type and the resolved member is not an extension to nullable type. -It is an error if the LHS of a bound class literal has nullable type: +It is an error if the LHS expression has nullable type and the resolved member is not an extension to nullable type: ``` class C { fun foo() {} } +fun C?.ext() {} + fun test(c: C?) { - c::foo // error - c::class // error - null::class // error + c::foo // error + c::ext // ok +} +``` + +### Resolution: class literal + +Resolution of the LHS of a class literal expression is performed with the same algorithm. + +``` +class C { + companion object +} + +fun test() { + C::class // class of C + C()::class // class of C + (C)::foo // class of C.Companion + C.Companion::class // class of C.Companion +} +``` + +Once the LHS is type-checked and its type is determined to be `T`, the type of the whole class literal expression is `kotlin.reflect.KClass` where `T'` is a type obtained by _substituting `T`'s arguments with star projections (`*`)_. Example: +``` +fun test() { + "a"::class // KClass + listOf("a")::class // KClass> (not "KClass>"!) + mapOf("a" to 42)::class // KClass> +} +``` + +> The reason for substitution with star projections is erasure: type arguments are not reified at runtime in Kotlin, so using class literals with seemingly full type information could result in exceptions at runtime. For example, consider the following code, where if we don't substitute arguments with `*`, an exception is thrown: +> ``` +> fun test(): String { +> val kClass: KClass> = listOf("")::class +> val strings: List = kClass.cast(listOf(42)) +> return strings[0] // ClassCastException! +> } +> ``` +> If we do the substitution as proposed above, the type of `kClass` is `KClass>` and you must perform a cast to make the code compile. + +It is an error if the LHS of a bound class literal has nullable type: +``` +fun test(s: String?) { + s::class // error + null::class // error } ``` From 80e9cd389bc0d6ca072f84f4fded3f78ab276db5 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 10 Aug 2016 19:09:31 +0300 Subject: [PATCH 5/7] Fix typo (#5) --- proposals/bound-callable-references.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/bound-callable-references.md b/proposals/bound-callable-references.md index e018604fe..9391e4ce8 100644 --- a/proposals/bound-callable-references.md +++ b/proposals/bound-callable-references.md @@ -157,7 +157,7 @@ class C { fun test() { C::class // class of C C()::class // class of C - (C)::foo // class of C.Companion + (C)::class // class of C.Companion C.Companion::class // class of C.Companion } ``` From 0cbc08ef8e18cc59b194f15a9a89e9cef9659e10 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 12 Aug 2016 16:40:47 +0300 Subject: [PATCH 6/7] Do not erase arguments of Array expressions in class literals, add examples (#5) --- proposals/bound-callable-references.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/proposals/bound-callable-references.md b/proposals/bound-callable-references.md index 9391e4ce8..5939febbc 100644 --- a/proposals/bound-callable-references.md +++ b/proposals/bound-callable-references.md @@ -162,12 +162,24 @@ fun test() { } ``` -Once the LHS is type-checked and its type is determined to be `T`, the type of the whole class literal expression is `kotlin.reflect.KClass` where `T'` is a type obtained by _substituting `T`'s arguments with star projections (`*`)_. Example: +Once the LHS is type-checked and its type is determined to be `T`, the type of the whole class literal expression is `KClass`, where `erase(T)` represents the most accurate possible type-safe representation of the type `T` and is obtained by substituting `T`'s arguments with star projections (`*`), except when the type is an array. + +More formally, let `T` = `C`, where `C` is a class and `N`, the number of type arguments, might be zero. `erase(T)` is then defined as follows: +- if `C` = `kotlin.Array`: + - if `A1` is a projection of some type (covariant, contravariant, or star), `erase(T)` = `kotlin.Array<*>` + - otherwise `erase(T)` = `kotlin.Array` +- otherwise `erase(T)` = `C<*, ..., *>` + +Examples: ``` fun test() { - "a"::class // KClass - listOf("a")::class // KClass> (not "KClass>"!) - mapOf("a" to 42)::class // KClass> + "a"::class // KClass + listOf("a")::class // KClass> (not "KClass>"!) + listOf(listOf("a"))::class // KClass> (not "KClass>>"!) + mapOf("a" to 42)::class // KClass> + arrayOf("a")::class // KClass> + arrayOf(arrayOf("a"))::class // KClass>> + arrayOf(listOf("a"))::class // KClass>> } ``` From 4587f891f642956c5562cf02d1bb815d7941befa Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Mon, 15 Aug 2016 18:33:44 +0300 Subject: [PATCH 7/7] Add TODO about Array type arguments in JS (#5) --- proposals/bound-callable-references.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proposals/bound-callable-references.md b/proposals/bound-callable-references.md index 5939febbc..7c7e97d54 100644 --- a/proposals/bound-callable-references.md +++ b/proposals/bound-callable-references.md @@ -193,6 +193,8 @@ fun test() { > ``` > If we do the substitution as proposed above, the type of `kClass` is `KClass>` and you must perform a cast to make the code compile. +TODO: in JS, there seems to be no way to determine type arguments of arrays at runtime + It is an error if the LHS of a bound class literal has nullable type: ``` fun test(s: String?) {