Kotlin Language Specification - Kotlin-Spec
Kotlin Language Specification - Kotlin-Spec
Version 1.9-rfc+0.1
I Kotlin/Core 1
Introduction 3
Compatibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Experimental features . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2 Type system 43
Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
2.1 Type kinds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.1.1 Built-in types . . . . . . . . . . . . . . . . . . . . . . . . . 46
kotlin.Any . . . . . . . . . . . . . . . . . . . . . . . . . . 46
kotlin.Nothing . . . . . . . . . . . . . . . . . . . . . . . 46
kotlin.Function . . . . . . . . . . . . . . . . . . . . . . 46
Built-in integer types . . . . . . . . . . . . . . . . . . . . . 46
Array types . . . . . . . . . . . . . . . . . . . . . . . . . . 46
2.1.2 Classier types . . . . . . . . . . . . . . . . . . . . . . . . 47
Simple classier types . . . . . . . . . . . . . . . . . . . . 48
Parameterized classier types . . . . . . . . . . . . . . . . 48
iii
iv CONTENTS
4 Declarations 89
Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.1 Classier declaration . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.1.1 Class declaration . . . . . . . . . . . . . . . . . . . . . . . 90
Constructor declaration . . . . . . . . . . . . . . . . . . . 92
Constructor declaration scopes . . . . . . . . . . . 94
Nested and inner classiers . . . . . . . . . . . . . . . . . 94
Inheritance delegation . . . . . . . . . . . . . . . . . . . . 96
Abstract classes . . . . . . . . . . . . . . . . . . . . . . . . 98
4.1.2 Data class declaration . . . . . . . . . . . . . . . . . . . . 98
Data object declaration . . . . . . . . . . . . . . . . . . . 101
4.1.3 Enum class declaration . . . . . . . . . . . . . . . . . . . 102
4.1.4 Annotation class declaration . . . . . . . . . . . . . . . . 104
4.1.5 Value class declaration . . . . . . . . . . . . . . . . . . . . 106
4.1.6 Interface declaration . . . . . . . . . . . . . . . . . . . . . 106
Functional interface declaration . . . . . . . . . . . . . . . 107
4.1.7 Object declaration . . . . . . . . . . . . . . . . . . . . . . 109
4.1.8 Local class declaration . . . . . . . . . . . . . . . . . . . . 109
4.1.9 Classier initialization . . . . . . . . . . . . . . . . . . . . 110
4.1.10 Classier declaration scopes . . . . . . . . . . . . . . . . . 112
4.2 Function declaration . . . . . . . . . . . . . . . . . . . . . . . . . 112
4.2.1 Function signature . . . . . . . . . . . . . . . . . . . . . . 114
4.2.2 Named, positional and default parameters . . . . . . . . . 114
4.2.3 Variable length parameters . . . . . . . . . . . . . . . . . 116
4.2.4 Extension function declaration . . . . . . . . . . . . . . . 116
4.2.5 Inlining . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
4.2.6 Inx functions . . . . . . . . . . . . . . . . . . . . . . . . 119
4.2.7 Local function declaration . . . . . . . . . . . . . . . . . . 119
4.2.8 Tail recursion optimization . . . . . . . . . . . . . . . . . 119
4.2.9 Function declaration scopes . . . . . . . . . . . . . . . . . 120
4.3 Property declaration . . . . . . . . . . . . . . . . . . . . . . . . . 121
4.3.1 Read-only property declaration . . . . . . . . . . . . . . . 121
4.3.2 Mutable property declaration . . . . . . . . . . . . . . . . 122
vi CONTENTS
5 Inheritance 137
5.1 Classier type inheritance . . . . . . . . . . . . . . . . . . . . . . 137
5.1.1 Abstract classes . . . . . . . . . . . . . . . . . . . . . . . . 138
5.1.2 Sealed classes and interfaces . . . . . . . . . . . . . . . . . 138
5.1.3 Inheritance from built-in types . . . . . . . . . . . . . . . 138
5.2 Matching and subsumption of declarations . . . . . . . . . . . . . 138
5.3 Inheriting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
5.4 Overriding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
7 Statements 147
7.1 Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
7.1.1 Simple assignments . . . . . . . . . . . . . . . . . . . . . . 148
7.1.2 Operator assignments . . . . . . . . . . . . . . . . . . . . 148
7.1.3 Safe assignments . . . . . . . . . . . . . . . . . . . . . . . 149
7.2 Loop statements . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
7.2.1 While-loop statements . . . . . . . . . . . . . . . . . . . . 150
7.2.2 Do-while-loop statements . . . . . . . . . . . . . . . . . . 151
7.2.3 For-loop statements . . . . . . . . . . . . . . . . . . . . . 151
7.3 Code blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
7.3.1 Coercion to kotlin.Unit . . . . . . . . . . . . . . . . . . 153
8 Expressions 155
Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
8.1 Constant literals . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
8.1.1 Boolean literals . . . . . . . . . . . . . . . . . . . . . . . . 156
CONTENTS vii
16 Exceptions 279
16.1 Catching exceptions . . . . . . . . . . . . . . . . . . . . . . . . . 279
16.2 Throwing exceptions . . . . . . . . . . . . . . . . . . . . . . . . . 280
17 Annotations 281
17.1 Annotation values . . . . . . . . . . . . . . . . . . . . . . . . . . 281
17.2 Annotation retention . . . . . . . . . . . . . . . . . . . . . . . . . 282
17.3 Annotation targets . . . . . . . . . . . . . . . . . . . . . . . . . . 282
17.4 Annotation declarations . . . . . . . . . . . . . . . . . . . . . . . 282
17.5 Built-in annotations . . . . . . . . . . . . . . . . . . . . . . . . . 283
17.5.1 kotlin.annotation.Retention . . . . . . . . . . . . . . 283
17.5.2 kotlin.annotation.Target . . . . . . . . . . . . . . . . 283
17.5.3 kotlin.annotation.Repeatable . . . . . . . . . . . . . . 284
17.5.4 kotlin.RequiresOptIn / kotlin.OptIn . . . . . . . . . 284
17.5.5 kotlin.Deprecated / kotlin.ReplaceWith . . . . . . . 284
17.5.6 kotlin.Suppress . . . . . . . . . . . . . . . . . . . . . . 285
17.5.7 kotlin.SinceKotlin . . . . . . . . . . . . . . . . . . . . 285
17.5.8 kotlin.UnsafeVariance . . . . . . . . . . . . . . . . . . 285
17.5.9 kotlin.DslMarker . . . . . . . . . . . . . . . . . . . . . . 286
17.5.10 kotlin.PublishedApi . . . . . . . . . . . . . . . . . . . . 286
17.5.11 kotlin.BuilderInference . . . . . . . . . . . . . . . . . 286
17.5.12 kotlin.RestrictSuspension . . . . . . . . . . . . . . . . 286
17.5.13 kotlin.OverloadResolutionByLambdaReturnType . . . 286
19 Concurrency 297
xii CONTENTS
Part I
Kotlin/Core
1
Introduction
Kotlin took inspiration from many programming languages, including (but not
limited to) Java, Scala, C# and Groovy. One of the main ideas behind Kotlin
is being pragmatic, i.e., being a programming language useful for day-to-day
development, which helps the users get the job done via its features and its tools.
Thus, a lot of design decisions were and still are inuenced by how benecial
these decisions are for Kotlin users.
Kotlin is a multiplatform, statically typed, general-purpose programming lan-
guage. Currently, as of version 1.9, it supports compilation to the following
platforms.
• JVM (Java Virtual Machine)
• JS (JavaScript)
• Native (native binaries for various architectures)
Furthermore, it supports transparent interoperability between dierent platforms
via its Kotlin Multiplatform Project (Kotlin MPP) feature.
The type system of Kotlin distinguishes at compile time between nullable and
non-nullable types, achieving null-safety, i.e., guaranteeing the absence of runtime
errors caused by the absence of value (i.e., null value). Kotlin also extends
its static type system with elements of gradual and ow typing, for better
interoperability with other languages and ease of development.
Kotlin is an object-oriented language which also has a lot of functional program-
ming elements. From the object-oriented side, it supports nominal subtyping with
bounded parametric polymorphism (akin to generics) and mixed-site variance.
From the functional programming side, it has rst-class support for higher-order
functions and lambda literals.
This specication covers Kotlin/Core, i.e., fundamental parts of Kotlin which
should function mostly the same way irregardless of the underlying platform.
These parts include such important things as language expressions, declarations,
type system and overload resolution.
Important: due to the complexities of platform-specic implemen-
tations, platforms may extend, reduce or change the way some as-
3
4
Compatibility
Kotlin Language Specication is still in progress and has experimental stability
level, meaning no compatibility should be expected between even incremental
releases of the specication, any parts can be added, removed or changed without
warning.
Important: while the specication has experimental stability level,
the Kotlin language itself and its compiler have dierent stability
levels for dierent components, which are described in more detail
here.
Experimental features
In several cases this specication discusses experimental Kotlin features, i.e.,
features which are still in active development and which may be changed in the
future. When so, the specication talks about the current state of said features,
with no guarantees of their future stability (or even existence in the language).
The experimental features are marked as such in the specication to the best of
our abilities.
Acknowledgments
We would like to thank the following people for their invaluable help and feedback
during the writing of this specication.
Note: the format is “First name Last name”, ordered by last name
• Zalim Bashorov
• Andrey Breslav
• Roman Elizarov
• Stanislav Erokhin
• Neal Gafter
• Dmitrii Petrov
• Victor Petukhov
• Vladimir Reshetnikov
• Dmitry Savvinov
• Anastasiia Spaseeva
5
• Mikhail Zarechenskii
• Denis Zharkov
We would also like to thank Pandoc, its authors and community, as this speci-
cation would be much harder to implement without Pandoc’s versatility and
support.
Feedback
If you have any feedback for this document, feel free to create an issue at our
GitHub. In case you prefer to use email, you can use [email protected]
and [email protected].
Reference
If one needs to reference this specication, they may use the following:
Marat Akhin, Mikhail Belyaev et al. “Kotlin language specication:
Kotlin/Core”, JetBrains / JetBrains Research, 2020
6
Chapter 1
1.1 Notation
This section uses a BNF-based notation similar to EBNF with the following
conventions:
• Any sequence of characters given in single-quotes and monospace font
denote a terminal sequence;
• Special terminal sequences that needs specication are given in angle
brackets: <. . . >;
• Normal parentheses are used sparingly to specify priority between other
operations;
• A sequence of rules A and B: (A B);
• Choice between rules A and B: (A | B);
• Optional use of rule A: [A];
• Repetition of rule A: {A}.
Rule names starting with capital letters denote lexical rules, while rule names
starting with lowercase letters denote syntactic rules.
Note: this notation is similar to ISO EBNF as per standard ISO/IEC
14977, but does not employ any special symbols for concatenation
or termination and does not use some of the additional notation
symbols
7
8 CHAPTER 1. SYNTAX AND GRAMMAR
CR:
<unicode character Carriage Return U+000D>
ShebangLine:
'#!' {<any character excluding CR and LF >}
DelimitedComment:
'/*' { DelimitedComment | <any character> } '*/'
LineComment:
'//' {<any character excluding CR and LF >}
WS:
<one of the following characters: SPACE U+0020, TAB U+0009, Form
Feed U+000C>
NL: LF | (CR [LF])
Hidden:
DelimitedComment | LineComment | WS
MOD:
'%'
DIV :
'/'
ADD:
'+'
SUB:
'-'
INCR:
'++'
DECR:
'--'
CONJ :
'&&'
DISJ :
'||'
EXCL_WS:
'!' Hidden
EXCL_NO_WS:
'!'
COLON :
':'
SEMICOLON :
';'
ASSIGNMENT :
'='
ADD_ASSIGNMENT :
'+='
SUB_ASSIGNMENT :
'-='
MULT_ASSIGNMENT :
'*='
DIV_ASSIGNMENT :
'/='
MOD_ASSIGNMENT :
'%='
10 CHAPTER 1. SYNTAX AND GRAMMAR
ARROW :
'->'
DOUBLE_ARROW :
'=>'
RANGE:
'..'
COLONCOLON :
'::'
DOUBLE_SEMICOLON :
';;'
HASH :
'#'
AT_NO_WS:
'@'
AT_POST_WS:
'@' (Hidden | NL)
AT_PRE_WS:
(Hidden | NL) '@'
AT_BOTH_WS:
(Hidden | NL) '@' (Hidden | NL)
QUEST_WS:
'?' Hidden
QUEST_NO_WS:
'?'
LANGLE:
'<'
RANGLE:
'>'
LE: '<='
GE:
'>='
EXCL_EQ:
'!='
EXCL_EQEQ:
'!=='
AS_SAFE:
'as?'
1.2. LEXICAL GRAMMAR 11
EQEQ:
'=='
EQEQEQ:
'==='
SINGLE_QUOTE:
'\''
RETURN_AT :
'return@' Identier
CONTINUE_AT :
'continue@' Identier
BREAK_AT :
'break@' Identier
THIS_AT :
'this@' Identier
SUPER_AT :
'super@' Identier
FILE:
'file'
FIELD:
'field'
PROPERTY :
'property'
GET :
'get'
SET :
'set'
RECEIVER:
'receiver'
PARAM :
'param'
SETPARAM :
'setparam'
DELEGATE:
'delegate'
PACKAGE:
'package'
12 CHAPTER 1. SYNTAX AND GRAMMAR
IMPORT :
'import'
CLASS:
'class'
INTERFACE:
'interface'
FUN :
'fun'
OBJECT :
'object'
VAL:
'val'
VAR:
'var'
TYPE_ALIAS:
'typealias'
CONSTRUCTOR:
'constructor'
BY :
'by'
COMPANION :
'companion'
INIT :
'init'
THIS:
'this'
SUPER:
'super'
TYPEOF:
'typeof'
WHERE:
'where'
IF: 'if'
ELSE:
'else'
WHEN :
'when'
1.2. LEXICAL GRAMMAR 13
TRY :
'try'
CATCH :
'catch'
FINALLY :
'finally'
FOR:
'for'
DO:
'do'
WHILE:
'while'
THROW :
'throw'
RETURN :
'return'
CONTINUE:
'continue'
BREAK :
'break'
AS: 'as'
IS: 'is'
IN : 'in'
NOT_IS:
'!is' (Hidden | NL)
NOT_IN :
'!in' (Hidden | NL)
OUT :
'out'
DYNAMIC :
'dynamic'
PUBLIC :
'public'
PRIVATE:
'private'
14 CHAPTER 1. SYNTAX AND GRAMMAR
PROTECTED:
'protected'
INTERNAL:
'internal'
ENUM :
'enum'
SEALED:
'sealed'
ANNOTATION :
'annotation'
DATA:
'data'
INNER:
'inner'
TAILREC :
'tailrec'
OPERATOR:
'operator'
INLINE:
'inline'
INFIX:
'infix'
EXTERNAL:
'external'
SUSPEND:
'suspend'
OVERRIDE:
'override'
ABSTRACT :
'abstract'
FINAL:
'final'
OPEN :
'open'
CONST :
'const'
1.2. LEXICAL GRAMMAR 15
LATEINIT :
'lateinit'
VARARG:
'vararg'
NOINLINE:
'noinline'
CROSSINLINE:
'crossinline'
REIFIED:
'reified'
EXPECT :
'expect'
ACTUAL:
'actual'
1.2.3 Literals
DecDigitNoZero:
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
DecDigit:
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
DecDigitOrSeparator:
DecDigit | '_'
DecDigits:
DecDigit {DecDigitOrSeparator } DecDigit
| DecDigit
DoubleExponent:
('e' | 'E') [('+' | '-')] DecDigits
RealLiteral:
FloatLiteral | DoubleLiteral
FloatLiteral:
DoubleLiteral ('f' | 'F')
| DecDigits ('f' | 'F')
DoubleLiteral:
[DecDigits] '.' DecDigits [DoubleExponent]
| DecDigits DoubleExponent
IntegerLiteral:
DecDigitNoZero {DecDigitOrSeparator } DecDigit
| DecDigit
16 CHAPTER 1. SYNTAX AND GRAMMAR
HexDigit:
DecDigit
| 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
| 'a' | 'b' | 'c' | 'd' | 'e' | 'f'
HexDigitOrSeparator:
HexDigit | '_'
HexLiteral
'0' ('x' | 'X') HexDigit {HexDigitOrSeparator } HexDigit
| '0' ('x' | 'X') HexDigit
BinDigit
'0' | '1'
BinDigitOrSeparator
BinDigit | '_'
BinLiteral
'0' ('b' | 'B') BinDigit {BinDigitOrSeparator } BinDigit
| '0' ('b' | 'B') BinDigit
UnsignedLiteral
(IntegerLiteral | HexLiteral | BinLiteral) ('u' | 'U') ['L']
LongLiteral
(IntegerLiteral | HexLiteral | BinLiteral) 'L'
BooleanLiteral
'true' | 'false'
NullLiteral
'null'
CharacterLiteral
''' (EscapeSeq | <any character excluding CR, LF, ''' or '\'>) '''
UniCharacterLiteral
'\' 'u' HexDigit HexDigit HexDigit HexDigit
EscapedIdentier
'\' ('t' | 'b' | 'r' | 'n' | ''' | '"' | '\' | '$')
EscapeSeq
UniCharacterLiteral | EscapedIdentier
1.2.4 Identiers
Letter
<any unicode character of categories Lu, Ll, Lt, Lm or Lo>
QuotedSymbol
<any character excluding CR, LF and '`'>
1.2. LEXICAL GRAMMAR 17
UnicodeDigit
<any unicode character of category Nd>
Identier
(Letter | '_') {Letter | '_' | UnicodeDigit}
| '`' QuotedSymbol {QuotedSymbol} '`'
Kotlin supports escaping identiers by enclosing any sequence of characters
into backtick (`) characters, allowing to use any name as an identier. This
allows not only using non-alphanumeric characters (like @ or #) in names, but
also using keywords like if or when as identiers. Actual set of characters that
is allowed to be escaped may, however, be a subject to platform restrictions.
Consult particular platform sections for details.
Note: for example, the following characters are not allowed in iden-
tiers used as declaration names on the JVM platform even when
escaped due to JVM restrictions: ., ;, [, ], /, <, >, :, \\ .
Escaped identiers are treated the same as corresponding non-escaped identier if
it is allowed. For example, an escaped identier `foo` and non-escaped identier
foo may be used interchangeably and refer to the same program entity.
IdentierOrSoftKey
Identier
| ABSTRACT
| ANNOTATION
| BY
| CATCH
| COMPANION
| CONSTRUCTOR
| CROSSINLINE
| DATA
| DYNAMIC
| ENUM
| EXTERNAL
| FINAL
| FINALLY
| IMPORT
| INFIX
| INIT
| INLINE
| INNER
| INTERNAL
| LATEINIT
| NOINLINE
| OPEN
| OPERATOR
| OUT
18 CHAPTER 1. SYNTAX AND GRAMMAR
| OVERRIDE
| PRIVATE
| PROTECTED
| PUBLIC
| REIFIED
| SEALED
| TAILREC
| VARARG
| WHERE
| GET
| SET
| FIELD
| PROPERTY
| RECEIVER
| PARAM
| SETPARAM
| DELEGATE
| FILE
| EXPECT
| ACTUAL
| CONST
| SUSPEND
Some of the keywords used in Kotlin are allowed to be used as identiers even
when not escaped. Such keywords are called soft keywords. You can see the
complete list of soft keyword in the rule above. All other keywords are considered
hard keywords and may only be used as identiers if escaped.
val field = 2
TRIPLE_QUOTE_OPEN
'"""'
FieldIdentier
'$' IdentierOrSoftKey
Opening a double quote (QUOTE_OPEN) rule puts the lexical grammar into
the special “line string” mode with the following rules. Closing double quote
(QUOTE_CLOSE) rule exits this mode.
1.2. LEXICAL GRAMMAR 19
QUOTE_CLOSE
'"'
LineStrRef
FieldIdentier
LineStrText
{<any character except '\', '"' or '$'>} | '$'
LineStrEscapedChar
EscapedIdentier | UniCharacterLiteral
LineStrExprStart
'${'
Opening a triple double quote (TRIPLE_QUOTE_OPEN) rule puts the lexical
grammar into the special “multiline string” mode with the following rules. Closing
triple double quote (TRIPLE_QUOTE_CLOSE) rule exits this mode.
TRIPLE_QUOTE_CLOSE
[MultilineStringQuote] '"""'
MultilineStringQuote
'"""' {'"'}
MultiLineStrRef
FieldIdentier
MultiLineStrText
{<any character except '"' or '$'>} | '$'
MultiLineStrExprStart
'${'
1.2.6 Tokens
These are all the valid tokens in one rule. Note that syntax grammar ignores
tokens DelimitedComment, LineComment and WS .
KotlinToken
ShebangLine
| DelimitedComment
| LineComment
| WS
| NL
| RESERVED
| DOT
| COMMA
| LPAREN
| RPAREN
| LSQUARE
20 CHAPTER 1. SYNTAX AND GRAMMAR
| RSQUARE
| LCURL
| RCURL
| MULT
| MOD
| DIV
| ADD
| SUB
| INCR
| DECR
| CONJ
| DISJ
| EXCL_WS
| EXCL_NO_WS
| COLON
| SEMICOLON
| ASSIGNMENT
| ADD_ASSIGNMENT
| SUB_ASSIGNMENT
| MULT_ASSIGNMENT
| DIV_ASSIGNMENT
| MOD_ASSIGNMENT
| ARROW
| DOUBLE_ARROW
| RANGE
| COLONCOLON
| DOUBLE_SEMICOLON
| HASH
| AT_NO_WS
| AT_POST_WS
| AT_PRE_WS
| AT_BOTH_WS
| QUEST_WS
| QUEST_NO_WS
| LANGLE
| RANGLE
| LE
| GE
| EXCL_EQ
| EXCL_EQEQ
| AS_SAFE
| EQEQ
| EQEQEQ
| SINGLE_QUOTE
| RETURN_AT
| CONTINUE_AT
1.2. LEXICAL GRAMMAR 21
| BREAK_AT
| THIS_AT
| SUPER_AT
| FILE
| FIELD
| PROPERTY
| GET
| SET
| RECEIVER
| PARAM
| SETPARAM
| DELEGATE
| PACKAGE
| IMPORT
| CLASS
| INTERFACE
| FUN
| OBJECT
| VAL
| VAR
| TYPE_ALIAS
| CONSTRUCTOR
| BY
| COMPANION
| INIT
| THIS
| SUPER
| TYPEOF
| WHERE
| IF
| ELSE
| WHEN
| TRY
| CATCH
| FINALLY
| FOR
| DO
| WHILE
| THROW
| RETURN
| CONTINUE
| BREAK
| AS
| IS
| IN
| NOT_IS
22 CHAPTER 1. SYNTAX AND GRAMMAR
| NOT_IN
| OUT
| DYNAMIC
| PUBLIC
| PRIVATE
| PROTECTED
| INTERNAL
| ENUM
| SEALED
| ANNOTATION
| DATA
| INNER
| TAILREC
| OPERATOR
| INLINE
| INFIX
| EXTERNAL
| SUSPEND
| OVERRIDE
| ABSTRACT
| FINAL
| OPEN
| CONST
| LATEINIT
| VARARG
| NOINLINE
| CROSSINLINE
| REIFIED
| EXPECT
| ACTUAL
| Identier
| RealLiteral
| IntegerLiteral
| HexLiteral
| BinLiteral
| LongLiteral
| BooleanLiteral
| NullLiteral
| CharacterLiteral
| QUOTE_OPEN
| QUOTE_CLOSE
| TRIPLE_QUOTE_OPEN
| TRIPLE_QUOTE_CLOSE
| LineStrRef
| LineStrText
| LineStrEscapedChar
1.3. SYNTAX GRAMMAR 23
| LineStrExprStart
| MultilineStringQuote
| MultiLineStrRef
| MultiLineStrText
| MultiLineStrExprStart
EOF
<end of input>
importList:
{importHeader}
importHeader:
'import' identier [('.' '*') | importAlias] [semi]
importAlias:
'as' simpleIdentier
topLevelObject:
declaration [semis]
typeAlias:
[modiers]
'typealias'
{NL}
simpleIdentier
[{NL} typeParameters]
{NL}
'='
{NL}
type
declaration:
classDeclaration
| objectDeclaration
| functionDeclaration
| propertyDeclaration
| typeAlias
classDeclaration:
[modiers]
('class' | (['fun' {NL}] 'interface'))
{NL}
simpleIdentier
[{NL} typeParameters]
[{NL} primaryConstructor]
[{NL} ':' {NL} delegationSpeciers]
[{NL} typeConstraints]
[({NL} classBody) | ({NL} enumClassBody)]
primaryConstructor:
[[modiers] 'constructor' {NL}] classParameters
classBody:
'{'
{NL}
classMemberDeclarations
{NL}
'}'
1.3. SYNTAX GRAMMAR 25
classParameters:
'('
{NL}
[classParameter {{NL} ',' {NL} classParameter} [{NL} ',']]
{NL}
')'
classParameter:
[modiers]
['val' | 'var']
{NL}
simpleIdentier
':'
{NL}
type
[{NL} '=' {NL} expression]
delegationSpeciers:
annotatedDelegationSpecier {{NL} ',' {NL} annotatedDelegationSpeci-
er}
delegationSpecier:
constructorInvocation
| explicitDelegation
| userType
| functionType
| ('suspend' {NL} functionType)
constructorInvocation:
userType {NL} valueArguments
annotatedDelegationSpecier:
{annotation} {NL} delegationSpecier
explicitDelegation:
(userType | functionType)
{NL}
'by'
{NL}
expression
typeParameters:
'<'
{NL}
typeParameter
{{NL} ',' {NL} typeParameter}
[{NL} ',']
{NL}
'>'
26 CHAPTER 1. SYNTAX AND GRAMMAR
typeParameter:
[typeParameterModiers] {NL} simpleIdentier [{NL} ':' {NL} type]
typeConstraints:
'where' {NL} typeConstraint {{NL} ',' {NL} typeConstraint}
typeConstraint:
{annotation}
simpleIdentier
{NL}
':'
{NL}
type
classMemberDeclarations:
{classMemberDeclaration [semis]}
classMemberDeclaration:
declaration
| companionObject
| anonymousInitializer
| secondaryConstructor
anonymousInitializer:
'init' {NL} block
companionObject:
[modiers]
'companion'
{NL}
['data']
{NL}
'object'
[{NL} simpleIdentier]
[{NL} ':' {NL} delegationSpeciers]
[{NL} classBody]
functionValueParameters:
'('
{NL}
[functionValueParameter {{NL} ',' {NL} functionValueParameter} [{NL}
',']]
{NL}
')'
functionValueParameter:
[parameterModiers] parameter [{NL} '=' {NL} expression]
functionDeclaration:
[modiers]
1.3. SYNTAX GRAMMAR 27
'fun'
[{NL} typeParameters]
[{NL} receiverType {NL} '.']
{NL}
simpleIdentier
{NL}
functionValueParameters
[{NL} ':' {NL} type]
[{NL} typeConstraints]
[{NL} functionBody]
functionBody:
block
| ('=' {NL} expression)
variableDeclaration:
{annotation} {NL} simpleIdentier [{NL} ':' {NL} type]
multiVariableDeclaration:
'('
{NL}
variableDeclaration
{{NL} ',' {NL} variableDeclaration}
[{NL} ',']
{NL}
')'
propertyDeclaration:
[modiers]
('val' | 'var')
[{NL} typeParameters]
[{NL} receiverType {NL} '.']
({NL} (multiVariableDeclaration | variableDeclaration))
[{NL} typeConstraints]
[{NL} (('=' {NL} expression) | propertyDelegate)]
[{NL} ';']
{NL}
(([getter] [{NL} [semi] setter]) | ([setter] [{NL} [semi] getter]))
propertyDelegate:
'by' {NL} expression
getter:
[modiers] 'get' [{NL} '(' {NL} ')' [{NL} ':' {NL} type] {NL} func-
tionBody]
setter:
[modiers] 'set' [{NL} '(' {NL} functionValueParameterWithOptional-
Type [{NL} ','] {NL} ')' [{NL} ':' {NL} type] {NL} functionBody]
28 CHAPTER 1. SYNTAX AND GRAMMAR
parametersWithOptionalType:
'('
{NL}
[functionValueParameterWithOptionalType {{NL} ',' {NL} functionVal-
ueParameterWithOptionalType} [{NL} ',']]
{NL}
')'
functionValueParameterWithOptionalType:
[parameterModiers] parameterWithOptionalType [{NL} '=' {NL} expres-
sion]
parameterWithOptionalType:
simpleIdentier {NL} [':' {NL} type]
parameter:
simpleIdentier
{NL}
':'
{NL}
type
objectDeclaration:
[modiers]
'object'
{NL}
simpleIdentier
[{NL} ':' {NL} delegationSpeciers]
[{NL} classBody]
secondaryConstructor:
[modiers]
'constructor'
{NL}
functionValueParameters
[{NL} ':' {NL} constructorDelegationCall]
{NL}
[block]
constructorDelegationCall:
('this' | 'super') {NL} valueArguments
enumClassBody:
'{'
{NL}
[enumEntries]
[{NL} ';' {NL} classMemberDeclarations]
{NL}
'}'
1.3. SYNTAX GRAMMAR 29
enumEntries:
enumEntry {{NL} ',' {NL} enumEntry} {NL} [',']
enumEntry:
[modiers {NL}] simpleIdentier [{NL} valueArguments] [{NL} classBody]
type:
[typeModiers] (functionType | parenthesizedType | nullableType | typeRef-
erence | denitelyNonNullableType)
typeReference:
userType
| 'dynamic'
nullableType:
(typeReference | parenthesizedType) {NL} (quest {quest})
quest:
QUEST_NO_WS
| QUEST_WS
userType:
simpleUserType {{NL} '.' {NL} simpleUserType}
simpleUserType:
simpleIdentier [{NL} typeArguments]
typeProjection:
([typeProjectionModiers] type)
| '*'
typeProjectionModiers:
typeProjectionModier {typeProjectionModier}
typeProjectionModier:
(varianceModier {NL})
| annotation
functionType:
[receiverType {NL} '.' {NL}]
functionTypeParameters
{NL}
'->'
{NL}
type
functionTypeParameters:
'('
{NL}
[parameter | type]
{{NL} ',' {NL} (parameter | type)}
[{NL} ',']
30 CHAPTER 1. SYNTAX AND GRAMMAR
{NL}
')'
parenthesizedType:
'('
{NL}
type
{NL}
')'
receiverType:
[typeModiers] (parenthesizedType | nullableType | typeReference)
parenthesizedUserType:
'('
{NL}
(userType | parenthesizedUserType)
{NL}
')'
denitelyNonNullableType:
[typeModiers]
(userType | parenthesizedUserType)
{NL}
'&'
{NL}
[typeModiers]
(userType | parenthesizedUserType)
statements:
[statement {semis statement}] [semis]
statement:
{label | annotation} (declaration | assignment | loopStatement | expression)
label:
simpleIdentier (AT_NO_WS | AT_POST_WS) {NL}
controlStructureBody:
block
| statement
block:
'{'
{NL}
statements
{NL}
'}'
loopStatement:
forStatement
1.3. SYNTAX GRAMMAR 31
| whileStatement
| doWhileStatement
forStatement:
'for'
{NL}
'('
{annotation}
(variableDeclaration | multiVariableDeclaration)
'in'
expression
')'
{NL}
[controlStructureBody]
whileStatement:
'while'
{NL}
'('
expression
')'
{NL}
(controlStructureBody | ';')
doWhileStatement:
'do'
{NL}
[controlStructureBody]
{NL}
'while'
{NL}
'('
expression
')'
assignment:
((directlyAssignableExpression '=') | (assignableExpression assignmentAn-
dOperator)) {NL} expression
semi:
(';' | NL) {NL}
semis:
';' | NL {';' | NL}
expression:
disjunction
disjunction:
conjunction {{NL} '||' {NL} conjunction}
32 CHAPTER 1. SYNTAX AND GRAMMAR
conjunction:
equality {{NL} '&&' {NL} equality}
equality:
comparison {equalityOperator {NL} comparison}
comparison:
genericCallLikeComparison {comparisonOperator {NL} genericCallLike-
Comparison}
genericCallLikeComparison:
inxOperation {callSux}
inxOperation:
elvisExpression {(inOperator {NL} elvisExpression) | (isOperator {NL}
type)}
elvisExpression:
inxFunctionCall {{NL} elvis {NL} inxFunctionCall}
elvis:
QUEST_NO_WS ':'
inxFunctionCall:
rangeExpression {simpleIdentier {NL} rangeExpression}
rangeExpression:
additiveExpression {('..' | '..<') {NL} additiveExpression}
additiveExpression:
multiplicativeExpression {additiveOperator {NL} multiplicativeExpression}
multiplicativeExpression:
asExpression {multiplicativeOperator {NL} asExpression}
asExpression:
prexUnaryExpression {{NL} asOperator {NL} type}
prexUnaryExpression:
{unaryPrex} postxUnaryExpression
unaryPrex:
annotation
| label
| (prexUnaryOperator {NL})
postxUnaryExpression:
primaryExpression {postxUnarySux}
postxUnarySux:
postxUnaryOperator
| typeArguments
| callSux
1.3. SYNTAX GRAMMAR 33
| indexingSux
| navigationSux
directlyAssignableExpression:
(postxUnaryExpression assignableSux)
| simpleIdentier
| parenthesizedDirectlyAssignableExpression
parenthesizedDirectlyAssignableExpression:
'('
{NL}
directlyAssignableExpression
{NL}
')'
assignableExpression:
prexUnaryExpression
| parenthesizedAssignableExpression
parenthesizedAssignableExpression:
'('
{NL}
assignableExpression
{NL}
')'
assignableSux:
typeArguments
| indexingSux
| navigationSux
indexingSux:
'['
{NL}
expression
{{NL} ',' {NL} expression}
[{NL} ',']
{NL}
']'
navigationSux:
memberAccessOperator {NL} (simpleIdentier | parenthesizedExpression |
'class')
callSux:
[typeArguments] (([valueArguments] annotatedLambda) | valueArguments)
annotatedLambda:
{annotation} [label] {NL} lambdaLiteral
34 CHAPTER 1. SYNTAX AND GRAMMAR
typeArguments:
'<'
{NL}
typeProjection
{{NL} ',' {NL} typeProjection}
[{NL} ',']
{NL}
'>'
valueArguments:
'(' {NL} [valueArgument {{NL} ',' {NL} valueArgument} [{NL} ',']
{NL}] ')'
valueArgument:
[annotation]
{NL}
[simpleIdentier {NL} '=' {NL}]
['*']
{NL}
expression
primaryExpression:
parenthesizedExpression
| simpleIdentier
| literalConstant
| stringLiteral
| callableReference
| functionLiteral
| objectLiteral
| collectionLiteral
| thisExpression
| superExpression
| ifExpression
| whenExpression
| tryExpression
| jumpExpression
parenthesizedExpression:
'('
{NL}
expression
{NL}
')'
collectionLiteral:
'[' {NL} [expression {{NL} ',' {NL} expression} [{NL} ','] {NL}] ']'
literalConstant:
BooleanLiteral
1.3. SYNTAX GRAMMAR 35
| IntegerLiteral
| HexLiteral
| BinLiteral
| CharacterLiteral
| RealLiteral
| 'null'
| LongLiteral
| UnsignedLiteral
stringLiteral:
lineStringLiteral
| multiLineStringLiteral
lineStringLiteral:
'"' {lineStringContent | lineStringExpression} '"'
multiLineStringLiteral:
'"""' {multiLineStringContent | multiLineStringExpression | '"'}
TRIPLE_QUOTE_CLOSE
lineStringContent:
LineStrText
| LineStrEscapedChar
| LineStrRef
lineStringExpression:
'${'
{NL}
expression
{NL}
'}'
multiLineStringContent:
MultiLineStrText
| '"'
| MultiLineStrRef
multiLineStringExpression:
'${'
{NL}
expression
{NL}
'}'
lambdaLiteral:
'{'
{NL}
[[lambdaParameters] {NL} '->' {NL}]
statements
36 CHAPTER 1. SYNTAX AND GRAMMAR
{NL}
'}'
lambdaParameters:
lambdaParameter {{NL} ',' {NL} lambdaParameter} [{NL} ',']
lambdaParameter:
variableDeclaration
| (multiVariableDeclaration [{NL} ':' {NL} type])
anonymousFunction:
['suspend']
{NL}
'fun'
[{NL} type {NL} '.']
{NL}
parametersWithOptionalType
[{NL} ':' {NL} type]
[{NL} typeConstraints]
[{NL} functionBody]
functionLiteral:
lambdaLiteral
| anonymousFunction
objectLiteral:
['data']
{NL}
'object'
[{NL} ':' {NL} delegationSpeciers {NL}]
[{NL} classBody]
thisExpression:
'this'
| THIS_AT
superExpression:
('super' ['<' {NL} type {NL} '>'] [AT_NO_WS simpleIdentier])
| SUPER_AT
ifExpression:
'if'
{NL}
'('
{NL}
expression
{NL}
')'
{NL}
1.3. SYNTAX GRAMMAR 37
nallyBlock:
'finally' {NL} block
jumpExpression:
('throw' {NL} expression)
| (('return' | RETURN_AT) [expression])
| 'continue'
| CONTINUE_AT
| 'break'
| BREAK_AT
callableReference:
[receiverType] '::' {NL} (simpleIdentier | 'class')
assignmentAndOperator:
'+='
| '-='
| '*='
| '/='
| '%='
equalityOperator:
'!='
| '!=='
| '=='
| '==='
comparisonOperator:
'<'
| '>'
| '<='
| '>='
inOperator:
'in'
| NOT_IN
isOperator:
'is'
| NOT_IS
additiveOperator:
'+'
| '-'
multiplicativeOperator:
'*'
| '/'
| '%'
1.3. SYNTAX GRAMMAR 39
asOperator:
'as'
| 'as?'
prexUnaryOperator:
'++'
| '--'
| '-'
| '+'
| excl
postxUnaryOperator:
'++'
| '--'
| ('!' excl)
excl:
'!'
| EXCL_WS
memberAccessOperator:
({NL} '.')
| ({NL} safeNav)
| '::'
safeNav:
QUEST_NO_WS '.'
modiers:
annotation | modier {annotation | modier}
parameterModiers:
annotation | parameterModier {annotation | parameterModier}
modier:
(classModier | memberModier | visibilityModier | functionModier | prop-
ertyModier | inheritanceModier | parameterModier | platformModier)
{NL}
typeModiers:
typeModier {typeModier}
typeModier:
annotation
| ('suspend' {NL})
classModier:
'enum'
| 'sealed'
| 'annotation'
| 'data'
40 CHAPTER 1. SYNTAX AND GRAMMAR
| 'inner'
| 'value'
memberModier:
'override'
| 'lateinit'
visibilityModier:
'public'
| 'private'
| 'internal'
| 'protected'
varianceModier:
'in'
| 'out'
typeParameterModiers:
typeParameterModier {typeParameterModier}
typeParameterModier:
(reicationModier {NL})
| (varianceModier {NL})
| annotation
functionModier:
'tailrec'
| 'operator'
| 'infix'
| 'inline'
| 'external'
| 'suspend'
propertyModier:
'const'
inheritanceModier:
'abstract'
| 'final'
| 'open'
parameterModier:
'vararg'
| 'noinline'
| 'crossinline'
reicationModier:
'reified'
platformModier:
'expect'
1.3. SYNTAX GRAMMAR 41
| 'actual'
annotation:
(singleAnnotation | multiAnnotation) {NL}
singleAnnotation:
((annotationUseSiteTarget {NL}) | AT_NO_WS | AT_PRE_WS) un-
escapedAnnotation
multiAnnotation:
((annotationUseSiteTarget {NL}) | AT_NO_WS | AT_PRE_WS) '['
(unescapedAnnotation {unescapedAnnotation}) ']'
annotationUseSiteTarget:
(AT_NO_WS | AT_PRE_WS) ('field' | 'property' | 'get' | 'set' |
'receiver' | 'param' | 'setparam' | 'delegate') {NL} ':'
unescapedAnnotation:
constructorInvocation
| userType
simpleIdentier:
Identier
| 'abstract'
| 'annotation'
| 'by'
| 'catch'
| 'companion'
| 'constructor'
| 'crossinline'
| 'data'
| 'dynamic'
| 'enum'
| 'external'
| 'final'
| 'finally'
| 'get'
| 'import'
| 'infix'
| 'init'
| 'inline'
| 'inner'
| 'internal'
| 'lateinit'
| 'noinline'
| 'open'
| 'operator'
| 'out'
| 'override'
42 CHAPTER 1. SYNTAX AND GRAMMAR
| 'private'
| 'protected'
| 'public'
| 'reified'
| 'sealed'
| 'tailrec'
| 'set'
| 'vararg'
| 'where'
| 'field'
| 'property'
| 'receiver'
| 'param'
| 'setparam'
| 'delegate'
| 'file'
| 'expect'
| 'actual'
| 'const'
| 'suspend'
| 'value'
identier:
simpleIdentier {{NL} '.' simpleIdentier}
Type system
Glossary
T Type (with unknown nullability)
T !! Non-nullable type
T ? Nullable type
{T } Universe of all possible types
{T !!}
Universe of non-nullable types
{T ?}
Universe of nullable types
Well-formed type
A properly constructed type w.r.t. Kotlin type system
Γ Type context
A <: B
A is a subtype of B
A <:
>B
A and B are not related w.r.t. subtyping
Type constructor
An abstract type with one or more type parameters, which must be
instantiated before use
Parameterized type
A concrete type, which is the result of type constructor instantiation
Type parameter
Formal type parameter of a type constructor
Type argument
Actual type argument in a parameterized type
T [A1 , ▷ ▷ ▷ , An ]
The result of type constructor T instantiation with type arguments A i
T [σ] The result of type constructor T (F1 , ▷ ▷ ▷ , Fn ) instantiation with the assumed
43
44 CHAPTER 2. TYPE SYSTEM
substitution σ : F1 = A1 , ▷ ▷ ▷ , Fn = An
σT The result of type substitution in type T w.r.t. substitution σ
KT (F, A)
Captured type from the type capturing of type parameter F and type
argument A in parameterized type T
T ⟨K1 , ▷ ▷ ▷ , Kn ⟩
The result of type capturing for parameterized type T with captured types
Ki
T ⟨τ ⟩
The result of type capturing for parameterized type T (F1 , ▷ ▷ ▷ , Fn ) with
captured substitution τ : F1 = K1 , ▷ ▷ ▷ , Fn = Kn
A&B
Intersection type of A and B
A|B
Union type of A and B
GLB
Greatest lower bound
LUB
Least upper bound
Introduction
Similarly to most other programming languages, Kotlin operates on data in
the form of values or objects, which have types — descriptions of what is the
expected behaviour and possible values for their datum. An empty value is
represented by a special null object; most operations with it result in runtime
errors or exceptions.
Kotlin has a type system with the following main properties.
• Hybrid static, gradual and ow type checking;
• Null safety;
• No unsafe implicit conversions;
• Unied top and bottom types;
• Nominal subtyping with bounded parametric polymorphism and mixed-site
variance.
Type safety (consistency between compile and runtime types) is veried stat-
ically, at compile time, for the majority of Kotlin types. However, for better
interoperability with platform-dependent code Kotlin also support a variant of
gradual types in the form of exible types. Even more so, in some cases the
compile-time type of a value may change depending on the control- and data-ow
of the program; a feature usually known as ow typing, represented in Kotlin as
smart casts.
Null safety is enforced by having two type universes: nullable (with nullable types
T ?) and non-nullable (with non-nullable types T !!). A value of any non-nullable
2.1. TYPE KINDS 45
type cannot contain null, meaning all operations within the non-nullable type
universe are safe w.r.t. empty values, i.e., should never result in a runtime error
caused by null.
Implicit conversions between types in Kotlin are limited to safe upcasts w.r.t.
subtyping, meaning all other (unsafe) conversions must be explicit, done via
either a conversion function or an explicit cast. However, Kotlin also supports
smart casts — a special kind of implicit conversions which are safe w.r.t. program
control- and data-ow, which are covered in more detail here.
The unied supertype type for all types in Kotlin is kotlin.Any?, a nullable
version of kotlin.Any. The unied subtype type for all types in Kotlin is
kotlin.Nothing.
Kotlin uses nominal subtyping, meaning subtyping relation is dened when
a type is declared, with bounded parametric polymorphism, implemented as
generics via parameterized types. Subtyping between these parameterized types
is dened through mixed-site variance.
latter are special types which are not expressible in Kotlin and are used by the
compiler in type inference, smart casts, etc.
kotlin.Any
kotlin.Any is the unied supertype (⊤) for {T !!}, i.e., all non-nullable types are
subtypes of kotlin.Any, either explicitly, implicitly, or by subtyping relation.
Note: additional details about kotlin.Any are available here.
kotlin.Nothing
kotlin.Nothing is the unied subtype (⊥) for {T }, i.e., kotlin.Nothing is a
subtype of all well-formed Kotlin types, including user-dened ones. This makes
it an uninhabited type (as it is impossible for anything to be, for example, a
function and an integer at the same time), meaning instances of this type can
never exist at runtime; subsequently, there is no way to create an instance of
kotlin.Nothing in Kotlin.
Note: additional details about kotlin.Nothing are available here.
kotlin.Function
kotlin.Function(R) is the unied supertype of all function types. It is param-
eterized over function return type R.
Array types
Kotlin arrays are represented as a parameterized type kotlin.Array(T ), where
T is the type of the stored elements, which supports get/set operations. The
2.1. TYPE KINDS 47
kotlin.Array(T ) type follows the rules of regular type constructors and param-
eterized types w.r.t. subtyping.
In addition to the general kotlin.Array(T ) type, Kotlin also has the following
specialized array types:
ATS takes an important part in how variable length parameters are handled.
Note: additional details about built-in array types are available here.
T : S1 , ▷ ▷ ▷ , S m
consists of
• type name T
• (optional) list of supertypes S1 , ▷ ▷ ▷ , Sm
To represent a well-formed simple classier type, T : S1 , ▷ ▷ ▷ , Sm should satisfy
the following conditions.
• T is a valid type name
• ∀i ∈ [1, m] : Si must be concrete, non-nullable, well-formed type
• the transitive closure S∗ (T ) of the set of type supertypes S(T :
S1 , ▷ ▷ ▷ , Sm ) = {S1 , ▷ ▷ ▷ , Sm } ∪ S(S1 ) ∪ ▷ ▷ ▷ ∪ S(Sm ) is consistent, i.e., does
not contain two parameterized types with dierent type arguments.
Example:
// A well-formed type with no supertypes
interface Base
// An ill-formed type,
// as nullable type cannot be a supertype
interface Invalid : Base?
Note: for the purpose of dierent type system examples, we assume
the presence of the following well-formed concrete types:
• class String
• interface Number
• class Int <: Number
• class Double <: Number
Note: Number is actually a built-in abstract class; we use it as an
interface for illustrative purposes.
T (F1 , ▷ ▷ ▷ , Fn ) : S1 , ▷ ▷ ▷ , Sm
• type name T
• type parameters F1 , ▷ ▷ ▷ , Fn
• (optional) list of supertypes S1 , ▷ ▷ ▷ , Sm
To represent a well-formed type constructor, T (F1 , ▷ ▷ ▷ , Fn ) : S1 , ▷ ▷ ▷ , Sm should
satisfy the following conditions.
• T is a valid type name
• ∀i ∈ [1, n] : Fi must be well-formed type parameter
• ∀j ∈ [1, m] : Sj must be concrete, non-nullable, well-formed type
To instantiate a type constructor, one provides it with type arguments, creating
a concrete parameterized classier type
T [A1 , ▷ ▷ ▷ , An ]
which consists of
• type constructor T
• type arguments A1 , ▷ ▷ ▷ , An
To represent a well-formed parameterized type, T [A1 , ▷ ▷ ▷ , An ] should satisfy the
following conditions.
• T is a well-formed type constructor with n type parameters
• ∀i ∈ [1, n] : Ai must be well-formed concrete type
• ∀i ∈ [1, n] : variance of Ai does not contradict variance of Fi
• ∀i ∈ [1, n] : Ai <: τ Ui , where Ui is the upper bound for Fi and captured
substitution τ : F1 = K1 , ▷ ▷ ▷ , Fn = Kn manipulates captured types.
• the transitive closure S∗ (T ) of the set of type supertypes S(T ⟨τ ⟩ :
τ S1 , ▷ ▷ ▷ , τ Sm ) = {τ S1 , ▷ ▷ ▷ , τ Sm } ∪ S(τ S1 ) ∪ ▷ ▷ ▷ ∪ S(τ Sm ) is consistent, i.e.,
does not contain two parameterized types with dierent type arguments.
Example:
// A well-formed type constructor with no supertypes
// A and B are unbounded type parameters
interface Generic<A, B>
Mixed-site variance
To implement subtyping between parameterized types, Kotlin uses mixed-site
variance — a combination of declaration- and use-site variance, which is easier
52 CHAPTER 2. TYPE SYSTEM
Note: Kotlin does not support specifying both co- and contravariance
at the same time, i.e., it is impossible to have T<out A in B> neither
on declaration- nor on use-site.
For further discussion about mixed-site variance and its practical applications,
we readdress you to subtyping.
Declaration-site variance
A type parameter F may be invariant, covariant or contravariant.
fun testInvariant() {
var invInt: Invariant<Int> = ...
var invNumber: Invariant<Number> = ...
fun testOut() {
var outInt: Out<Int> = ...
var outNumber: Out<Number> = ...
fun testIn() {
var inInt: In<Int> = ...
var inNumber: In<Number> = ...
Use-site variance
Kotlin also supports use-site variance, by specifying the variance for type argu-
ments. Similarly to type parameters, one can have type arguments being co-,
contra- or invariant.
Important: use-site variance cannot be used when declaring a super-
type top-level type argument.
By default, all type arguments are invariant.
To specify a covariant type argument, it is marked as out A. To specify a
contravariant type argument, it is marked as in A.
Kotlin prohibits contradictory combinations of declaration- and use-site variance
as follows.
• It is a compile-time error to use a covariant type argument in a contravariant
type parameter
• It is a compile-time error to use a contravariant type argument in a covariant
type parameter
In case one cannot specify any well-formed type argument, but still needs
to use a parameterized type in a type-safe way, they may use bivariant type
argument ⋆, which is roughly equivalent to a combination of out kotlin.Any?
and in kotlin.Nothing (for further details, see subtyping).
Note: informally, T [⋆] means “I can give out something very generic
(kotlin.Any?) and cannot take in anything”.
Example:
// A type constructor with an invariant type parameter
interface Inv<A>
fun test() {
var invInt: Inv<Int> = ...
var invNumber: Inv<Number> = ...
var outInt: Inv<out Int> = ...
var outNumber: Inv<out Number> = ...
var inInt: Inv<in Int> = ...
var inNumber: Inv<in Number> = ...
when (random) {
2.1. TYPE KINDS 55
1 -> {
inInt = invInt // OK
// T<in Int> :> T<Int>
inInt = invNumber // OK
// T<in Int> :> T<in Number> :> T<Number>
}
2 -> {
outNumber = invInt // OK
// T<out Number> :> T<out Int> :> T<Int>
outNumber = invNumber // OK
// T<out Number> :> T<Number>
}
3 -> {
invInt = inInt // ERROR
invInt = outInt // ERROR
// It is invalid to assign less specific type
// to a more specific one
// T<Int> <: T<in Int>
// T<Int> <: T<out Int>
}
4 -> {
inInt = outInt // ERROR
inInt = outNumber // ERROR
// types with co- and contravariant type parameters
// are not connected by subtyping
// T<in Int> not(<:>) T<out Int>
}
}
}
L <: K <: U
interface Root<T>
interface A
interface B : A
interface C : B
fun test01() {
fun test02() {
fun test03() {
fun test04() {
val rec: Recursive<*> = mk()
// KS ⪯ KT
// (from subtyping for parameterized types)
// KS ⊆ KT
// (from type containment rules)
// (Nothing <: KS <: Recursive<KS>) ⊆ (Nothing <: KT <: Any?)
//
// True
• in A ⪯ in B if A :> B
Rules for captured types follow the same structure.
• KA ⪯ KB if KA ⊆ KB
• KA ⪯ out KB if KA <: KB
• KA ⪯ in KB if KA :> KB
• out KA ⪯ out KB if KA <: KB
• in KA ⪯ in KB if KA :> KB
In case we need to establish type containment between regular type A and
captured type KB , A is considered as if it is a captured type KA : A <: KA <: A.
FT(A1 , ▷ ▷ ▷ , An ) → R
consists of
• argument types Ai
• return type R
and may be considered the following instantiation of a special type constructor
FunctionN(in P1 , ▷ ▷ ▷ , in Pn , out R) (please note the variance of type parame-
ters)
FT(A1 , ▷ ▷ ▷ , An ) → R ≡ FunctionN[A1 , ▷ ▷ ▷ , An , R]
These FunctionN types follow the rules of regular type constructors and parame-
terized types w.r.t. subtyping.
A function type with receiver FTR
FTR(RT, A1 , ▷ ▷ ▷ , An ) → R
consists of
• receiver type RT
• argument types Ai
• return type R
62 CHAPTER 2. TYPE SYSTEM
From the type system’s point of view, it is equivalent to the following function
type
FTR(RT, A1 , ▷ ▷ ▷ , An ) → R ≡ FT(RT, A1 , ▷ ▷ ▷ , An ) → R
fun bar() {
val fooRef: (Int) -> String = ::foo
val fooLambda: (Int) -> String = { it.toString() }
val suspendFooLambda: suspend (Int) -> String = { it.toString() }
runtime type T will be a specic type satisfying ∃S : T <: S ∧ L <: S <: U , thus
making the substitution possibly unsafe, which is why Kotlin generates dynamic
assertions, when it is impossible to prove statically the safety of exible type
use.
Dynamic type
Kotlin includes a special dynamic type, which in many contexts can be viewed
as a exible type (kotlin.Nothing ▷▷ kotlin.Any?). By denition, this type
represents any possible Kotlin type, and may be used to support interoperability
with dynamically typed libraries, platforms or languages.
However, as a platform may assign special meaning to the values of dynamic type,
it may be handled dierently from the regular exible type. These dierences
are to be explained in the corresponding platform-dependent sections of this
specication.
Platform types
The main use cases for exible types are platform types — types which the Kotlin
compiler uses, when interoperating with code written for another platform (e.g.,
Java). In this case all types on the interoperability boundary are subject to
exibilization — the process of converting a platform-specic type to a Kotlin-
compatible exible type.
For further details on how exibilization is done, see the corresponding JVM
section.
Important: platform types should not be confused with multi-platform
projects — another Kotlin feature targeted at supporting platform
interop.
Nullability lozenge
fun main() {
val nested = Parent.Nested(42)
2.3. SUBTYPING 69
2.3 Subtyping
Kotlin uses the classic notion of subtyping as substitutability — if S is a subtype
of T (denoted as S <: T ), values of type S can be safely used where values of
type T are expected. The subtyping relation <: is:
• reexive (A <: A)
• rigidly transitive (A <: B ∧ B <: C ⇒ A <: C for non-exible types A, B
and C)
Two types A and B are equivalent (A ≡ B), i A <: B ∧ B <: A. Due to the
presence of exible types, this relation is also only rigidly transitive, e.g., holds
only for non-exible types (see here for more details).
fun iltAsIntersection() {
val a: Int = 42 // ILT(Byte, Short, Int, Long) <: Int
2.3. SUBTYPING 71
fun iltAsUnion() {
val a: Short = 42
select(a, 1337.asIn())
// For argument a:
// Short <: S
// For argument b:
// In<ILT(Short, Int, Long)> <: In<S> =>
// S <: ILT(Short, Int, Long)
// Solution: S =:= Short
}
Example:
η(inv X) = {out X, in X}
η(out X) = {out X, in kotlin.Nothing}
η(in X) = {out kotlin.Any?, in X}
η(⋆) = {out kotlin.Any?, in kotlin.Nothing}
References
1. Ross Tate. “Mixed-site variance.” FOOL, 2013.
2. Ross Tate, Alan Leung, and Sorin Lerner. “Taming wildcards in Java’s
type system.” PLDI, 2011.
78 CHAPTER 2. TYPE SYSTEM
Chapter 3
Kotlin has several built-in classier types, which are important for the rest of
this document. Some information about these types is given in the type system
section, here we extend it with additional non-type-system-relevant details.
Note: these types may have regular declarations in the standard
library, but they also introduce semantics not representable via Kotlin
source code.
In this section we describe these types and their semantics.
Note: this section is not meant to be a detailed description of all
types available in the standard library, for that please refer to the
standard library documentation.
3.1 kotlin.Any
Besides being the unied supertype of all non-nullable types, kotlin.Any must
also provide the following methods.
• public open operator fun equals(other: Any?): Boolean
Returns true i a value is equal to some other value. Implementations
of equals must satisfy the properties of reexivity (x.equals(x) is al-
ways true), symmetry (x.equals(y) == y.equals(x)), transitivity (if
x.equals(y) is true and y.equals(z) is true, x.equals(z) is also true)
and consistency (x.equals(y) should not change its result between multi-
ple invocations). A non-null value also must never be considered equal to
null, i.e., x.equals(null) must be false.
79
80 CHAPTER 3. BUILT-IN TYPES AND THEIR SEMANTICS
3.2 kotlin.Nothing
kotlin.Nothing is an uninhabited type, which means the evaluation of an
expression with kotlin.Nothing type can never complete normally. Therefore,
it is used to mark special situations, such as
• non-terminating expressions
• exceptional control ow
• control ow transfer
Further details about how kotlin.Nothing should be handled are available here
and here.
3.3 kotlin.Unit
kotlin.Unit is a unit type, i.e., a type with only one value kotlin.Unit; all
values of type kotlin.Unit should reference the same underlying kotlin.Unit
object. It is somewhat similar in purpose to void return type in other program-
ming languages in that it signies an absence of a value (i.e. the returned type
for a function returning nothing), but is dierent in that there is, in fact, a single
value of this type.
3.4 kotlin.Boolean
kotlin.Boolean is the boolean logic type of Kotlin, representing a value which
may be either true or false. It is the type of boolean literals as well as the
type returned or expected by some built-in Kotlin operators.
• kotlin.Int
• kotlin.Short
• kotlin.Byte
• kotlin.Long
Note: Kotlin does not have a built-in arbitrary-precision integer type.
Note: Kotlin does not have any built-in unsigned integer types.
These types may or may not have dierent runtime representations, depending
on the used platform and/or implementation. Consult the specic platform
reference for further details.
kotlin.Int is the type of integer numbers that is required to be able to hold
the values at least in the range from −231 to 231 − 1. If an arithmetic operation
on kotlin.Int results in arithmetic overow, the result is unspecied.
kotlin.Short is the type of integer numbers that is required to be able to hold
the values at least in the range from −215 to 215 − 1. If an arithmetic operation
on kotlin.Short results in arithmetic overow, the result is unspecied.
kotlin.Byte is the type of integer numbers that is required to be able to hold
the values at least in the range from −27 to 27 − 1. If an arithmetic operation
on kotlin.Byte results in arithmetic overow, the result is unspecied.
kotlin.Long is the type of integer numbers that is required to be able to hold
the values at least in the range from −263 to 263 − 1. If an arithmetic operation
on kotlin.Long results in arithmetic overow, the result is unspecied.
Note: by “arithmetic overow” we assume both positive and negative
integer overows.
Important: a platform implementation may specify behaviour for an
arithmetic overow.
...
foo(2)
As the integer literal 2 has a type that is applicable for both versions
of foo (see Overload resolution section for details) and the types
kotlin.Int and kotlin.Short are not related w.r.t. subtyping,
it would not be possible to select a more specic candidate
out of the two. However, if we consider Widen(kotlin.Int)
and Widen(kotlin.Short) respectively as the types of value,
rst candidate becomes more specic than the second, because
Widen(kotlin.Int) <: Widen(kotlin.Short).
3.7 kotlin.Char
kotlin.Char is the built-in classier type which represents a single Unicode
symbol in UCS-2 character encoding. It is the type of character literals.
3.8. KOTLIN.STRING 83
3.8 kotlin.String
kotlin.String is the built-in classier type which represents a sequence of
Unicode symbol in UCS-2 character encoding. It is the type of the result of
string interpolation.
Important: a platform implementation may extend the supported
character encodings, e.g., to UTF-16.
3.9 kotlin.Enum
kotlin.Enum<T> is the built-in parameterized classier type which is used as
a superclass for all enum classes: every enum class E is implicitly a subtype of
kotlin.Enum<E>.
kotlin.Enum<T> has the following characteristics.
• kotlin.Enum<T> is a subtype of kotlin.Comparable<T>
kotlin.Enum<T> provides the following properties.
• public final val name: String
Contains the name of this enumeration constant, exactly as declared in its
declaration.
• public final val ordinal: Int
Contains the ordinal of this enumeration constant, i.e., its position in the
declaration, starting from zero.
kotlin.Enum<T> provides the following member functions.
• public override final fun compareTo(other: T): Int
The implementation of kotlin.Comparable. The result of a.compareTo(b)
for enum class instances a and b is equivalent to a.ordinal.compareTo(b.ordinal).
• public override final fun equals(other: Any?): Boolean
• public override final fun hashCode(): Int
These member functions are dened to their default behaviour: only the
same entry of an enum class is equal to itself and no other object. Hash
implementation is required to be consistent, but unspecied.
Note: the presence of these nal member functions ensures the
semantics of equality and comparison for the enumeration objects,
as they cannot be overridden by the user.
84 CHAPTER 3. BUILT-IN TYPES AND THEIR SEMANTICS
3.12 kotlin.Throwable
kotlin.Throwable is the built-in classier type that is the base type of all
exception types. Any value that is used in a throw expression must have a static
type that is a subtype of kotlin.Throwable. Any type that is used in a catch
part of the try expression must be a subtype of (or equal to) kotlin.Throwable.
Other members may exist, please refer to the standard library documentation for
details. No subtype of kotlin.Throwable is allowed to have type parameters.
Declaring such a type is a compile-time error.
3.13 kotlin.Comparable
kotlin.Comparable<in T> is a built-in parameterized type which represents
values that may be compared for total ordering. It provides the following member
function:
3.14 kotlin.Function
kotlin.Function<out R> is the base classier type of all function types. See
the relevant section for details.
3.16.2 kotlin.reflect.KCallable
kotlin.reflect.KCallable<out R> is the class used to represent runtime in-
formation for callables (i.e. properties and functions). It is mainly used as base
type for other types described in this section. It provides at least the following
property:
public val name: String
This property contains the name of the callable. Other members or base types
for this class may be provided by platform and/or implementation.
3.16.3 kotlin.reflect.KProperty
kotlin.reflect.KProperty<out R> is the class used to represent runtime in-
formation for properties. It is the base type of property references. This type
is used in property delegation. kotlin.reflect.KProperty<R> is a subtype of
kotlin.reflect.KCallable<R>. Other members or base types for this class
may be provided by platform and/or implementation.
3.16.4 kotlin.reflect.KFunction
kotlin.reflect.KFunction<out R> is the class used to represent run-
time information for functions. It is the base type of function references.
kotlin.reflect.KFunction<R> is a subtype of kotlin.reflect.KCallable<R>
and kotlin.Function<R>. Other members or base types for this class may be
provided by platform and/or implementation.
88 CHAPTER 3. BUILT-IN TYPES AND THEIR SEMANTICS
Chapter 4
Declarations
Glossary
Entity
Distinguishable part of a program
Identier
Name of a program entity
Path
Sequence of identiers which references a program entity in a given scope
Introduction
Declarations in Kotlin are used to introduce entities (values, types, etc.); most
declarations are named, i.e. they also assign an identier to their own entity,
however, some declarations may be anonymous.
Every declaration is accessible in a particular scope, which is dependent both
on where the declaration is located and on the declaration itself. Every named
declaration introduces a binding for this name in the scope it is declared in. For
most of the declarations, this scope is the declaration scope introduced by the
parent declaration, e.g. the declaration this declaration is syntactically nested
in. See scoping section for details.
89
90 CHAPTER 4. DECLARATIONS
simpleIdentier
[{NL} typeParameters]
[{NL} primaryConstructor]
[{NL} ':' {NL} delegationSpeciers]
[{NL} typeConstraints]
[({NL} classBody) | ({NL} enumClassBody)]
objectDeclaration:
[modiers]
'object'
{NL}
simpleIdentier
[{NL} ':' {NL} delegationSpeciers]
[{NL} classBody]
Classier declarations introduce new types to the program, of the forms described
here. There are three kinds of classier declarations:
• class declarations;
• interface declarations;
• object declarations.
Important: object literals are similar to object declarations and are
considered to be anonymous classier declarations, despite being
expressions.
It is allowed to inherit from a single class only, i.e., multiple class inheritance is
not supported. Multiple interface inheritance is allowed.
Instance initialization block describes a block of code which should be executed
during object creation.
Property and function declarations in the class body introduce their respective
entities in this class’ scope, meaning they are available only on an entity of the
corresponding class.
Companion object declaration companion object CO { ... } for class C intro-
duces an object, which is available under this class’ name or under the path
C.CO. Companion object name may be omitted, in which case it is considered to
be equal to Companion.
Nested classier declarations introduce new classiers, available under this class’
name. Further details are available here.
A parameterized class declaration, in addition to what constitutes a simple class
declaration, also has a type parameter list T1 , ▷ ▷ ▷ , Tm and extends the rules for
a simple class declaration w.r.t. this type parameter list. Further details are
described here.
Examples:
// An open class with no supertypes
//
open class Base
init {
i = arg * arg
92 CHAPTER 4. DECLARATIONS
}
}
companion object {
fun duplet(a: Int) = Pair(a, a)
}
}
Constructor declaration
There are two types of class constructors in Kotlin: primary and secondary.
A primary constructor is a concise way of describing class properties together
with constructor parameters, and has the following form
ptor : (p1 , ▷ ▷ ▷ , pn )
4.1. CLASSIFIER DECLARATION 93
One can consider primary constructor parameters to have the following syntactic
expansion.
class Foo(i: Int, vararg d_: Double, s_: String) : Super(i, d_, s_) {
val d = d_
var s = s_
}
When accessing property constructor parameters inside the class body, one
works with their corresponding properties; however, when accessing them in
the supertype specier list (e.g., as an argument to a superclass constructor
invocation), we see them as actual parameters, which cannot be changed.
If a class declaration has a primary constructor and also includes a class supertype
specier, that specier must represent a valid invocation of the supertype
constructor.
If a class does not have a primary constructor, its secondary constructors must
delegate to either the superclass constructor via super(...) (if the superclass
is present in the supertype specier list) or to another secondary constructor via
this(...). If the only superclass is kotlin.Any, delegation is optional.
If a class does not have neither primary, nor secondary constructors, it is assumed
to implicitly have a default parameterless primary constructor. This also means
that, if a class declaration includes a class supertype specier, that specier must
represent a valid invocation of the supertype constructor.
Examples:
open class Base
creates a nested classier type — a classier type available under the path
PD ▷ ND. In all other aspects, nested classiers are equivalent to regular ones.
Inner classes are a special kind of nested classiers, which introduce types of
objects associated (linked) with other (parent) objects. An inner class declaration
ID nested in another classier declaration PD may reference an object of type
ID associated with it.
This association happens when instantiating an object of type ID, as its con-
structor may be invoked only when a receiver of type PD is available, and this
receiver becomes associated with the new instantiated object of type ID.
Inner classes cannot be declared in interface declarations, as interfaces cannot
be instantiated.
Inner classes cannot be declared in a statement scope, as such scope does not
have an object to associate the inner class with.
Inner classes cannot be declared in object declarations, as object declarations
also create a single named value of their type, which makes additional association
unnecessary.
Note: for information on how type parameters of parent and nested
/ inner classiers interoperate, we delegate you to the type system
section of the specication.
Note: unlike object declarations, in object literals only inner classes
are allowed, as types of object literals are anonymous, making their
nested classiers available only through explicit receiver, eectively
forcing them to be inner.
Examples:
interface Quz {
interface Bar
class Nested
// Error: no parent object to reference,
// as interfaces cannot be instantiated
// inner class Inner
}
class Foo {
interface Bar
class Nested
inner class Inner
}
object Single {
interface Bar
class Nested
96 CHAPTER 4. DECLARATIONS
fun foo() {
// Error: interfaces cannot be local
// interface Bar
class Nested
fun test() {
val fooV = Foo()
Quz.Nested()
Foo.Nested()
fooV.Inner()
Single.Nested()
anon.Inner()
}
Inheritance delegation
In a classier (an object or a class) declaration C, any supertype I inheritance
may be delegated to an arbitrary value v if:
• The supertype I is an interface type;
• v has type T such that T <: I.
The inheritance delegation uses a syntax similar to property delegation using
the by keyword, but is specied in the classier declaration header and is a very
dierent concept. If inherited using delegation, each method M of I (whether
they have a default implementation or not) is delegated to the corresponding
4.1. CLASSIFIER DECLARATION 97
is expanded to
mut = x1
val d1 = D() // d1 methods are delegated to x1
mut = x2
val d2 = D() // d2 methods are delegated to x2
// but d1 methods are still delegated to x1
Abstract classes
A class declaration can be marked abstract. Such classes cannot be instantiated
directly; they are used as superclasses for other classes or objects.
Abstract classes may contain one or more abstract members: members without
implementation, which should be implemented in a subtype of this abstract
class.
• It has a number of predened values that are declared in the class itself
(enum entries);
• No other values of this class can be constructed;
• It implicitly inherits the built-in class kotlin.Enum<E> (and cannot have
any other base classes);
• It is implicitly nal and cannot be inherited from;
• It cannot have type parameters of any kind;
• It has special syntax to accommodate for the properties described above.
Enum class body uses special kind of syntax (see grammar) to declare enum
entries in addition to all other declarations inside the class body. Enum entries
have their own bodies that may contain their own declarations, similar to object
declarations.
Note: an enum class can have zero enum entries. This makes objects
of this class impossible to construct.
dened to be the ordinal of the entry, e.g. the position of this entry in the
list of entries, starting with 0;
...
State.SOLID.name // "SOLID"
State.SOLID.ordinal // 1
State.GAS > State.LIQUID // true
State.SOLID.toString() // "SOLID"
104 CHAPTER 4. DECLARATIONS
State.valueOf("SOLID") // State.SOLID
State.valueOf("Foo") // throws exception
State.values() // arrayOf(State.LIQUID, State.SOLID, State.GAS)
...
// enum class can have additional declarations that may be overridden in its value
enum class Direction(val symbol: Char) {
UP('ˆ') {
override val opposite: Direction
get() = DOWN
},
DOWN('v') {
override val opposite: Direction
get() = UP
},
LEFT('<') {
override val opposite: Direction
get() = RIGHT
},
RIGHT('>') {
override val opposite: Direction
get() = LEFT
};
abstract val opposite: Direction
}
Note: annotation classes can have type parameters, but cannot use
them as types for their primary constructor parameters. Their main
use is for various annotation processing tools, which can access the
type arguments from the source code.
The main use of annotation classes is when specifying code annotations for other
entities. Additionally, annotation classes can be instantiated directly, for cases
when you require working with an annotation instance directly. For example,
this is needed for interoperability with some Java annotation APIs, as in Java
you can implement an annotation interface and then instantiate it.
Examples:
@Transmogrifiable
fun f(): Int = TODO()
@WithTypes(Super::class, Transmogrifiable::class)
val x = 4
106 CHAPTER 4. DECLARATIONS
fun interface FI {
fun bar(s: Int): Int
}
fun foo() {
doIt { it }
doIt { s: Int -> s + 42 }
doIt { s: Number -> s.toInt() }
doIt(l)
}
Example:
fun interface FI {
fun bar(s: Int): Int
}
fun foo() {
val fi = FI { it }
val fi2 = FI { s: Int -> s + 42 }
val fi3 = FI { s: Number -> s.toInt() }
val fi4 = FI({ it })
Note: in Kotlin version 1.3 and earlier, SAM conversion was not
available for Kotlin functional interfaces.
Examples:
interface I
init {
println("3: $this") /* (3) */
}
init {
println("5: $this") /* (5) */
}
init {
println("7: $this") /* (7) */
}
init {
println("9: $this") /* (9) */
}
fun main() {
Init(5)
// 2: Init(a=null, b='null', c=null, d=0.0)
// 3: Init(a=5, b='null', c=null, d=0.0)
// 5: Init(a=5, b='5', c=null, d=0.0)
// 7: Init(a=5, b='5', c=b is 5, d=0.0)
// 9: Init(a=5, b='5', c=b is 5, d=42.0)
// 10: Init(a=5, b='5', c=b is 5, d=42.0)
{NL}
simpleIdentier
{NL}
functionValueParameters
[{NL} ':' {NL} type]
[{NL} typeConstraints]
[{NL} functionBody]
functionBody:
block
| ('=' {NL} expression)
Function declarations assign names to functions — blocks of code which may be
called by passing them a number of arguments. Functions have special function
types which are covered in more detail here.
A simple function declaration consists of four main parts:
• Name f ;
• Parameter list (p1 : P1 [= v1 ], ▷ ▷ ▷ , pn : Pn [= vn ]);
• Return type R;
• Body b.
and has a function type f : (p1 : P1 , ▷ ▷ ▷ , pn : Pn ) → R.
Parameter list (p1 : P1 [= v1 ], ▷ ▷ ▷ , pn : Pn [= vn ]) describes function parameters,
i.e. inputs needed to execute the declared function. Each parameter pi : Pi = vi
introduces pi as a name of value with type Pi available inside function body b;
therefore, parameters are nal and cannot be changed inside the function. A
function may have zero or more parameters.
A parameter may include a default value vi , which is used if the corresponding
argument is not specied in function invocation; vi must be an expression which
evaluates to type V <: Pi .
Return type R, if omitted, is calculated as follows.
• If function body b is present in the expression form and it may be inferred
to have a valid type B : B ̸≡ kotlin.Nothing, R ≡ B.
• If function body b is present in the block form, R ≡ kotlin.Unit.
In other cases return type R cannot be omitted and must be specied explicitly.
As type kotlin.Nothing has a special meaning in Kotlin type system,
it must be specied explicitly, to avoid spurious kotlin.Nothing
function return types.
Function body b is optional; if it is omitted, a function declaration creates an
abstract function, which does not have an implementation. This is allowed only
inside an abstract class or an interface. If a function body b is present, it should
evaluate to type B which should satisfy B <: R.
114 CHAPTER 4. DECLARATIONS
class Bar {
fun foo() { println(this) } // this has type Bar
fun Int.foo() { println(this) } // this has type Int
}
4.2.5 Inlining
A function may be declared inline using a special inline modier. This allows
the compiler to inline the function at call-site, replacing the call with the body
of the function with arguments mapped to corresponding parameters. It is
unspecied whether inlining will actually be performed, however.
Declaring a function inline has two additional eects:
• It allows type parameters of the function to be declared reified, making
them runtime-available and allowing usage of specic expressions involving
these parameters, such as type checks and class literals. Calling such a
118 CHAPTER 4. DECLARATIONS
println(bar()) // 2
x = 42
println(bar()) // 42
}
Examples:
the property is created. Properties that are not allowed to have backing elds
(see getters and setters section for details) are also not allowed to have initializer
expressions.
Note: although a property with an initializer expression looks similar
to an assignment, it is dierent in several key ways: rst, a read-only
property cannot be assigned, but may have an initializer expression;
second, the initializer expression never invokes the property setter,
but assigns the property backing eld value directly.
view these properties as regular properties with special delegating getters and
setters:
var x: T by e
is the same as
val x$delegate = e
var x: T
get(): T = x$delegate.getValue(thisRef, ::x)
set(value: T) { x$delegate.setValue(thisRef, ::x, value) }
Read access is handled the same way as for a delegated read-only property. Any
write access to x (using, for example, an assignment operator x = y) becomes
an overloadable form with the following expansion:
e.setValue(thisRef, property, y)
where
• e is the delegating entity; the compiler needs to make sure that this is
accessible in any place x is accessible;
• setValue is a suitable operator function available on e;
• thisRef is the receiver object for the property. This argument is null for
local properties;
• property is an object of the type kotlin.KProperty<*> that contains
information relevant to x (for example, its name, see standard library
documentation for details);
• y is the value x is assigned to. In case of complex assignments (see the
assignment section), as they are all overloadable forms, rst the assignment
expansion is performed, and after that, the expansion of the delegated
property using normal assignment.
The type of a delegated property may be omitted at the declaration site, meaning
that it may be inferred from the delegating function itself, as it is with regular
getters and setters. If this type is omitted, it is inferred as if it was assigned the
value of its expansion. If this inference fails, it is a compile-time error.
If the delegate expression has a suitable operator function called provideDelegate,
a provided delegate is used instead. The provided delegate is accessed using the
following expansion:
val x: T by e
is the same as
val x$delegate = e.provideDelegate(thisRef, ::x)
val x: T
get(): T = x$delegate.getValue(thisRef, ::x)
and
var x: T by e
126 CHAPTER 4. DECLARATIONS
is the same as
val x$delegate = e.provideDelegate(thisRef, ::x)
val x: T
get(): T = x$delegate.getValue(thisRef, ::x)
set(value) { x$delegate.setValue(thisRef, ::x, value) }
where provideDelegate is a suitable operator function available using the re-
ceiver e, while getValue and setValue work the same way they do with normal
property delegation. As is the case withsetValue and getValue, thisRef is a ref-
erence to the receiver of the property or null for local properties, but there is also
a special case: for extension properties thisRef supplied to provideDelegate is
null, while thisRef provided to getValue and setValue is the actual receiver.
This is due to the fact that, during the creation of the property, no receiver is
available.
For both provided and standard delegates, the generated delegate value is placed
in the same context as its corresponding property. This means that for a class
member property it will be a synthetic member, for a local property it is a local
value in the same scope as the property and for top-level (both extension and
non-extension) properties it will be a top-level value. This aects this value’s
lifetime in the same way normal value lifetime works.
Example:
operator fun <V, R : V> Map<in String, V>.getValue(
thisRef: Any?, property: KProperty<*>): R =
getOrElse(property.name) {
throw NoSuchElementException()
} as R
fun main() {
handleConfig(mutableMapOf(
"parent" to "",
"host" to "https://kotlinlang.org/"
))
}
Example with provideDelegate:
operator fun <V> MutableMap<in String, V>.provideDelegate(
thisRef: Any?,
property: KProperty<*>): MutableMap<in String, V> =
if (containsKey(property.name)) this
else throw NoSuchElementException()
...
}
fun main() {
handleConfig(mutableMapOf(
"parent" to "",
"host" to "https://kotlinlang.org/"
))
}
the property called the receiver parameter. This is dierent from usual property
declarations, that do not have any parameters. There are other dierences from
standard property declarations:
Aside from these dierences, extension properties are similar to regular properties,
but, when accessing such a property one always need to supply a receiver , implicit
or explicit. Like for regular properties, the type of the receiver must be a subtype
of the receiver parameter, and the value that is supplied as the receiver is bound
to the receiver parameter. For more information on how a particular receiver for
each access is chosen, please refer to the overloading section.
The receiver parameter can be accessed inside getter and setter scopes of the
property as the implicit receiver or this. It may also be accessed inside nested
scopes using labeled this syntax using the name of the property declared as
the label. For delegated properties, the value passed into the operator functions
getValue and setValue as the receiver is the value of the receiver parameter,
rather than the value of the outer classier. This is also true for local extension
properties: while regular local properties are passed null as the rst argument
of these operator functions, local extension properties are passed the value of
the receiver argument instead.
For all other purposes, extension properties are not dierent from non-extension
properties.
Examples:
class Bar {
val foo get() = this // returns type Bar
val Int.foo get() = this // returns type Int
}
4.3. PROPERTY DECLARATION 129
At the moment, Kotlin supports only top-level type aliases. The scope where it
is accessible is dened by its visibility modiers.
Examples:
// simple typealias declaration
typealias IntList = List<Int>
// parameterized type alias declaration
// T has out variance implicitly
typealias IntMap<T> = Map<Int, T>
// type parameter may be unreferenced
typealias Strange<T> = String
interface MyRunnable<T> {
fun execute(): T
}
fun main() {
val s = run<StringRunnable, _ /* inferred to String */ >()
assert(s == "test")
fun bar() {
val a: Boolean = foo(true)
// resolves to (1)
// per overload resolution rules
Inheritance
137
138 CHAPTER 5. INHERITANCE
• B and D match;
• The classier of B (where it is declared) is a supertype of the classier of
D.
The notions of matching and subsumption are used when talking about how
declarations are inherited and overridden.
5.3 Inheriting
A callable declaration (that is, a property or member function declaration) inside
a classier declaration is said to be inheritable if:
• Its visibility (and the visibility of its getter and setter, if present) is not
private.
If the declaration B of the base classier type is inheritable, no other inheritable
declaration from the base classier types subsume B, no declarations in the
derived classier type override B, then B is inherited by the derived classier
type.
As Kotlin is a language with single inheritance (only one supertype can be a
class, any number of supertypes can be an interface), there are several additional
rules which rene how declarations are inherited.
• If a derived class type inherits a declaration from its superclass, no other
matching abstract declarations from its superinterfaces are inherited.
• If a derived classier type inherits several matching concrete declarations
from its supertypes, it is a compile-time error (this means a derived classier
type should override such declarations).
• If a derived concrete classier type inherits an abstract declaration from
its supertypes, it is a compile-time error (this means a derived classier
type should override such declaration).
• If a derived classier type inherits both an abstract and a concrete dec-
laration from its superinterfaces, it is a compile-time error (this means a
derived classier type should override such declarations).
5.4 Overriding
A callable declaration (that is, a property or member function declaration) inside
a classier declaration is said to be overridable if:
• Its visibility (and the visibility of its getter and setter, if present) is not
private;
• It is declared as open, abstract or override (interface methods and
properties are implicitly abstract if they don’t have a body or open if
they do).
140 CHAPTER 5. INHERITANCE
open class P {
5.4. OVERRIDING 141
class Q : P() {
protected open override fun g() {}
// this is an error, as protected visibility is
// stronger that public visibility
// from the base declaration
}
Important: platforms may introduce additional cases of both overrid-
ability and subsumption of declarations, as well as limit the overriding
mechanism due to implementation limitations.
Note: Kotlin does not have a concept of full hiding (or shadowing)
of declarations.
Note: if a declaration binds a new function to the same name as was
introduced in the base class, but which does not subsume it, it is
neither a compile-time error nor an overriding declaration. In this
case these two declarations follow the normal rules of overloading.
However, these declarations may still result in a compile-time error
as a result of conicting overload detection.
142 CHAPTER 5. INHERITANCE
Chapter 6
143
144 CHAPTER 6. SCOPES AND IDENTIFIERS
fun foo() = x + 2
val x = 3
6.3 Labels
Labels are special syntactic marks which allow one to reference certain code
fragments or elements. Lambda expressions and loop statements are allowed to
be labeled, with label identier associated with the corresponding entity.
Note: in Kotlin version 1.3 and earlier, labels were allowed to be
placed on any expression or statement.
Labels can be redeclared, meaning that the same label identier may be reused
with dierent parts of code (or even on the same expression/loop) several times.
Labels are scoped, meaning that they are only available in the scope they were
declared in.
Labels are used by certain expressions, such as break, continue and return,
to specify exactly what entity the expression corresponds to. Please refer to the
corresponding sections for details.
When resolving labels (determining which label an expression refers to), the
closest label with the matching identier is chosen, i.e., a label in an innermost
scope syntactically closest to the point of its use.
Chapter 7
Statements
statements:
[statement {semis statement}] [semis]
statement:
{label | annotation} (declaration | assignment | loopStatement | expression)
Kotlin does not explicitly distinguish between statements, expressions and dec-
larations, i.e., expressions and declarations can be used in statement positions.
This section focuses only on those statements that are not expressions or decla-
rations. For information on those parts of Kotlin, please refer to the Expressions
and Declarations sections of the specication.
7.1 Assignments
assignment:
((directlyAssignableExpression '=') | (assignableExpression assignmentAn-
dOperator)) {NL} expression
assignmentAndOperator:
'+='
| '-='
| '*='
| '/='
| '%='
147
148 CHAPTER 7. STATEMENTS
An assignment is a statement that writes a new value to some program entity, de-
noted by its left-hand side. Both left-hand and right-hand sides of an assignment
must be expressions, more so, there are several restrictions for the expression on
the left-hand side.
For an expression to be assignable, i.e. be allowed to occur on the left-hand side
of an assignment, it must be one of the following:
• An identier referring to a mutable property;
• A navigation expression referring to a mutable property. If this navigation
operator is the safe navigation operator, this introduces a special case of
safe assignment;
• An indexing expression.
Note: Kotlin assignments are not expressions and cannot be used
as such.
when(val $tmp = a) {
null -> null
else -> { $tmp.c }
}
For any right-hand combinations of operators present in c, which are
expanded further, as usual.
Example: The assignment
x?.y[0] = z
is expanded to
when(val $tmp = x) {
null -> null
else -> { $tmp.y[0] = z }
}
which, according to expansion rules for indexing assignments is, in
turn, expanded to
when(val $tmp = x) {
null -> null
else -> { $tmp.y.set(0, z) }
}
{NL}
(controlStructureBody | ';')
A while-loop statement is similar to an if expression in that it also has a condition
expression and a body consisting of zero or more statements. While-loop
statement evaluating its body repeatedly for as long as its condition expression
evaluates to true or a jump expression is evaluated to nish the loop.
Note: this also means that the condition expression is evaluated
before every evaluation of the body, including the rst one.
The while-loop condition expression must be a subtype of kotlin.Boolean.
Note: unlike most other languages, Kotlin does not have a free-form
condition-based for loops. The only form of a for-loop available in
Kotlin is the “foreach” loop, which iterates over lists, arrays and
other data structures.
A for-loop statement is a special kind of loop statement used to iterate over some
data structure viewed as an iterable collection of elements. A for-loop statement
consists of a loop body, a container expression and an iteration variable
declaration.
The for-loop is actually an overloadable syntax form with the following expansion:
for(VarDecl in C) Body is the same as
when(val $iterator = C.iterator()) {
else -> while ($iterator.hasNext()) {
val VarDecl = __iterator.next()
<... all the statements from Body>
}
}
where iterator, hasNext, next are all suitable operator functions available in
the current scope. VarDecl here may be a variable name or a set of variable
names as per destructuring variable declarations.
Note: the expansion is hygienic, i.e., the generated iterator variable
never clashes with any other variable in the program and cannot be
accessed outside the expansion.
expression if it does not contain any statements or its last statement is not an
expression (e.g., it is an assignment, a loop or a declaration).
Informally: you may consider the case of a missing last expression
as if a synthetic last expression with no runtime semantics and type
kotlin.Unit is introduced in its place.
A control structure body is either a single statement or a code block. A last
expression of a control structure body CSB is either the last expression of a
code block (if CSB is a code block) or the single expression itself (if CSB is an
expression). If a control structure body is not a code block or an expression, it
has no last expression.
Note: this is equivalent to wrapping the single expression in a new
synthetic code block.
In some contexts, a control structure body is expected to have a value and/or a
type. The value of a control structure body is:
• the value of its last expression if it exists;
• the singleton kotlin.Unit object otherwise.
The type of a control structure body is the type of its value.
Expressions
Glossary
CSB
Control structure body
Introduction
Expressions (together with statements) are one of the main building blocks of
any program, as they represent ways to compute program values or control the
program execution ow.
In Kotlin, an expression may be used as a statement or used as an expression
depending on the context. As all expressions are valid statements, standalone
expressions may be used as single statements or inside code blocks.
An expression is used as an expression, if it is encountered in any position where
a statement is not allowed, for example, as an operand to an operator or as an
immediate argument for a function call. An expression is used as a statement if
it is encountered in any position where a statement is allowed.
Some expressions are allowed to be used as statements, only if certain restrictions
are met; this may aect the semantics, the compile-time type information or/and
the safety of these expressions.
155
156 CHAPTER 8. EXPRESSIONS
Keywords true and false denote boolean literals of the same values. These are
strong keywords which cannot be used as identiers unless escaped. Values true
and false always have the type kotlin.Boolean.
HexLiteral
'0' ('x' | 'X') HexDigit {HexDigitOrSeparator } HexDigit
| '0' ('x' | 'X') HexDigit
BinLiteral
'0' ('b' | 'B') BinDigit {BinDigitOrSeparator } BinDigit
| '0' ('b' | 'B') BinDigit
DecDigitNoZero:
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
DecDigitOrSeparator:
DecDigit | '_'
HexDigitOrSeparator:
HexDigit | '_'
BinDigitOrSeparator
BinDigit | '_'
DecDigits:
DecDigit {DecDigitOrSeparator } DecDigit
| DecDigit
Note: unlike other languages, Kotlin does not support octal literals.
Even more so, any decimal literal starting with digit 0 and containing
more than 1 digit is not a valid decimal literal.
8.1. CONSTANT LITERALS 157
other languages, Kotlin real literals may only be expressed in decimal numbers.
A real literal may also be followed by a type sux (f or F).
The exponent is an exponent mark (e or E) followed by an optionally signed
decimal integer (a sequence of decimal digits).
The whole-number part and the exponent part may be omitted. The fraction
part may be omitted only together with the decimal point, if the whole-number
part and either the exponent part or the type sux are present. Unlike other
languages, Kotlin does not support omitting the fraction part, but leaving the
decimal point in.
The digits of the whole-number part or the fraction part or the exponent may
be optionally separated by underscores, but an underscore may not be placed
between, before, or after these parts. It also may not be placed before or after
the exponent mark symbol.
A real literal without the type sux has type kotlin.Double, a real literal with
the type sux has type kotlin.Float.
Note: this means there is no special sux associated with type
kotlin.Double.
Escaped characters
A character literal may also contain an escaped symbol of two kinds: a simple
escaped symbol or a Unicode codepoint. Simple escaped symbols include:
• \t — the Unicode TAB symbol (U+0009);
8.2. CONSTANT EXPRESSIONS 159
multiLineStringLiteral:
'"""' {multiLineStringContent | multiLineStringExpression | '"'}
TRIPLE_QUOTE_CLOSE
lineStringContent:
LineStrText
| LineStrEscapedChar
| LineStrRef
lineStringExpression:
'${'
{NL}
expression
{NL}
'}'
multiLineStringContent:
MultiLineStrText
| '"'
| MultiLineStrRef
multiLineStringExpression:
'${'
{NL}
expression
{NL}
'}'
String interpolation expressions replace the traditional string literals and super-
sede them. A string interpolation expression consists of one or more fragments
of two dierent kinds: string content fragments (raw pieces of string content
inside the quoted literal) and interpolated expression fragments, specied by a
special syntax using the $ symbol.
Interpolated expressions support two dierent forms.
• $id, where id is a simple path available in the current scope;
• ${e}, where e is a valid Kotlin expression.
Note: the rst form requires id to be a simple path; if you want to
reference a qualied path (e.g., foo.bar), you should use the second
form as ${foo.bar}.
In either case, the interpolated value is evaluated and converted into
kotlin.String by a process dened below. The resulting value of a string
interpolation expression is the concatenation of all fragments in the expression.
An interpolated value v is converted to kotlin.String according to the following
convention:
• If it is equal to the null reference, the result is "null";
8.4. TRY-EXPRESSIONS 161
8.4 Try-expressions
tryExpression:
'try' {NL} block ((({NL} catchBlock {{NL} catchBlock}) [{NL} nally-
Block]) | ({NL} nallyBlock))
catchBlock:
'catch'
{NL}
'('
{annotation}
simpleIdentier
':'
type
162 CHAPTER 8. EXPRESSIONS
[{NL} ',']
')'
{NL}
block
nallyBlock:
'finally' {NL} block
A try-expression is an expression starting with the keyword try. It consists of a
code block (try body) and one or more of the following kinds of blocks: zero or
more catch blocks and an optional nally block. A catch block starts with the soft
keyword catch with a single exception parameter, which is followed by a code
block. A nally block starts with the soft keyword finally, which is followed
by a code block. A valid try-expression must have at least one catch or nally
block.
The try-expression evaluation evaluates its body; if any statement in the try body
throws an exception (of type E), this exception, rather than being immediately
propagated up the call stack, is checked for a matching catch block. If a catch
block of this try-expression has an exception parameter of type T :> E, this
catch block is evaluated immediately after the exception is thrown and the
exception itself is passed inside the catch block as the corresponding parameter.
If there are several catch blocks which match the exception type, the rst one is
picked.
For an in-detail explanation on how exceptions and catch-blocks work, please
refer to the Exceptions section. For a low-level explanation, please refer to the
platform-specic parts of this document.
If there is a nally block, it is evaluated after the evaluation of all previous
try-expression blocks, meaning:
• If no exception is thrown during the evaluation of the try body, no catch
blocks are executed, the nally block is evaluated after the try body, and
the program execution continues as normal.
• If an exception was thrown, and one of the catch blocks matched its type,
the nally block is evaluated after the evaluation of the matching catch
block.
• If an exception was thrown, but no catch block matched its type, the nally
block is evaluated before propagating the exception up the call stack.
The value of the try-expression is the same as the value of the last expression of
the try body (if no exception was thrown) or the value of the last expression of
the matching catch block (if an exception was thrown and matched). All other
situations mean that an exception is going to be propagated up the call stack,
and the value of the try-expression is undened.
Note: as described, the nally block (if present) is always executed,
but has no eect on the value of the try-expression.
8.5. CONDITIONAL EXPRESSIONS 163
The type of the try-expression is the least upper bound of the types of the last
expressions of the try body and the last expressions of all the catch blocks.
Note: these rules mean the try-expression always may be used as an
expression, as it always has a corresponding result value.
The type of the resulting when expression is the least upper bound of the types
of all its entries. If when expression is not exhaustive, it has type kotlin.Unit
and may be used only as a statement.
Examples:
val a = 42
val b = -1
when {
a == b -> {}
a != b -> {}
}
val d = when {
a == b -> {}
a != b -> {}
else -> {}
}
when {
a == b || a != b -> {}
42 > 0 -> {}
}
val a = 42
val b = -1
val l = (1..10).toList()
when (a) {
is Int, !is Int -> {}
in l, !in l -> {}
}
– The bound expression is of an enum class type and all its enumerated
values are checked for equality using constant expression;
– The bound expression is of a nullable type T ? and one of the cases
above is met for its non-nullable counterpart T together with another
condition which checks the bound value for equality with null.
For object types, the type test condition may be replaced with equality check
with the object value.
Note: if one were to override equals for an object type incorrectly
(i.e., so that an object is not equal to itself), it would break the
exhaustiveness check. It is unspecied whether this situation leads
to an exception or an undened value for this when expression.
sealed class Base
class Derived1: Base()
object Derived2: Base()
val c = when(b) {
is Derived1 -> ...
Derived2 -> ...
// no else needed here
}
sealed interface I1
sealed interface I2
sealed interface I3
class D1 : I1, I2
class D2 : I1, I3
fun foo() {
val b: I1 = mk()
val c = when(a) {
!is I3 -> {} // covers D1
is D2 -> {} // covers D2
// D3 is sealed and does not take part
// in the exhaustiveness check
}
}
Informally: an exhaustive when expression is guaranteed to evaluate
one of its CSBs regardless of the specic when conditions.
8.7. LOGICAL DISJUNCTION EXPRESSIONS 169
they represent the same runtime value. In particular, this means that two values
acquired by the same constructor call are equal by reference, while two values
created by two dierent constructor calls are not equal by reference. A value
created by any constructor call is never equal by reference to a null reference.
There is an exception to these rules: values of value classes are not guaranteed
to be reference equal even if they are created by the same constructor invocation
as said constructor invocation is explicitly allowed to be inlined by the compiler.
It is thus highly discouraged to compare value classes by reference.
For special values created without explicit constructor calls, notably, constant
literals and constant expressions composed of those literals, and for values of
value classes, the following holds:
• If these values are non-equal by value, they are also non-equal by reference;
• Any instance of the null reference null is equal by reference to any other
instance of the null reference;
• Otherwise, equality by reference is implementation-dened and should not
be used as a means of comparing such values.
Reference equality expressions always have type kotlin.Boolean.
Kotlin checks the applicability of reference equality operators at compile-time
and may reject certain combinations of types for A and B. Specically, it uses
the following basic principle.
If type of A and type of B are denitely distinct and not related by
subtyping, A === B is an invalid expression and should result in a
compile-time error.
Informally: this principle means “no two objects of dierent types
can be equal by reference”.
Value equality expressions always have type kotlin.Boolean as does the equals
method in kotlin.Any.
| NOT_IN
isOperator:
'is'
| NOT_IS
Note: this means that, contrary to the order of appearance in the code,
the right-hand side expression of a containment-checking expression
is evaluated before its left-hand side expression
This operator is lazy, meaning that if the left-hand side expression is not
reference equal to null, the right-hand side expression is not evaluated.
The type of elvis operator expression is the least upper bound of the non-nullable
variant of the type of the left-hand side expression and the type of the right-hand
side expression.
8.13. RANGE EXPRESSIONS 175
| '/'
| '%'
A multiplicative expression is a binary expression which uses a multiplication
(*), division (/) or remainder (%) operators. These are overloadable operators
with the following expansions:
• A * B is exactly the same as A.times(B)
• A / B is exactly the same as A.div(B)
• A % B is exactly the same as A.rem(B)
where times, div, rem is a valid operator function available in the current scope.
Note: in Kotlin version 1.3 and earlier, there was an additional
overloadable operator for % called mod, which has been removed in
Kotlin 1.4.
The return type of these functions is not restricted. A multiplicative expression
has the same type as the return type of the corresponding operator function
overload variant.
postxUnarySux:
postxUnaryOperator
| typeArguments
| callSux
| indexingSux
| navigationSux
postxUnaryOperator:
'++'
| '--'
| ('!' excl)
As the result of inc is assigned to A, the return type of inc must be a subtype
of A. Otherwise, such declaration is a compile-time error.
A postx increment expression has the same type as its operand expression (for
our examples, the type of A).
180 CHAPTER 8. EXPRESSIONS
indexingSux:
'['
{NL}
expression
{{NL} ',' {NL} expression}
[{NL} ',']
{NL}
']'
An indexing expression is a sux expression which uses one or more subexpres-
sions as indices between square brackets ([ and ]).
It is an overloadable operator with the following expansion:
• A[I_0,I_1,...,I_N] is exactly the same as A.get(I_0,I_1,...,I_N),
where get is a valid operator function available in the current scope.
An indexing expression has the same type as the corresponding get expression.
Indexing expressions are assignable, for a corresponding assignment form, see
here.
{NL}
typeProjection
{{NL} ',' {NL} typeProjection}
[{NL} ',']
{NL}
'>'
typeProjection:
([typeProjectionModiers] type)
| '*'
typeProjectionModiers:
typeProjectionModier {typeProjectionModier}
memberAccessOperator:
({NL} '.')
| ({NL} safeNav)
| '::'
companion object {
val bProp: Int = 42
object O {
val a: Int = 42
fun main() {
// Error: ambiguity between two possible callables
// val errorAmbiguity = A::a
are not used (i.e., for which the call-site provides explicit arguments)
are not evaluated at such call-sites.
Examples: we use a notation similar to the control-ow section to
illustrate the evaluation order.
fun f(x: Int = h(), y: Int = g())
...
f() // $1 = h(); $2 = g(); $result = f($1, $2)
f(m(), n()) // $1 = m(); $2 = n(); $result = f($1, $2)
f(y = n(), x = m()) // $1 = n(); $2 = m(); $result = f($2, $1)
f(y = n()) // $1 = n(); $2 = h(); $result = f($2, $1)
fun f(x: Int = h(), y: () -> Int)
...
f(y = {2}) // $1 = {2}; $2 = h(); $result = f($2, $1)
f { 2 } // $1 = {2}; $2 = h(); $result = f($2, $1)
f(m()) { 2 } // $1 = m(); $2 = {2}; $result = f($1, $2)
Operator calls work in a similar way: every operator evaluates in the same order
as its expansion does, unless specied otherwise.
Note: this means that the containment-checking operators are eec-
tively evaluated right-to-left w.r.t. their expansion.
// is equivalent to
foo("a", "b", "c", "d", "e", "f", "g")
Spread operator expressions are not allowed in any other context. See Variable
length parameter section for details.
The type of a spread argument must be a subtype of ATS(kotlin.Array(out T ))
for a variable length parameter of type T .
Example: for parameter vararg a: Int the type of a corresponding
spread argument must be a subtype of IntArray, for parameter
vararg b: T where T is a classier type the type of a corresponding
spread argument must be a subtype of Array<out T>.
}
val plus: (Pair<Int, Double>) -> String = { p ->
val i = p.component1()
val d = p.component2()
"$i + $d = ${i + d}"
}
If a lambda expression has no parameter list, it can be dening a function with
either zero or one parameter, the exact case dependent on the use context of
this lambda. The selection of number of parameters in this case is performed
during type inference.
If a lambda expression has no explicit parameter list, but does have one parameter,
this parameter can be accessed inside the lambda body using a special property
called it.
Note: having no explicit parameter list (no arrow operator) in a
lambda is dierent from having zero parameters (nothing preceding
the arrow operator).
Any lambda may dene either a normal function or an extension function, the
exact case dependent on the use context of the lambda. If a lambda expression
denes an extension function, its extension receiver may be accessed using the
standard this syntax inside the lambda body.
Lambda literals are dierent from other forms of function declarations in that
non-labeled return expressions inside lambda body refer to the outer non-lambda
function the expression is used in rather than the lambda expression itself. Such
non-labeled returns are only allowed if the lambda and all its parent lambdas (if
present) are guaranteed to be inlined, otherwise it is a compile-time error.
If a lambda expression is labeled, it can be returned from using a labeled return
expression.
If a non-labeled lambda expression is used as a parameter to a function call,
the name of the function called may be used as a label.
If a labeled return expression is used when there are several matching labels
available (e.g., inside several nested function calls with the same name), this is
resolved as return to the nearest matching label.
Example:
// kotlin.run is a standard library inline function
// receiving a lambda parameter
}
}
}
Any properties used inside the lambda body are captured by the lambda
expression and, depending on whether it is inlined or not, aect how these
192 CHAPTER 8. EXPRESSIONS
properties are processed by other mechanisms, e.g. smart casts. See corresponding
sections for details.
class M {
8.24. THIS-EXPRESSIONS 193
fun main() {
M().test1() // OK
M().test2().bar() // Error: Unresolved reference: bar
}
8.24 This-expressions
thisExpression:
'this'
| THIS_AT
This-expressions are special kind of expressions used to access receivers available
194 CHAPTER 8. EXPRESSIONS
in the current scope. The basic form of this expression, denoted by a non-
labeled this keyword, is used to access the default implicit receiver according
to the receiver priority. In order to access other implicit receivers, labeled this
expressions are used. These may be any of the following:
• this@type, where type is a name of any classier currently being declared
(that is, this-expression is located in the inner scope of the classier
declaration), refers to the implicit object of the type being declared;
• this@function, where function is a name of any extension function
currently being declared (that is, this-expression is located in the function
body), refers to the implicit receiver object of the extension function;
• this@lambda, where lambda is a label provided for a lambda literal cur-
rently being declared (that is, this-expression is located in the lambda
expression body), refers to the implicit receiver object of the lambda
expression;
• this@outerFunction, where outerFunction is the name of a function
which takes lambda literal currently being declared as an immediate argu-
ment (that is, this-expression is located in the lambda expression body),
refers to the implicit receiver object of the lambda expression.
Note: this@outerFunction notation is mutually exclusive with
this@lambda notation, meaning if a lambda literal is labeled
this@outerFunction cannot be used.
Note: this@outerFunction and this@label notations can be
used only in lambda literals which have an extension function
type, i.e., have an implicit receiver.
Important: any other forms of this-expression are illegal and
should result in a compile-time error.
In case there are several entities with the same label, labeled this refers to the
closest label.
Example:
interface B
object C
8.25 Super-forms
superExpression:
('super' ['<' {NL} type {NL} '>'] [AT_NO_WS simpleIdentier])
| SUPER_AT
Super-forms are special kind of expression which can only be used as receivers
in a call or property access expression. Any use of super-form expression in any
other context is a compile-time error.
Super-forms are used in classier declarations to access implementations from
the immediate supertypes without invoking overriding behaviour.
If an implementation is not available (e.g., one attempts to access an abstract
method of a supertype in this fashion), this is a compile-time error.
The basic form of this expression, denoted by super keyword, is used to access
the immediate supertype of the currently declared classier selected as a part
of overload resolution. In order to access a specic supertype implementations,
extended super expressions are used. These may be any of the following:
• super<Klazz>, where Klazz is a name of one of the immediate super-
types of the currently declared classier, refers to that supertype and its
implementations;
• super<Klazz>@type, where type is a name of any currently declared
classier and Klazz is a name of one of the immediate supertypes of the
type classier, refers to that supertype and its implementations.
Note: super<Klazz>@type notation can be used only in inner
classes, as only inner classes can have access to supertypes of
other classes, i.e., supertypes of their parent class.
Example:
interface A {
196 CHAPTER 8. EXPRESSIONS
open class C : A {
override fun foo() { println("C") }
}
class E : C() {
init {
super.foo() // "C"
super<C>.foo() // "C"
}
}
class D : C(), A, B {
init {
// Error: ambiguity as several immediate supertypes
// with callable `foo` are available here
// super.foo()
super<C>.foo() // "C"
super<B>.foo() // "B"
// Error: A is *not* an immediate supertype,
// as C inherits from A and is considered
// to be "more immediate"
// super<A>.foo()
}
Jump expressions are expressions which redirect the evaluation of the program to
a dierent program point. All these expressions have several things in common:
• They all have type kotlin.Nothing, meaning that they never produce
any runtime value;
• Any code which follows such expressions is never evaluated.
There are two forms of return expression: a simple return expression, specied
using the non-labeled return keyword, which returns from the innermost function
declaration (or anonymous function declaration), and a labeled return expression
of the form return@Context which works as follows.
Operator overloading
Some syntax forms in Kotlin are dened by convention, meaning that their
semantics are dened through syntactic expansion of one syntax form into
another syntax form.
Particular cases of denition by convention include:
• Arithmetic and comparison operators;
• invoke convention;
• Operator-form assignments;
• For-loop statements;
• Delegated properties;
• Destructuring declarations.
Important: another case of denition by convention is safe navigation,
which is covered in more detail in its respective section.
There are several points shared among all the syntax forms dened using denition
by convention:
• The expansions are hygienic: if they introduce new identiers that were not
present in original syntax, all such identiers are not accessible outside the
expansion and cannot clash with any other declarations in the program;
• The expressions captured by an expansion are using call-by-need evaluation
strategy, meaning that they are evaluated only once during rst usage
specied in the expansion even if the expansion itself has more than one
usage of such an expression;
• An expansion may lead to another expansion, following the same rules;
• All call expressions that are produced by expansion are only allowed to
use operator functions.
This expansion of a particular syntax form to a dierent piece of code is usually
dened in the terms of operator functions.
199
200 CHAPTER 9. OPERATOR OVERLOADING
Operator functions are function which are declared with a special keyword
operator and are not dierent from regular functions when called via function
calls. However, operator functions can also be used in denition by convention.
Note: it is not important whether an operator function is a member
or an extension, nor whether it is suspending or not. The only
requirements are the ones listed in the respected sections.
For example, for an operator form a + b where a is of type A and b
is of type B any of the following function denitions are applicable:
class A {
// member function
operator fun plus(b: B) = ...
// suspending member function
suspend operator fun plus(b: B) = ...
}
// extension function
operator fun A.plus(b: B) = ...
// suspending extension function
suspend operator fun A.plus(b: B) = ...
Assuming additional implicit receiver of this type is available, it may
also be an extension dened in another type:
object Ctx {
// extension that is a member of some context type
operator fun A.plus(b: B) = ...
fun add(a: A, b: B) = a + b
}
Note: dierent platforms may add additional criteria on whether a
function may be considered a suitable candidate for operator conven-
tion.
The details of individual expansions are available in the sections of their respective
operators, here we would like to describe how they interoperate.
For example, take the following declarations:
class A {
operator fun inc(): A { ... }
}
object B {
operator fun get(i: Int): A { ... }
operator fun set(i: Int, value: A) { ... }
}
9.1. DESTRUCTURING DECLARATIONS 201
object C {
operator fun get(i: Int): B { ... }
}
The expression C[0][0]++ is expanded (see the Expressions section for details)
using the following rules:
• The operations are expanded in order of their priority.
• First, the increment operator is expanded, resulting in:
C[0][0] = C[0][0].inc()
• Second, the assignment to an indexing expression (produced by the previous
expansion) is expanded, resulting in:
C[0].set(C[0][0].inc())
• Third, the indexing expressions are expanded, resulting in:
C.get(0).set(C.get(0).get(0).inc())
Important: although the resulting expression contains several in-
stances of the subexpression C.get(0), as all these instances were
created from the same original syntax form, the subexpression is
evaluated only once, making this code roughly equivalent to:
val $tmp = C.get(0)
$tmp.set($tmp.get(0).inc())
TM which is used in type inference as any property type would. Note that an
ignoring placeholder may also be provided with a type signature, in which case
although the call to corresponding componentM function is not performed, it still
must be checked for function applicability during type inference.
Examples:
val (x: A, _, z) = f()
is expanded to
val $tmp = f()
val x: A = $tmp.component1()
val z = $tmp.component3()
where component1 and component3 are suitable operator functions
available on the value returned by f()
for((x: A, _, z) in f()) { ... }
is expanded to (as per for-loop expansion)
when(val $iterator = f().iterator()) {
else -> while ($iterator.hasNext()) {
val $tmp = $iterator.next()
val x: A = $tmp.component1()
val z = $tmp.component3()
...
}
}
where iterator(), next(), hasNext(), component1() and
component3 are all suitable operator functions available on their
respective receivers.
foo { (x: A, _, z) -> ... }
is expanded to
foo { $tmp ->
val x: A = $tmp.component1()
val z = $tmp.component3()
...
}
where component1() and component3 are all suitable operator func-
tions available on the value of lambda argument.
Chapter 10
10.1 Importing
Program entities declared in one package may be freely used in any le in
the same package with the only two restrictions being module boundaries and
visibility constraints. In order to use an entity from a le belonging to a dierent
package, the programmer must use import directives.
203
204 CHAPTER 10. PACKAGES AND IMPORTS
importList:
{importHeader}
importHeader:
'import' identier [('.' '*') | importAlias] [semi]
importAlias:
'as' simpleIdentier
An import directive contains a simple or a qualied path, with the name of an
imported entity as its last component. A path may include not only a package,
but also an object or a type, in which case it refers to the companion object of
that type. The last component may reference any named declaration within that
scope (that is, top-level scope of all les in the package or an object declaration
scope) may be imported using their names.
There are two special kinds of imports: star-imports ending in an asterisk (*)
and renaming imports employing the as operator.
Star-imports import all named entities inside the corresponding scope, but have
lesser priority during overload resolution of functions and properties.
Renaming imports work just like regular imports, but introduce the entity into
the current le with the specied name, such that an unqualied access to this
entity is possible only using the newly specied name. This means that renaming
imports of entities from the same package eectively change their unqualied
name.
Example:
package foo
fun test() {
// Qualified access is unchanged by the renaming import
foo.foo() // resolves to (1)
foo.bar() // resolved to (2)
Imports are local to their les, meaning if an entity is introduced into le A.kt
from package foo.bar, it does not introduce that entity to any other le from
package foo.bar.
There are some packages which have all their entities implicitly imported into any
Kotlin le, meaning one can access such entity without explicitly using import
directives.
Note: one may, however, import these entities explicitly if they choose
to do so.
The following packages of the standard library are implicitly imported:
• kotlin
• kotlin.annotation
• kotlin.collections
• kotlin.comparisons
• kotlin.io
• kotlin.ranges
• kotlin.sequences
• kotlin.text
• kotlin.math
Note: platform implementations may introduce additional implic-
itly imported packages, for example, to extend Kotlin code with
the platform-specic functionality. An example of this would be
java.lang package implicitly imported on the JVM platform.
Importing certain entities may be disallowed by their visibility modiers.
• public entities can be imported anywhere
• internal entities can be imported only within the same module
• protected entities cannot be imported
• top-level private entities can be imported within their declaring le
• other private entities cannot be imported
10.2 Modules
A module is a concept on the boundary between the code itself and the resulting
application, thus it depends on and inuences both of them. A Kotlin module
is a set of Kotlin les which are considered to be interdependent and must be
handled together during compilation.
In a simple case, a module is a set of les compiled at the same time in a given
project.
• A set of les being compiled with a single Kotlin compiler invocation
• A Maven module
• A Gradle project
206 CHAPTER 10. PACKAGES AND IMPORTS
Overload resolution
Glossary
type(e)
Type of expression e
Introduction
Kotlin supports overloading for callables and properties, that is, the ability for
several callables (functions or function-like properties) or properties with the
same name to coexist in the same scope, with the compiler picking the most
suitable one when such entity is referenced. This section describes overload
resolution process in detail.
Note: most of this section explains the overload resolution process for
callables, as the overload resolution process for properties uses the
same framework. Important dierences w.r.t. properties are covered
in the corresponding section.
Unlike many object-oriented languages, Kotlin does not have only regular class
methods, but also top-level functions, local functions, extension functions and
function-like values, which complicate the overload resolution process quite a
bit. Additionally, Kotlin has inx functions, operator and property overloading,
which add their own specics to this process.
11.1 Basics
11.1.1 Receivers
Every function or property that is dened as a method or an extension has one
207
208 CHAPTER 11. OVERLOAD RESOLUTION
class X : Y {
fun Y.foo() {} // `foo` is an extension for Y,
// needs extension receiver to be called
fun bar() {
foo() // `this` reference is both
// the extension and the dispatch receiver
}
}
fun main() {
val x: X = mk()
val y: Y = mk()
// y.foo()
// Error, as there is no implicit receiver
// of type X available
with (x) {
y.foo() // OK!
}
}
They mostly follow the same rules as calls with an explicit value receiver. However,
there are some dierences which we outline below.
For a callable f with an explicit basic super-form receiver super in a classier
declaration with supertypes A1, A2, . . . , AN the following sets are considered for
non-emptiness:
1. Non-extension member callables named f of type A1;
2. Non-extension member callables named f of type A2;
3. . . . ;
n. Non-extension member callables named f of type AN.
If at least two of these sets are non-empty, this is a compile-time error. Otherwise,
the non-empty set (if any) is analyzed as usual.
For a callable f with an explicit extended super-form receiver super<A> the
following sets are analyzed (in the given order):
1. Non-extension member callables named f of type A.
Additionally, in either case, abstract callables are not considered valid candidates
for the overload resolution process.
11.3.2 Description
Determining function applicability for a specic call is a type constraint problem.
First, for every non-lambda argument of the function called, type inference is
performed. Lambda arguments are excluded, as their type inference needs the
results of overload resolution to nish.
Second, the following constraint system is built:
• For every non-lambda argument inferred to have type Ti , corresponding to
the function parameter of type Uj , a constraint Ti <: Uj is constructed;
• All declaration-site type constraints for the function are also added to the
constraint system;
• For every lambda argument with the number of lambda arguments known
to be K, corresponding to the function parameter of type Um , a special con-
straint of the form (FT(L1 , ▷ ▷ ▷ , LK ) → R & FTR(RT, L1 , ▷ ▷ ▷ , Ln ) → R) <:
Um is added to the constraint system, where R, RT, L1 , ▷ ▷ ▷ , LK are fresh
type variables;
218 CHAPTER 11. OVERLOAD RESOLUTION
Both functions (1) and (2) are applicable for the call, but function (1) could
easily call function (2) by forwarding both arguments into it, and the reverse is
impossible. As a result, function (1) is more specic of the two.
fun f1(arg: Int, arg2: String) {
f2(arg, arg2) // VALID: can forward both arguments
}
fun f2(arg: Any?, arg2: CharSequence) {
f1(arg, arg2) // INVALID: function f1 is not applicable
}
The rest of this section will describe how the Kotlin compiler checks for this
property in more detail.
1. Only one of the two candidates is more applicable than the other;
2. Neither of the two candidates is more applicable than the other;
3. Both F1 and F2 are more applicable than the other.
In case 1, the more applicable candidate of the two is found and no additional
steps are needed.
In case 2, an additional step is performed.
• Any non-parameterized callable is a more specic candidate than any
parameterized callable; If there are several non-parameterized candidates,
further steps are limited to those candidates.
In case 3, several additional steps are performed in order.
• Any non-parameterized callable is a more specic candidate than any pa-
rameterized callable (same as case 2). If there are several non-parameterized
candidates, further steps are limited to those candidates;
• For each candidate we count the number of default parameters not specied
in the call (i.e., the number of parameters for which we use the default
value). The candidate with the least number of non-specied default
parameters is a more specic candidate;
• For all candidates, the candidate having any variable-argument parameters
is less specic than any candidate without them.
Note: it may seem strange to process built-in integer types in a
way dierent from other types, but it is needed for cases when the
call argument is an integer literal with an integer literal type. In
this particular case, several functions with dierent built-in integer
types for the corresponding parameter may be applicable, and the
kotlin.Int overload is selected to be the most specic.
Important: compiler implementations may extend these steps with
additional checks, if they deem necessary to do so.
If after these additional steps there are still several candidates which are equally
applicable for the call, we may attempt to use the lambda return type to rene
function applicability. If there are still more than one most specic candidate
afterwards, this is an overload ambiguity which must be reported as a compile-
time error.
Note: unlike the applicability test, the candidate comparison con-
straint system is not based on the actual call, meaning that, when
comparing two candidates, only constraints visible at declaration site
apply.
If the callables in check are properties with available invoke, the same process
is applied in two steps:
• First, the properties are compared for applicability and the most applicable
property is chosen as described above. If several properties are equally
11.4. CHOOSING THE MOST SPECIFIC CANDIDATE FROM THE OVERLOAD CANDIDATE SET221
are any, they are included in the resulting most specic candidate set Cres , with
which we nish the MSC selection. Otherwise, we nish the MSC selection with
the set C ′ .
Example:
@OverloadResolutionByLambdaReturnType
fun foo(cb: (Unit) -> String) = Unit // (1)
@OverloadResolutionByLambdaReturnType
fun foo(cb: (Unit) -> Int) = Unit // (2)
fun testOk01() {
foo { 42 }
// Both (1) and (2) are applicable
// (2) is preferred by the lambda return type
}
Example:
@OverloadResolutionByLambdaReturnType
fun foo(cb: Unit.() -> String) = Unit // (1)
@OverloadResolutionByLambdaReturnType
fun foo(cb: (Unit) -> Int) = Unit // (2)
fun testError01() {
val take = Unit
// Overload ambiguity
foo { 42 }
// Both (1) and (2) are applicable
// None is preferred by the lambda return type
// as their parameters are not SEERT
}
Example:
@OverloadResolutionByLambdaReturnType
fun foo(cb: Unit.() -> String) = Unit // (1)
@OverloadResolutionByLambdaReturnType
fun foo(cb: (Unit) -> Int) = Unit // (2)
fun testOk02() {
val take = Unit
foo { a -> 42 }
// Only (2) is applicable
// as its lambda takes one parameter
}
11.5. RESOLVING PROPERTY ACCESS 223
Example:
@OverloadResolutionByLambdaReturnType
fun foo(cb: (Unit) -> String) = Unit // (1)
@OverloadResolutionByLambdaReturnType
fun foo(cb: (Unit) -> CharSequence) = Unit // (2)
fun testError02() {
// Error: required String, found CharSequence
foo { a ->
val a: CharSequence = "42"
a
}
// Both (1) and (2) are applicable
// (1) is the only most specific candidate
// We do not attempt refinement by the lambda return type
}
fun test() {
with(42.0) {
println([email protected]) // Resolves to (1)
println(this.a) // Resolves to (2)
println(a) // Resolves to (2)
}
}
}
class AA {
fun a$get(): Int = 5 // (1)
fun test() {
with(42.0) {
println([email protected]$get()) // Resolves to (1)
println(this.a$get()) // Resolves to (2)
println(a$get()) // Resolves to (2)
}
}
}
Property assignment a.x = y is resolved the same way as if it was replaced with
a special function call a.x$set(y) and each property var/val x: T was re-
placed with a corresponding function fun x$set(value: T) having all the same
extension receiver parameters, context receiver parameters, type parameters and
scope as the original property and providing direct access to the property setter.
For dierent avors of property declarations and setters, refer to corresponding
11.5. RESOLVING PROPERTY ACCESS 225
section. Please note that, although a read-only property declaration (using the
keyword val) does not allow for assignment or having a setter, it still takes part
in overload resolution for property assignment and may still be picked up as a
candidate. Such a candidate (in case it is selected as the nal candidate) will
result in compiler error at later stages of compilation.
Note: informally, one may look at property assignment resolution
as a sub-kind of read-only property resolution described above, rst
resolving the property as if it was accessed in a read-only fashion,
and then using the setter. Read-only property access and property
assignment syntax used in the same position never resolve to dierent
property candidates
Example: one may consider property access in class B to be resolved
as if it has been transformed to class BB. Declaration bodies for
ephemeral functions are omitted to avoid confusion
class B {
var b: Int = 5 // (1)
fun test() {
b = 5 // Resolves to (1)
with(42.0) {
// Resolves to (1)
[email protected] = 5
// Resolves to (2) and compiler error: cannot assign read-only property
this.b = 5
// Resolves to (2) and compiler error: cannot assign read-only property
b = 5
}
}
}
class BB {
fun b$get(): Int // (1, getter)
fun b$set(value: Int) // (1, setter)
fun test() {
b$set(5) // Resolves to (1)
with(42.0) {
226 CHAPTER 11. OVERLOAD RESOLUTION
// Resolves to (1)
[email protected]$set(5)
// Resolves to (2)
this.b$set(5)
// Resolves to (2)
this.b$set(5)
}
}
}
The overload resolution for properties has the following features distinct from
overload resolution for callables.
• Properties without getter or setter are assumed to have default implemen-
tations for accessors (ones which get or set its backing eld);
• The overload resolution takes into account the kind of property, meaning
an extension read-only property is considered to have an extension getter,
an extension mutable property is considered to have an extension getter
and setter, etc.;
• Object declarations and enumeration entries may be accessed using the
property access syntax given that they may be resolved in the current
scope.
bar(::foo)
candidate (2) is picked, because (assuming CRT is the type of the
callable reference) the constraint CRT <: FT(kotlin.Double) →
kotlin.Double is built and only candidate (2) is applicable w.r.t.
this constraint.
Please note that no bidirectional resolution is performed here as there
is only one candidate for bar. If there were more than one candidate,
the bidirectional resolution process would apply, possibly resulting
in an overload resolution failure.
after overload resolution is done; meaning type inference may not aect the way
overload resolution candidate is picked in any way.
Note: if we had allowed interdependence between type inference and
overload resolution, we would have been able to create an innitely
oscillating behaviour, leading to an innite compilation.
Important: an exception to this limitation is when a lambda return
type is used to rene function applicability. By limiting the scope of
interdependence between type inference and overload resolution to a
single step, we avoid creating an oscillating behaviour.
Several Kotlin features such as variable initialization analysis and smart cast-
ing analysis require performing control- and data-ow analyses. This section
describes them and their applications.
231
232 CHAPTER 12. CONTROL- AND DATA-FLOW ANALYSIS
We also use the eval b notation where b is not a single statement, but rather
a control structure body. The fragment for a control structure body is the
sequence of fragments for its statements, connected in the program order.
Some of the fragments have two kinds of outgoing edges, labeled t and f on the
pictures. In a similar fashion, some eval nodes have two outgoing edges with
the same labels. If such a fragment is inserted into such a node, only edges with
matching labels are merged into each other. If either the fragment or the node
have only unlabeled outgoing edges, the process is performed same as above.
Some nodes are labeled, similarly to how statements may be labeled in Kotlin.
Labeled nodes are considered CFG-unique and are handled as follows: if a
fragment mentions a particular labeled node, this node is the same as any other
node with this label in the complete CFG (i.e., a singular actual node is shared
between all its labeled references). This is important when building graphs
representing loops.
There are two other special kinds of nodes: unreachable nodes, signifying
unreachable code, and backedge nodes, important for some kinds of analyses.
12.1.1 Expressions
Simple expressions, like literals and references, do not aect the control-ow of
the program in any way and are irrelevant w.r.t. CFG.
x.f(arg1,..., argN)
12.1. CONTROL FLOW GRAPH 233
f(arg1,..., argN)
Conditional expressions
if(c) tt else ff
234 CHAPTER 12. CONTROL- AND DATA-FLOW ANALYSIS
when {
c1 -> b1
else -> bE
}
when {
<entry1>
else -> {
when {
<entries...>
else -> bE
}
}
}
Boolean operators
!x
x || y
236 CHAPTER 12. CONTROL- AND DATA-FLOW ANALYSIS
x && y
Other expressions
x ?: y
x?.y
12.1. CONTROL FLOW GRAPH 237
try { a... }
catch (e1: T1) { b1... }
...
catch (eN: TN) { bN... }
finally { c... }
a!!
238 CHAPTER 12. CONTROL- AND DATA-FLOW ANALYSIS
a as T
a as? T
return
return@label
return a
return@label a
throw a
break@loop
continue@loop
12.1.2 Statements
Note: to simplify the notation, we consider only labeled loops, as
unlabeled loops may be trivially turned into labeled ones by assigning
them a unique label.
12.1.3 Declarations
var a = b
var a by b
val a = b
val a by b
12.1. CONTROL FLOW GRAPH 241
class A (...) {
'declaration 1'
'declaration 2'
'init-block 1'
'declaration 3'
'init-block 2'
...
}
For every declaration and init block in a class body, the control ow is propagated
through every element in the order of their appearance. Here we give a simplied
example.
242 CHAPTER 12. CONTROL- AND DATA-FLOW ANALYSIS
12.1.4 Examples
Important: each specic analysis may decide to either use this in-
formation or ignore it for a given program. If unreachability from
kotlin.Nothing is used, it can be represented in dierent ways, e.g.,
by changing the CFG structure or via killDataFlow instructions.
• A lattice S (a partially ordered set that has both a greatest lower bound
and a least upper bound dened for every pair of its elements) of values,
called abstract states;
• A transfer function for mapping CFG nodes to the elements of S, essentially
a set of rules on how to calculate an abstract state for each node of the
CFG either directly or by using abstract states of other nodes.
The result of an analysis is a xed point of the transfer function for each node
of the given CFG, i.e., an abstract state for each node such that the transfer
function maps the state to itself. For the particular shapes of the transfer
function used in program analyses, given a nite S, the xed point always exists,
although the details of how this works go out of scope of this document.
The at lattice is usually used for analyses interested in exact facts, such as
denite (un)assignment or constant propagation, as the xed point results
are either exact elements from the set A, or top/bottom elements.
A → L = {[a1 → l1 , ▷ ▷ ▷ , an → ln ] |∀i : ai ∈ A, li ∈ L}
f ≤ g ⇔ ∀ai ∈ A : f (ai ) ≤ g(ai ), where f, g ∈ A → L
The map lattice is usually used as the “top-level” lattice for bootstrapping
the monotone framework analysis, by providing a way to represent the
mapping from program entities (e.g., variables or expressions) to interesting
facts (e.g., their initialization or availability) as a lattice.
Note: such lattice has 0 as its bottom element and does not have a
top element.
[[backedge]] (s) = {⋆ → 0}
[[l]] (s) = [[p]] (s)
p∈predecessor(l)
After running this analysis, for every backedge b and every variable x present
in s, if ∃bp , bs : bp ∈ predecessors(b) ∧ bs ∈ successors(b) ∧ [[bp ]] (x) > [[bs ]] (x), a
killDataFlow(x) instruction must be inserted after b.
var x: Int = 0
var y: Int = 0
while (b1) {
y = f()
do {
x = g()
} while (b2)
}
which results in the following CFG diagram (annotated with the analysis results
where it is important):
248 CHAPTER 12. CONTROL- AND DATA-FLOW ANALYSIS
There are two backedges: one for the inner loop (the inner backedge) and one
for the outer loop (the outer backedge). The inner backedge has one predecessor
with state {x → 2, y → 2} and one successor with state {x → 1, y → 2} with
the value for x being less in the successor, meaning that we need to insert
killDataFlow(x) after the backedge. The outer backedge has one predecessor
with state {x → 2, y → 2} and one successor with state {x → 1, y → 1} with
values for both variables being less in the successor, meaning we need to insert
killDataFlow(x) and killDataFlow(y) after the backedge.
12.2. PERFORMING ANALYSES ON THE CONTROL-FLOW GRAPH 249
There are no incorrect operations in this example, so the code does not produce
any compile-time errors.
Let us consider another example:
/* 1 */ val x: Int // {x → Unassigned, ⋆ → ⊥}
/* 2 */ var y: Int // {x → Unassigned, y → Unassigned, ⋆ → ⊥}
/* 3 */ while (c) { // {x → ⊤, y → ⊤, ⋆ → ⊥} Error!
/* 4 */ x = 40 // {x → ⊤, y → ⊤, ⋆ → ⊥}
/* 5 */ y = 4 // {x → ⊤, y → ⊤, ⋆ → ⊥}
/* 6 */ } //
/* 7 */ val z = x + y // {x → ⊤, y → ⊤, ⋆ → ⊥} More errors!
250 CHAPTER 12. CONTROL- AND DATA-FLOW ANALYSIS
At line 7 there is another compile-time error when both properties are used, as
there are paths in the CFG which reach line 7 when the properties have not
been assigned (i.e., the case when the while loop body was skipped).
Some standard-library functions in Kotlin are dened in such a way that they
adhere to a specic call contract that aects the way calls to such functions are
analyzed from the perspective of the caller’s control ow graph. A function’s
call contract consists of one or more eects.
These eects change the call graph that is produced for a function call of F
when supplied a lambda-expression parameter for P . Without any eect, the
graph looks like this:
Please note that control ow information is passed inside the lambda body,
but no information is extracted from it. If the corresponding parameter P is
introduced with exactly-once eect, this changes to:
x = 4
}
// could be error: x is not initialized
// but is ok
println(x)
Several examples of contract-introduced smart-cast:
val x: Any = ...
check(x is Int)
// x is known to be Int thanks to assume introduced by
// the contract of check
val y = x + 4 // would be illegal without contract
val x: Int? = ...
// x is known to be non-null thanks to assume introduced by
// the contract of require
require(x != null)
val y = x + 4 // would be illegal without contract
References
1. Frances E. Allen. “Control ow analysis.” ACM SIGPLAN Notices, 1970.
2. Flemming Nielson, Hanne R. Nielson, and Chris Hankin. “Principles of
program analysis.” Springer, 2015.
3. Kam, John B., and Jerey D. Ullman. “Monotone data ow analysis
frameworks.” Acta informatica 7.3 (1977): 305-317.
4. Anders Moller, and Michael I. Schwartzbach. “Static Program Analysis.”
2018 (https://cs.au.dk/~amoeller/spa/)
254 CHAPTER 12. CONTROL- AND DATA-FLOW ANALYSIS
Chapter 13
Some complex tasks that need to be solved when compiling Kotlin code are
formulated best using constraint systems over Kotlin types. These are solved
using constraint solvers.
255
256 CHAPTER 13. KOTLIN TYPE CONSTRAINTS
are no longer needed. The incorporation phase is used to introduce new bounds
and constraints from existing bounds.
A bound is similar to a constraint in that it has the form S <: T , at least one of
S or T is an inference variable. Thus, the current (and also the nal) solution is
a set of upper and lower bounds for each inference variable. A resolved type in
this context is any type which does not contain inference variables.
Reduction phase: for each constraint S <: T in the constraint system the
following rules are applied:
• If S and T are resolved types and:
– If S <: T , this constraint is eliminated;
– Otherwise, this is an inference error;
• Otherwise, if S is an inference variable α, a new bound α <: T is added to
current solution;
• Otherwise, if T is an inference variable β, a new bound S <: β is added to
current solution;
• Otherwise, if S is a exible type of the form (α▷▷α?) where α is an inference
variable, a new bound α <: (T▷▷T ?) is added to current solution;
• Otherwise, if T is a exible type of the form (α▷▷α?) where α is an inference
variable, a new bound (S▷▷S?) <: α is added to current solution;
• Otherwise, if S is a nullable type of the form A? and:
– If T is a known non-nullable type (a classier type, a nullability-
asserted type B!!, a type variable with a known non-nullable lower
bound, or an intersection type containing a known non-nullable type),
this is an inference error;
– Otherwise, the constraint is reduced to A <: T . Also, if T is also a
nullable type of the form B?, an additional constraint A!! <: B is
introduced;
• Otherwise, if S is a exible type of the form (B▷▷A?) and:
– If T is a nullable type of form C?, the constraint is reduced to
(B▷▷A) <: C, or to A <: C if A ≡ B;
– Otherwise, the constraint is reduced to (B▷▷A) <: T , or to A <: T if
A ≡ B;
• Otherwise, if T is a parameterized type G[A1 , ▷ ▷ ▷ , AN ], among all super-
types of S the one of the form G[B1 , ▷ ▷ ▷ , BN ] is chosen.
– If no such supertype exists, this is an inference error;
– Otherwise, for each M ∈ [1, N ], a type argument constraint for
containment AM ⪯ BM is introduced (see below);
• Otherwise, if T is any other classier type and T is among supertypes for
S, the constraint is eliminated; otherwise, this is an inference error;
• Otherwise, if T is a type variable and:
– If S is an intersection type containing T , this constraint is eliminated;
– Otherwise, if T has a lower bound B, the constraint is reduced to
S <: B;
– Otherwise, this is an inference error;
258 CHAPTER 13. KOTLIN TYPE CONSTRAINTS
BM′
respectively, the following new constraints are produced: A′M <: BM
′
and BM <: AM .
′ ′
Type inference
Kotlin has a concept of type inference for compile-time type information, meaning
some type information in the code may be omitted, to be inferred by the compiler.
There are two kinds of type inference supported by Kotlin.
Kotlin also supports ow-sensitive types in the form of smart casts, which have
direct eect on type inference. Therefore, we will discuss them rst, before
talking about type inference itself.
261
262 CHAPTER 14. TYPE INFERENCE
P1 × N1 ⊑ P2 × N2 ⇔ P1 <: P2 ∧ N1 :> N2
P1 × N1 ⊔ P2 × N2 = LUB(P1 , P2 ) × GLB(N1 , N2 )
P1 × N1 ⊓ P2 × N2 = GLB(P1 , P2 ) × LUB(N1 , N2 )
Note: a well-informed reader may notice the second component is
behaving very similarly to a negation type.
(P1 & ¬N1 ) | (P2 & ¬N2 ) ⊑ (P1 | P2 ) & (¬N1 | ¬N2 )
= (P1 | P2 ) & ¬(N1 & N2 )
(P1 & ¬N1 ) & (P2 & ¬N2 ) = (P1 & P2 ) & (¬N1 & ¬N2 )
= (P1 & P2 ) & ¬(N1 | N2 )
[[l]] (s) = [[p]] (s)
p∈predecessor(l)
where
swap(P × N ) = N × P
(kotlin.Nothing? ×⊤) if s ⊑ (kotlin.Nothing? ×⊤)
isNullable(s) =
(⊤ × ⊤) otherwise
N
[[l]] = [[li ]]
i=1
kotlin.Any if kotlin.Nothing? <: N
approxNegationType(N ) =
kotlin.Any? otherwise
fun noSmartCastInInference() {
var a: Any? = null
if (a == null) return
fun smartCastInInference() {
var a: Any? = null
if (a == null) return
var c = id(a)
Note: property declarations are not listed here, as their types are
derived from initializers.
Note: for the purposes of smart casts, most of these constructions are
simplied and/or desugared, when we are building the program CFG
for the data-ow analysis. We informally call such constructions
smart cast sources, as they are responsible for creating smart cast
specic instructions.
266 CHAPTER 14. TYPE INFERENCE
if (x != null) {
run {
// nested smart cast sink
x.inc()
// nested redefinition
x = ...
14.1. SMART CASTS 267
}
// direct smart cast sink
x.inc()
}
// direct redefinition
x = ...
}
A mutable local property P dened at D is considered eectively immutable at
a direct sink S, if there are no nested redenitions on any CFG path between D
and S.
A mutable local property P dened at D is considered eectively immutable at a
nested sink S, if there are no nested redenitions of P and all direct redenitions
of P precede S in the CFG.
Example:
fun directSinkOk() {
var x: Int? = 42 // definition
if (x != null) // smart cast source
x.inc() // direct sink
run {
x = null // nested redefinition
}
}
fun directSinkBad() {
var x: Int? = 42 // definition
run {
x = null // nested redefinition
// between a definition
// and a sink
}
if (x != null) // smart cast source
x.inc() // direct sink
}
fun nestedSinkOk() {
var x: Int? = 42 // definition
x = getNullableInt() // direct redefinition
run {
if (x != null) // smart cast source
x.inc() // nested sink
}
}
268 CHAPTER 14. TYPE INFERENCE
fun nestedSinkBad01() {
var x: Int? = 42 // definition
run {
if (x != null) // smart cast source
x.inc() // nested sink
}
x = getNullableInt() // direct redefinition
// after the nested sink
}
fun nestedSinkBad02() {
var x: Int? = 42 // definition
run {
x = null // nested redefinition
}
run {
if (x != null) // smart cast source
x.inc() // nested sink
}
}
while (true) {
if (a == null) return
if (randomBoolean()) break
}
fun doWhileAndSmartCasts() {
var a: Any? = null
do {
if (a == null) return
} while (randomBoolean())
fun doWhileAndSmartCasts2() {
var a: Any? = null
do {
println(a)
} while (a == null)
if (b is Int) {
// as a and b point to the same value,
// a also is Int
a.inc()
}
270 CHAPTER 14. TYPE INFERENCE
In more complex cases, however, it may not be trivial to deduce that two (or
more) properties point to the same runtime object. This relation is known as
must-alias relation between program references and it is implementation-dened
in which cases a particular Kotlin compiler may safely assume this relation holds
between two particular properties at a particular program point. However, it
must guarantee that if two properties are considered bound, it is impossible for
these properties to reference two dierent values at runtime.
One way of implementing bound smart casts would be to divide the space of
stable program properties into disjoint alias sets of properties, and the analysis
described above links the smart cast data ow information to sets of properties
instead of single properties.
Such view could be further rened by considering special alias sets separately;
e.g., an alias set of denitely non-null properties, which would allow the compiler
to infer that a?.b !== null implies a !== null (for non-nullable b).
fun bar() {
val x = run {
run {
run {
foo<Int>() // last expression inferred to be of type Int
} // this lambda is inferred to be of type () -> Int
} // this lambda is inferred to be of type () -> Int
} // this lambda is inferred to be of type () -> Int
// x is inferred to be of type Int
val y: Double = run { // this lambda has an external constraint R' <: Double
run { // this lambda has an external constraint R'' <: Double
foo() // this call has an external constraint T' <: Double
// allowing to infer T to be Double in foo
}
}
}
if (additionalEntry != null) {
put(additionalEntry.first, additionalEntry.second)
// provides information about String <: K, Int <: V
}
}
// solves to String =:= K, Number =:= V
// ...
}
276 CHAPTER 14. TYPE INFERENCE
Chapter 15
The runtime type information (RTTI) is the information about Kotlin types of
values available from these values at runtime. RTTI aects the semantics of
certain expressions, changing their evaluation depending on the amount of RTTI
available for particular values, implementation, and platform:
• The type checking operator
• The cast expression, especially the as? operator
• Class literals and the values they evaluate to
Runtime types are particular instances of RTTI for a particular value at runtime.
These model a subset of the Kotlin type system. Namely, the runtime types are
limited to classier types, function types and a special case of kotlin.Nothing?
which is the type of null reference and the only nullable runtime type. This
includes the classier types created by anonymous object literals. There is a
slight distinction between a Kotlin type system type and its runtime counterpart:
• On some platforms, some particular types may have the same runtime
type representation. This means that checking or casting values of these
types works the same way as if they were the same type
• Generic types with the same classier are not required to have dierent
runtime representations. One cannot generally rely on them having the
same representation outside of a particular platform. Platform specica-
tions must clarify whether some or all types on these platforms have this
feature.
RTTI is also the source of information for platform-specic reection facilities
in the standard library.
The types actual values may have are limited to class and object types and func-
tion types as well as kotlin.Nothing? for the null reference. kotlin.Nothing
(not to be confused with its nullable variant kotlin.Nothing?) is special in the
way that this type is never encountered as a runtime type even though it may
277
278 CHAPTER 15. RUNTIME TYPE INFORMATION
have a platform-specic representation. The reason for this is that this type is
used to signify non-existent values.
15.2 Reection
Particular platforms may provide more complex facilities for runtime type
introspection through the means of reection — special platform-provided part
of the standard library that allows to access more detailed information about
types and declarations at runtime. It is, however, platform-specic and one must
refer to particular platform documentation for details.
Chapter 16
Exceptions
An exception type declaration is any type declaration that meets the following
criteria:
• It is a class or object declaration;
• It has kotlin.Throwable as one of its supertypes (either explicitly or
implicitly);
• It has no type parameters.
Any object of an exception type may be thrown or caught.
279
280 CHAPTER 16. EXCEPTIONS
this block is evaluated after the body of the selected catch block. If these
evaluations results in throwing other exceptions (including the one caught by the
catch-block), they are propagated as if none of the catch-blocks were applicable.
Important: the try-expression itself is not considered active inside
its own catch and finally blocks.
If none of the catch-blocks of the currently active try-expression are applicable
for the exception, the finally block (if any) is still evaluated, and the exception
is propagated, meaning the next active try-expression becomes currently active
and is checked for applicability.
If there are no active try-blocks, the execution of the program nishes, signaling
that the exception has reached top level.
Annotations
281
282 CHAPTER 17. ANNOTATIONS
• SOURCE;
• BINARY;
• RUNTIME.
17.5.2 kotlin.annotation.Target
kotlin.annotation.Target is an annotation which is only used on annotation
classes to specify targets those annotations are valid for. It has the following
single eld:
• CLASS;
• ANNOTATION_CLASS;
• TYPE_PARAMETER;
• PROPERTY;
• FIELD;
• LOCAL_VARIABLE;
• VALUE_PARAMETER;
• CONSTRUCTOR;
• FUNCTION;
• PROPERTY_GETTER;
• PROPERTY_SETTER;
• TYPE;
• EXPRESSION;
• FILE;
• TYPEALIAS.
284 CHAPTER 17. ANNOTATIONS
17.5.3 kotlin.annotation.Repeatable
kotlin.annotation.Repeatable is an annotation which is only used on an-
notation classes to specify whether this particular annotation is repeatable.
Annotations are non-repeatable by default.
17.5.6 kotlin.Suppress
kotlin.Suppress is an annotation class with the following single eld:
• vararg val names: String
The names of features this annotation is suppressing.
kotlin.Suppress is used to optionally mark any piece of code as suppressing
some language feature, such as a compiler warning, an IDE mechanism or a
language feature. The names of features which one can suppress with this
annotation are implementation-dened, as is the processing of this annotation
itself.
17.5.7 kotlin.SinceKotlin
kotlin.SinceKotlin is an annotation class with the following single eld:
• val version: String
The version of Kotlin language.
kotlin.SinceKotlin is used to mark a declaration which is only available since
a particular version of the language. These mostly refer to standard library
declarations. It is implementation-dened how this annotation is processed.
17.5.8 kotlin.UnsafeVariance
kotlin.UnsafeVariance is an annotation class with no elds which is only
applicable to types. Any type instance marked by this annotation explicitly
286 CHAPTER 17. ANNOTATIONS
states that the variance errors arising for this particular type instance are to be
ignored by the compiler.
17.5.9 kotlin.DslMarker
kotlin.DslMarker is an annotation class with no elds which is applicable
only to other annotation classes. An annotation class annotated with
kotlin.DslMarker is marked as a marker of a specic DSL (domain-specic
language). Any type annotated with such a marker is said to belong to that
specic DSL. This aects overload resolution in the following way: no two
implicit receivers with types belonging to the same DSL are available in the
same scope. See Overload resolution section for details.
17.5.10 kotlin.PublishedApi
kotlin.PublishedApi is an annotation class with no elds which is applicable
to any declaration. It may be applied to any declaration with internal visibility
to make it available to public inline declarations. See Declaration visibility
section for details.
17.5.11 kotlin.BuilderInference
Marks the annotated function of function argument as eligible for builder-style
type inference. See corresponding section for details.
Note: as of Kotlin 1.9, this annotation is experimental and, in order
to use it in one’s code, one must explicitly enable it using opt-in
annotations given above. The particular marker class used to perform
this is implementation-dened.
17.5.12 kotlin.RestrictSuspension
In some cases we may want to limit which suspending functions can be called in
another suspending function with an extension receiver of a specic type; i.e., if
we want to provide a coroutine-enabled DSL, but disallow the use of arbitrary
suspending functions. To do so, the type T of that extension receiver needs to
be annotated with kotlin.RestrictSuspension, which enables the following
limitations.
• Suspending functions with an extension receiver of type T are restricted
from calling other suspending functions besides those accessible on this
receiver.
• Suspending functions of type T can be called only on an extension receiver.
17.5.13 kotlin.OverloadResolutionByLambdaReturnType
This annotation is used to allow using lambda return type to rene function
17.5. BUILT-IN ANNOTATIONS 287
Asynchronous programming
with coroutines
289
290CHAPTER 18. ASYNCHRONOUS PROGRAMMING WITH COROUTINES
18.2 Coroutines
A coroutine is a concept similar to a thread in traditional concurrent program-
ming, but based on cooperative multitasking, e.g., the switching between dierent
execution contexts is done by the coroutines themselves rather than the operating
system or a virtual machine.
In Kotlin, coroutines are used to implement suspending functions and can switch
contexts only at suspension points.
A call to a suspending function creates and starts a coroutine. As one can call a
suspending function only from another suspending function, we need a way to
bootstrap this process from a non-suspending context.
Note: this is required as most platforms are unaware of coroutines or
suspending functions, and do not provide a suspending entry point.
However, a Kotlin compiler may elect to provide a suspending entry
point on a specic platform.
One of the ways of starting suspending function from a non-suspending context
is via a coroutine builder: a non-suspending function which takes a suspending
18.3. IMPLEMENTATION DETAILS 291
function type argument (e.g., a suspending lambda literal) and handles the
coroutine lifecycle.
The implementation of coroutines is platform-dependent. Please refer to the
platform documentation for details.
18.3.1 kotlin.coroutines.Continuation<T>
Interface kotlin.coroutines.Continuation<T> is the main supertype of all
coroutines and represents the basis upon which the coroutine machinery is
implemented.
public interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(result: Result<T>)
}
Every suspending function is associated with a generated Continuation subtype,
which handles the suspension implementation; the function itself is adapted
to accept an additional continuation parameter to support the Continuation
Passing Style. The return type of the suspending function becomes the type
parameter T of the continuation.
CoroutineContext represents the context of the continuation and is an indexed
set from CoroutineContext.Key to CoroutineContext.Element (e.g., a special
kind of map). It is used to store coroutine-local information, and takes important
part in Continuation interception.
resumeWith function is used to propagate the results in between suspension
points: it is called with the result (or exception) of the last suspension point and
resumes the coroutine execution.
To avoid the need to explicitly create the Result<T> when calling resumeWith,
the coroutine implementation provides the following extension functions.
fun <T> Continuation<T>.resume(value: T)
fun <T> Continuation<T>.resumeWithException(exception: Throwable)
The calling convention is maintained by the compiler during the CPS transforma-
tion, which prevents the user from manually returning COROUTINE_SUSPENDED.
If the user wants to suspend a coroutine, they need to perform the following
steps.
As Kotlin does not currently support denotable union types, the return type
is changed to kotlin.Any?, so it can hold both the original return type T and
COROUTINE_SUSPENDED.
Example:
L0:
// result is expected to be `null` at this invocation
a = a()
label = 1
// 'this' is passed as a continuation
result = foo(a).await(this)
// return if await had suspended execution
if (result == COROUTINE_SUSPENDED)
return COROUTINE_SUSPENDED
L1:
// error handling
result.throwOnFailure()
// external code has resumed this coroutine
// passing the result of .await()
y = (Y) result
b()
label = 2
// 'this' is passed as a continuation
result = bar(a, y).await(this)
// return if await had suspended execution
if (result == COROUTINE_SUSPENDED)
return COROUTINE_SUSPENDED
L2:
// error handling
result.throwOnFailure()
// external code has resumed this coroutine
294CHAPTER 18. ASYNCHRONOUS PROGRAMMING WITH COROUTINES
Concurrency
Kotlin Core does not specify the semantics of the code running in concurrent
environments. For information on threading APIs, memory model, supported
synchronization and other concurrency-related capabilities of Kotlin on your
platform, please refer to platform documentation.
297
298 CHAPTER 19. CONCURRENCY