├── .gitignore ├── Makefile ├── README.md ├── doc-src ├── 00-files.txt ├── 00-latex.tex ├── 01-front-matter.md ├── 10-proposal.md ├── 1a-non-null-types.md ├── 1b-nnbd.md ├── 1c-generics.md ├── 1d-dynamic.md ├── 1e-misc.md ├── 1f-lib.md ├── 1g-migration.md ├── Makefile ├── file-stats.txt ├── x1-review.md ├── x2-tooling.md ├── xx-history.md └── zz-references.md ├── doc ├── dep-non-null-AUTOGENERATED-DO-NOT-EDIT.md └── dep-non-null.pdf ├── example └── doc │ ├── box.dart │ ├── null.dart │ ├── optional-parameters.dart │ └── point.dart ├── package.json ├── pubspec.yaml └── tool └── depNonNullFilter /.gitignore: -------------------------------------------------------------------------------- 1 | .buildlog 2 | .pub 3 | build 4 | node_modules 5 | packages 6 | pubspec.lock 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default pdf all 2 | default: pdf 3 | 4 | pdf: 5 | cd doc-src && $(MAKE) pdf 6 | 7 | all: 8 | cd doc-src && $(MAKE) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Non-null Types and Non-null By Default (NNBD) 2 | 3 | [Dart Enhancement Process][DEP] (DEP) proposal for ***non-null types*** and ***non-null by default***. The full proposal is available in two formats: 4 | 5 | - [PDF](https://github.com/chalin/DEP-non-null/raw/master/doc/dep-non-null.pdf), or 6 | - [markdown](doc/dep-non-null-AUTOGENERATED-DO-NOT-EDIT.md) (autogenerated, DO NOT EDIT). 7 | 8 | Original markdown sources for the proposal are under [doc-src](doc-src). 9 | 10 | [DEP]: https://github.com/dart-lang/dart_enhancement_proposals 11 | 12 | ----- 13 | 14 | If you make changes and want to generate the PDF, then follow these instructions. (When submitting a Pull Request, there is no need to submit the PDF or .md under [doc](doc).) 15 | 16 | 1. To build the proposal you will need [Pandoc](http://pandoc.org), which can be installed as a [brew](http://brew.sh) [cask](http://caskroom.io) as follows: 17 | 18 | ```shell 19 | $ brew cask install pandoc 20 | ``` 21 | 22 | (The `pandoc` cask takes minutes to install, against hours for the homebrew `pandoc` Formula.) 23 | 24 | 2. We also have a simple custom Pandoc filter that requires a node package so run: 25 | 26 | ```shell 27 | $ npm install 28 | ``` 29 | 30 | 3. To generate a PDF run: 31 | 32 | ```shell 33 | $ make 34 | ``` 35 | -------------------------------------------------------------------------------- /doc-src/00-files.txt: -------------------------------------------------------------------------------- 1 | # Main 2 | 01-front-matter.md 3 | 10-proposal.md 4 | # Parts 5 | 1a-non-null-types.md 6 | 1b-nnbd.md 7 | 1c-generics.md 8 | 1d-dynamic.md 9 | 1e-misc.md 10 | 1f-lib.md 11 | 1g-migration.md 12 | # Appendices 13 | x1-review.md 14 | x2-tooling.md 15 | xx-history.md 16 | # References 17 | zz-references.md 18 | -------------------------------------------------------------------------------- /doc-src/00-latex.tex: -------------------------------------------------------------------------------- 1 | % Fix even-page header problem (header is always ``CONTENTS'') 2 | 3 | \usepackage{fancyhdr} 4 | \pagestyle{fancy} 5 | \newcommand{\myTitle}{Dart DEP Non-null Types and Non-null By Default} 6 | \newcommand{\myShortTitle}{Dart DEP, Non-null} 7 | \fancyhead[RE]{\textsc{\myShortTitle}} 8 | \fancyhead[LO]{\textsc{P.~Chalin}} 9 | 10 | % DartNNBD specific definitions 11 | 12 | \newcommand{\botObject}{\bot_{\cd{Object}}} 13 | 14 | % Excerpt from dart.sty 15 | 16 | \def\builtinId#1{\textbf{#1}} 17 | \def\DYNAMIC{\builtinId{dynamic}} 18 | \newcommand{\cd}[1]{\textsf{#1}} 19 | 20 | % Math symbols 21 | 22 | \newcommand{\pg}[1]{{\operatorname{#1}}} 23 | \newcommand{\nut}[1]{{}^{?}#1} % nulled type 24 | 25 | \DeclareMathOperator{\asgn}{\Longleftrightarrow} 26 | \DeclareMathOperator{\subtype}{<:} 27 | \DeclareMathOperator{\mst}{<<} 28 | -------------------------------------------------------------------------------- /doc-src/01-front-matter.md: -------------------------------------------------------------------------------- 1 | % Dart DEP #30: Non-null Types and Non-null By Default (NNBD) 2 | % Patrice Chalin, [chalin@dsrg.org](mailto:chalin@dsrg.org) 3 | % 2015-07-09 (0.6.7) - [revision history](#revision-history) 4 | -------------------------------------------------------------------------------- /doc-src/10-proposal.md: -------------------------------------------------------------------------------- 1 | # DEP #30: Non-null Types and Non-null By Default (NNBD) {- #part-main} 2 | 3 | ## Contact information {-} 4 | 5 | - [Patrice Chalin][], @[chalin][], [chalin@dsrg.org](mailto:chalin@dsrg.org) 6 | - **[DEP #30][] home**: [github.com/chalin/DEP-non-null][DEP-non-null]. 7 | - **Additional stakeholders**: 8 | + Leaf Petersen, @[leafpetersen](https://github.com/leafpetersen), [Dart Dev Compiler][] team. 9 | 10 | ## 1 Introduction 11 | 12 | In this [DEP][] we propose [4 _core_ updates and additions](#lang-changes) to the language allowing us to [naturally recover](#part-non-null-types) a [**non-null-by-default** (NNBD)](#part-nnbd) interpretation of Dart _class types_. That is, _generally speaking_, an unadorned class type *T* will represent the *non-null* type consisting (strictly) of instances of *T*. When needed, the _meta type annotation_ `?` can be used; ?*T* denotes the *nullable* type derived from *T*, consisting of values from *T* or `null`. 13 | 14 | Careful consideration has been given to [language design goals (Section 5)](#goals) during the writing of this proposal. For example, this proposal fully preserves the optional nature of static type annotations. 15 | 16 | The scope of this proposal [includes](#proposal-details): 17 | generics ([Part C](#part-generics)); 18 | dealing with `dynamic` and missing static type annotations ([Part D](#part-dynamic)); 19 | miscellaneous features including syntactic sugar ([Part E](#part-misc)); 20 | preliminary impact on the Dart SDK libraries ([Part F](#part-libs)), and finally 21 | a migration strategy ([Part G](#part-migration)). 22 | 23 | TL;DR: the [_executive summary_ of updates and additions (Section 8.1)](#lang-changes) fits in 3/4 of a page. 24 | 25 | ## 2 Motivation 26 | 27 | ### 2.1 Dart: improving compilation to JavaScript 28 | 29 | Being able to **compile Dart into compact and efficient JavaScript** ([JS][]) is paramount, possibly even more so given the Dart news _[Dart for the Entire Web][]_, March 25, 2015 (emphasis mine): 30 | 31 | > In order to do what's best for our users and the web, and not just Google Chrome, we will **focus our web efforts on compiling Dart to JavaScript**. We have decided **not to integrate the Dart VM into Chrome**. 32 | 33 | [NNBD][] allows compilers and VMs to perform important optimizations. For example, under the current *nullable-by-default* semantics, the [Dart Dev Compiler][] (DDC) translates this Dart fragment: 34 | 35 | ```dart 36 | return (a + b) * a * b; // a and b are declared as int 37 | ``` 38 | into this [JS][]: 39 | 40 | ```javascript 41 | return dart.notNull(dart.notNull((dart.notNull(a) + dart.notNull(b))) 42 | * dart.notNull(a)) * dart.notNull(b); 43 | ``` 44 | 45 | as is pointed out in [DDC issue #64][]. Under [NNBD][], the `dart.notNull()` wrappers become unnecessary, so that the Dart and corresponding [JS][] would be identical. (We ignore here the issue of finite vs. arbitrary precision numerics, as it is both orthogonal to, and outside the scope of this proposal.) 46 | 47 | Interest in non-null types for Dart and related projects has been manifested in: 48 | 49 | - [Dart issue #22][], starred by 198 individuals, is a request for *support for non-nullable types*. 50 | - Bob Nystrom's 2011 strawman _[proposal for Null-safety in Dart][Nystrom, 2011]_ suggested the use of non-null types and [NNBD][]. Unfortunately, at the time, the language committee was busy with other issues that took precedence. 51 | - [Chrome V8][] team is giving thought to [NNBD][] for [JS][]; see [Experiments with Strengthening JavaScript][], especially p. 39 (the second to last page) of [JSExperimentalDirections][]. 52 | - Finally, non-null types are on [Lasse R.H. Nielsen's 2014 wish list][dart-misc-1], and so far, almost half of his wishes have either come true or are the subject of a [DEP][] ;). 53 | 54 | [dart-misc-1]: https://groups.google.com/a/dartlang.org/d/msg/misc/8Uchi3bW1YQ/WnVkaDDmvzUJ 55 | 56 | ### 2.2 Precedent: modern web/mobile languages with non-null types and [NNBD][] {#precedent} 57 | 58 | Programming languages, many recently released, that are relevant to web applications (either dialects of [JS][] or that compile to [JS][]) and/or mobile, and that support _non-null types_ and [NNBD][] include: 59 | 60 | | Language | About | v1.0? | Nullable via | Reference 61 | | -------------------- | ---------------------------------- | ------ | ------------- | --------- 62 | |[Ceylon][] (Red Hat)|Compiles to [JS][], [Java Bytecode][JB] (**JB**)|2013Q4| *T*? | [Ceylon optional types][] 63 | | [Fantom][] | Compiles to [JS][], [JB][], .Net CLR | 2005 | *T*? | [Fantom nullable types][] 64 | | [Flow][] (Facebook) | [JS][] superset and static checker | 2014Q4 | *T*? | [Flow maybe types][] 65 | | [Kotlin][] (JetBrains)| Compiles to [JS][] and [JB][] | 2011Q3 | *T*? | [Kotlin null safety][] 66 | | [Haste][] | [Haskell][] to [JS][] compiler | @0.4.4 |[option type][]| [Haskell maybe type][] 67 | | [Swift][] (Apple) | iOS/OS X Objective-C successor | 2014Q4 |[option type][]| [Swift optional type][] 68 | 69 | (Note: 2014Q4, for example, refers to the 4th quarter of 2014). There is even discussion of introducing non-null types to [TypeScript][], the Dart peer that brings optional types and static type checking to JavaScript: 70 | 71 | - [TS issue #185][], *Suggestion: non-nullable type*. 72 | - [TS issue #1265][], *Comparison with Facebook Flow Type System*. 73 | - [TS issue #3003][], *Compile / edit time pluggable analyzers like C#* as a possible mechanism for introducing support for nullity checks. 74 | 75 | ## 3 Normative reference, terms and notation {#terms} 76 | 77 | The main normative reference for this proposal is the *ECMA [Dart Specification Standard][DSS]*, 2nd Edition (December 2014, Dart v1.6) which we abbreviate as [DSS][]. When proposing changes to the [DSS][], text that is removed will be [marked like this][del] and, new or updated text will be [marked like this][ins]. 78 | 79 | Throughout this proposal, qualified section numbers, sometimes in parentheses, e.g. ([DSS][] 16.19), will refer to the corresponding section of the named resource. 80 | 81 | We will refer to current Dart, with nullable-by-default semantics, as *classic Dart* (**DartC**) and we will use **DartNNBD** to denote Dart as adapted to conform to this proposal. In cases where it is pertinent, we will mark code samples as being [DartC][] or [DartNNBD][] code. 82 | 83 | \label{terms} 84 | We adhere to the following terminology ([DSS][] 7, "Errors and Warnings") a: _[static warning](#terms)_ is a problem reported by the static checker; _[dynamic type error](#terms)_ is a type error reported in checked mode; _[run-time error](#terms)_ is an exception raised during execution. 85 | 86 | ## 4 Impact, examples and benefits: a first look {#first-look} 87 | 88 | Among other important language design goals ([5](#goals)), this proposal has been designed so as to _minimize_ the effort involved in migrating existing code, so that [DartC][] code will require _no_ or _few textual changes_ to run with the _same behavior_ in [DartNNBD][]. 89 | 90 | Consider the following program, slightly adapted from an article on Dart types ([Bracha, 2012][Dart Optional Types]): 91 | 92 | ```dart 93 | class Point { 94 | final num x, y; 95 | Point(this.x, this.y); 96 | Point operator +(Point other) => new Point(x+other.x, y+other.y); 97 | String toString() => "x: $x, y: $y"; 98 | } 99 | 100 | void main() { 101 | Point p1 = new Point(0, 0); 102 | Point p2 = new Point(10, 10); 103 | print("p1 + p2 = ${p1 + p2}"); 104 | } 105 | ``` 106 | 107 | This code has the same behavior in [DartC][] as in [DartNNBD][]. But, in [DartNNBD][], an expression like `new Point(0, `_some nullable expression_`)` would cause a [static warning][] to be issued and a [dynamic type error][] to be raised, whereas no problems would be reported in [DartC][]. 108 | 109 | **Dart SDK libraries**. What would be the impact on the Dart SDK libraries? The **`int`** API would look textually the same in [DartNNBD][] except for the addition of 3 instances of the `?` meta type annotation, out of 44 possible places where such an addition could be made ([F.1.1](#int-nnbd)). The **`Iterable`** interface would be unchanged ([F.1.2](#iterable-nnbd)). 110 | 111 | The **benefits** of this proposal include: 112 | 113 | - An increased *potential* for the static detection of unanticipated use of `null`. 114 | - A base semantics which enables compilers (and VMs) to be much more effective at generating efficient code (e.g., code with fewer runtime checks). 115 | - Annotated code contributes to improving API documentation. 116 | 117 | ## 5 Language design goals {#goals} 118 | 119 | Language design is a process of *balancing tensions* among goals under given constraints. It is difficult to assess a language, or a language proposal, when its design principles and goals are not clearly identified. In this section, we present some of the broad language design goals for this proposal. Other goals will be presented, as relevant, in their respective parts. 120 | 121 | The group of goals presented next will be collectively referred to as **G0**; subgoals will be referred to by name, such as [G0, compatibility](#g0). 122 | 123 | \label{g0} 124 | 125 | - **Goal G0, optional types**. Specifically these two aspects, which follow from the fundamental property of Dart that _static type annotations are_ **optional** (see "Overview", [Bracha, 2012][Dart Optional Types, overview]): 126 | 127 | (a) Static type annotations, whether they include nullity meta type annotations or not, shall have _no impact on_ **production mode execution**. 128 | 129 | (b) Static type checking rules shall never prevent code from executing, and hence never coerce a developer into _adding or changing_ static (nullity) type annotations. 130 | 131 | > Comment. This is why problems reported by the static checker are warnings and not errors. The basic premise is that "the developer knows best" since he or she can see beyond the inherent limitations of the static type system. 132 | 133 | - _Maximize_ each of the following properties: 134 | 135 | - **Goal G0, utility** (of which more is said in the paragraph below). 136 | - **Goal G0, [usability][]**: the ease with which [DartNNBD][] can be learned and used. 137 | - **Goal G0, compatibility** with [DartC][]; i.e., mainly _backwards compatibility_, 138 | though the intent is also to be respectful of Dart's language design philosophy. 139 | 140 | - **Goal G0, ease migration**. _Minimize_ the effort associated with the: (a) [migration](#part-migration) of [DartC][] code to [DartNNBD][]; (b) reengineering of tooling. 141 | 142 | \label{g0-utility} 143 | The main purpose of this proposal (**[G0, utility](#g0)**) is to enable static and dynamic checks to report situations where a possibly null expression is being used but a non-null value is expected. Even in the presence of non-null types, developers could choose to declare all types as nullable (and hence be back in the realm of [DartC][]). Consequently, to the extent possible, in this proposal we will give preference to new language features that will interpret unadorned types as being non-null _by default_. 144 | 145 | ## 6 Proposal details 146 | 147 | For ease of comprehension, this proposal has been divided into parts. Each part treats a self-contained topic and generally builds upon its predecessor parts, if any. Most parts start with a brief introduction or motivation, followed by proposed feature details, then ending with a discussion and/or presentation of alternatives. 148 | 149 | - [A. Recovering non-null types](#part-non-null-types). 150 | - [B. Non-null by default](#part-nnbd)---also introduces the type operators `?` and `!`. 151 | - [C. Generics](#part-generics). 152 | - [D. Dealing with `dynamic` and missing static type annotations](#part-dynamic). 153 | - [E. Miscellaneous, syntactic sugar and other conveniences](#part-misc). 154 | - [F. Impact on core libraries](#part-libs). 155 | - [G. Migration strategy](#part-migration). 156 | 157 | ## 7 Alternatives and deliverables {#alt-and-deliverables} 158 | 159 | Alternatives, as well as implications and limitations have been addressed throughout the proposal. [Appendix I](#appendix-1-review) is a review (survey) of nullity in programming languages: from languages without `null` to strategies for coping with `null`. It establishes a broad and partly historical context for this proposal and some of its presented alternatives. 160 | 161 | Once a "critical mass" of this proposal's features have gained approval, a fully updated version of the _Dart Language Specification_ will be produced. Tooling reengineering and a preliminary experience report can be found in [Appendix II](#appendix-tooling). 162 | 163 | ## 8 Executive summary 164 | 165 | ### 8.1 Language updates and additions {#lang-changes} 166 | 167 | **Core language design decisions**: 168 | 169 | - [A.2](#non-null-types). *Drop semantic rules giving special treatment to* `null`. In particular, the static type of `null` is taken to be `Null`, not $\bot$ (while still allowing `null` to be returned for `void` functions). As a consequence, all non-`Null` class types (except `Object`, which is addressed next) lose [assignment compatibility][assignment compatible] with `null`, and hence *naturally recover* their status as *non-null types*. 170 | 171 | - [B.2](#nnbd). Create a *new class hierarchy root* named `_Anything` with only two immediate subclasses: `Object` and `Null`. This new root is internal and hence inaccessible to users. Thus, `Object` _remains the implicit upper bound_ of classes. 172 | 173 | - [B.2](#nnbd). Introduce _type operators_: 174 | - ?*T* defines the _nullable_ variant of type *T*; 175 | - !*T*, an inverse of `?`, is useful in the context of type ([C.3](#generics)) 176 | and optional parameters ([E.1.1](#opt-func-param)). 177 | 178 | - [C.3](#generics). Redefine the _default type parameter upper bound_ as `?Object`, i.e., nullable-by-default ([C.3.4](#default-type-param-bound)). Non-null type parameters extend `Object` ([C.3.3](#generic-param-non-null)). Support for generics requires no additional features. 179 | 180 | **Subordinate language design decisions**: 181 | 182 | - [B.2.4](#type-test-ambiguity). Resolution of negated type test (`is!`) syntactic ambiguity. 183 | - [B.2.5](#factory-constructors). Syntax for nullable factory constructors. 184 | - [B.2.6](#nnbd-function-sig). Syntax for nullable parameters declared using function signature syntax. 185 | - [B.3.1](#uti). Union type interoperability. 186 | - [B.3.3](#shared-type-op-semantics). Runtime representation of type operators and other shared semantics. 187 | - [B.3.5](#new-assignment-semantics). Adjusted semantics for "assignment compatible" ($\Longleftrightarrow$). 188 | - [B.3.6](#multi-members). Static semantics of members of ?T. 189 | - [B.3.7](#type-promotion). Type promotion. 190 | - [B.3.8](#lub). Type least upper bound. 191 | - [B.3.9](#null-awareoperators). Null-aware operators. 192 | - [D.2.1](#dynamic-and-type-operators). `!dynamic` is the unknown non-null type, and `?dynamic` is `dynamic`. 193 | - [D.2.2](#bang-dynamic-subtype-of). Defining `!dynamic <:` *S*. 194 | - [E.1.1](#opt-func-param). Optional parameters are nullable-by-default in function bodies only. 195 | - [E.1.2](#normalization). Normalization of type expressions. 196 | - [E.2](#sugar). Syntactic sugar and other conveniences. 197 | 198 | ### 8.2 What is unchanged? 199 | 200 | Other than the changes listed above, the semantics of [DartNNBD][] match [DartC][], most notably: 201 | 202 | - [B.3.4](#var-init). _Default variable initialization_ semantics are untouched; i.e., `null` is the value of a variable when it is not explicitly initialized. Given that `null` is only [assignment compatible][] with `Null` in [DartNNBD][], this will result in [static warning][]s and [dynamic type error][]s for uninitialized variables declared to have a non-null type. 203 | 204 | - [D.2](#dynamic). The role and semantics of `dynamic` are untouched. Thus, `dynamic` (and `?dynamic`) denote the "unknown type", supertype of all types. Also, e.g., in the absence of static type annotations or type arguments, `dynamic` is still assumed. 205 | 206 | ### 8.3 Summary of alternatives and discussions {#alternatives} 207 | 208 | **Discussions / clarifications**: 209 | 210 | - [B.4.1](#ceylon-root). Precedent: [Ceylon][]'s root is `Object` | `Null`. 211 | - [C.5.5](#generics-related-work). Generics and nullity in other languages or frameworks. 212 | - [D.3.1](#extends-bang-dynamic). Clarification of the semantics of `T extends !dynamic`. 213 | - [E.3.1](#nnbd-scope). Scope of [NNBD][] in [DartNNBD][]. 214 | - [E.3.4](#function-subtype). Subtype relation over function types unaffected by nullity. 215 | - [E.3.5](#catch-type-qualification). Catch target types and meta type annotations. 216 | - [E.3.7](#style-guide-object). Dart Style Guide on `Object` vs. `dynamic`. 217 | 218 | **Points of variance / proposal part alternatives**: 219 | 220 | - [A.3.1](#why-nn-types). Why non-null *types*? 221 | - [A.3.2](#nullable-by-default). Embracing non-null types but preserving nullable-by-default? 222 | - [B.4.2](#var-init-alt). Default initialization of non-null variables. 223 | - [B.4.3](#factory-constructor-alt). Factory constructors. 224 | - [B.4.4](#semantics-of-bang-alt). Dealing with `!Null`. 225 | - [B.4.5](#type-test-ambiguity-alt). Resolution of negated type test (`is!`) syntactic ambiguity. 226 | - [B.4.6](#type-anno-alt). Encoding `?` and `!` as metadata. 227 | - [B.4.7](#object-not-nullable-alt). Ensuring `Object` is non-null: making `Null` a root too. 228 | - [C.5.1](#nullable-type-op-alt). Loss of expressivity due to union type interoperability. 229 | - [C.5.2](#lower-bound-for-maybe). Lower bounds to distinguish nullable/maybe-nullable parameters. 230 | - [C.5.3](#type-param-not-null). Statically constraining a type parameter to be nullable but _not_ `Null`. 231 | - [C.5.4](#generics-alt). Parametric nullity abstraction. 232 | - [D.3.2](#dynamic-alt). Semantics for `dynamic`. 233 | - [D.3.3](#bang-dynamic-subtype-of-alt). Defining `!dynamic <:` *S*. 234 | - [E.3.2](#discussion-nnbd-scope). Scope of [NNBD][] in other languages or frameworks. 235 | - [E.3.3](#opt-param-alt). Optional parameters are always nullable-by-default. 236 | - [E.3.6](#local-var-analysis). Reducing the annotation burden for local variables. 237 | 238 | ### 8.4 Assessment of goals {#goal-assessment} 239 | 240 | This proposal has strictly upheld [G0, optional types](#g0), in particular, in the choices made to preserve the [DartC][] semantics: 241 | 242 | - Regarding default (non-null) variable initialization ([B.3.4](#var-init) vs. [B.4.2](#var-init-alt)), and by 243 | - Leaving `dynamic`, the unknown type, as nullable ([D.2](#dynamic) vs. [D.3.2](#dynamic-alt)). 244 | 245 | Consequently, these features also support [G0, compatibility](#g0), and hence [G0, usability](#g0)---since fewer differences relative to [DartC][], and fewer special cases in the semantic rules, make [DartNNBD][] easier to learn and use---as well as [G0, ease migration](#g0). 246 | 247 | Unavoidably, recovery of non-null types ([A.2](#non-null-types)), induces two **breaking changes** that may impact the production mode execution of existing programs that: 248 | 249 | (a) Use _reflection_ to query: the direct members of, or the supertype of, `Object` or `Null` ([B.2.1](#new-root)); or, the upper bound of a type parameter ([C.3.4](#default-type-param-bound)). 250 | (b) Perform _type tests_ of the form *e* `is Object` since this will now return false for `null`. It seems unlikely though, that fielded code would actually contain such a type test given that it is always true in [DartC][]. 251 | 252 | We have noted that breaking changes of similar magnitude are sometimes incorporated in Dart minor releases---see the [Dart CHANGELOG][]. 253 | 254 | There are **no other backwards incompatible** changes impacting _production mode execution_ ([G0, compatibility](#g0)). 255 | 256 | [Trending](#precedent) seems to indicate that there is value ([G0, utility](#g0-utility)) in having non-null types and [NNBD][] supported by languages with static type systems. This proposal helps Dart recover its non-null types and proposes adoption of [NNBD][]. The latter is the principle feature in support of [G0, ease migration](#g0); another key decision in support of ease of migration is leaving optional parameters ([E.1.1](#opt-func-param)) outside the scope of [NNBD][] ([E.3.1](#nnbd-scope)). 257 | 258 | In fact, most of the core language design decisions adopted for this proposal relate back to the choices made concerning the **scope of [NNBD][]** ([E.3.1](#nnbd-scope)). During the creation of this proposal we constantly revisited potential impacts on the scope of [NNBD][] to ensure that the proposal stayed true to Dart's overall language design philosophy. Our main point of comparison, detailing the many ways in which this proposal could have been differently crafted, is the section on the _scope of [NNBD][] in other languages or frameworks_ ([E.3.2](#discussion-nnbd-scope)). 259 | 260 | Overall, we are hopeful that this proposal has found a suitable balance between the [G0 goals](#g0) of _utility_, _usability_, _compatibility_ and _ease of migration_. 261 | -------------------------------------------------------------------------------- /doc-src/1a-non-null-types.md: -------------------------------------------------------------------------------- 1 | # Part A: Recovering non-null types {- #part-non-null-types} 2 | 3 | The purpose of this part is to "recover" Dart's non-null types, in the sense that we describe next. 4 | 5 | ## A.1 Non-null types in [DartC][] 6 | 7 | In Dart, *everything* is an object. In contrast to other mainstream languages, the term `null` refers to the null *object*, not the null *reference*. That is, `null` denotes the singleton of the `Null` class. Although built-in, `Null` is like a regular Dart class and so it is a subtype of `Object`, etc. 8 | 9 | Given that everything is an object in Dart, and in particular that `null` is an object of type `Null` as opposed to a null *reference* then, in a sense, **[DartC][] types are already non-null**. To illustrate this, consider the following [DartC][] code: 10 | 11 | ``` {.dart .numberLines} 12 | const Null $null = null; 13 | 14 | void main() { 15 | int i = null, 16 | j = $null, 17 | k = "a-string"; 18 | print("i = $i, j = $j, k = $k"); 19 | print("i is ${i.runtimeType}, j is ${j.runtimeType}"); 20 | } 21 | ``` 22 | 23 | Running the [Dart Analyzer][] results in 24 | 25 | ```shell 26 | Analyzing [null.dart]... 27 | [warning] A value of type 'Null' cannot be assigned to a variable of type 'int' (line 5, col 11) 28 | [warning] A value of type 'String' cannot be assigned to a variable of type 'int' (line 6, col 11) 29 | 2 warnings found. 30 | ``` 31 | 32 | ### A.1.1 Static checking {#dartc-static-checking} 33 | 34 | As is illustrated above, the `Null` type is unrelated to the type `int`. In fact, as a direct subtype of `Object`, `Null` is only related to `Object` and itself. Hence, the assignment of `$null` to `j` results in a [static warning][] just as it would for an instance of any other type (such as `String`) unrelated to `int` ([DSS][] 16.19, "Assignment", referring to an assignment *v* = *e*): 35 | 36 | > It is a static type warning if the static type of *e* may not be assigned to the static type of *v*. 37 | 38 | While the static type of `$null` is `Null`, the language specification has a special rule used to establish the static type of `null`. This rule makes `null` [assignment compatible][] with any type *T*, including `void` ([DSS][] 16.2, "Null"): 39 | 40 | > The static type of `null` is $\bot$ (bottom). _(Rationale) The decision to use $\bot$ instead of `Null` allows `null` to be assigned everywhere without complaint by the static checker._ 41 | 42 | Because bottom is a subtype of every type ([DSS][] 19.7, "Type Void"), `null` can be assigned to or used as an initializer for a variable of any type, without a [static warning][] or [dynamic type error][] ([DSS][] 16.19; 19.4, "Interface Types"). 43 | 44 | ### A.1.2 Checked mode execution 45 | 46 | Execution in checked mode of the program given above results in an exception being reported only for the assignment to `k`: 47 | 48 | ```shell 49 | > dart -c null.dart 50 | Unhandled exception: type 'String' is not a subtype of type 'int' of 'k'. 51 | #0 main (~/example/null.dart:6:11) 52 | ``` 53 | 54 | The assignment to `j` raises no exception because of this clause ([DSS][] 16.19, "Assignment", where *o* is the result of evaluating *e* in *v* = *e*): 55 | 56 | > In checked mode, it is a dynamic type error if *o* is not `null` and the interface of the class of *o* is not a subtype of the actual type (19.8.1) of *v*. 57 | 58 | ### A.1.3 Production mode execution 59 | 60 | Production mode execution of our sample code results in successful termination and the following output is generated: 61 | 62 | ```shell 63 | i = null, j = null, k = a-string 64 | i is Null, j is Null 65 | ``` 66 | 67 | Note that `Null` is the `runtimeType` of both `null` and `$null`; bottom is not a runtime type. 68 | 69 | ### A.1.4 Relations over types: `<<`, `<:`, and $\Longleftrightarrow$ {#def-subtype} 70 | 71 | We reproduce here the definitions of essential binary relations over Dart types found in [DSS][] 19.4, "Interface Types". We will appeal to these definitions throughout the proposal. 72 | Let *S* and *T* be types. 73 | 74 | \label{assignment-compatible} 75 | 76 | - *T* may be _assigned to_ *S*, written $T \Longleftrightarrow S$, iff either $T <: S$ or $S <: T$. (Let *T* be the static type of *e*. We will sometimes write "*e* may be assigned to *S*" when we mean that "*T* may be assigned to *S*". Given that this relation is symmetric, we will sometimes write that *S* and *T* are **assignment compatible**.) 77 | 78 | - *T* is a _subtype_ of *S*, written $T <: S$, iff $[\bot/\DYNAMIC{}]T << S$. 79 | 80 | + $T$ is _more specific than_ $S$, written $T << S$, if one of the following conditions is met: 81 | 1. $T$ is $S$. 82 | 2. T is $\bot$. 83 | 3. S is \DYNAMIC{}. 84 | 4. $S$ is a direct supertype of $T$. 85 | 5. $T$ is a type parameter and $S$ is the upper bound of $T$. 86 | 6. $T$ is a type parameter and $S$ is \cd{Object}. 87 | 7. $T$ is of the form $I$ and $S$ is of the form $I$ and: 88 | $T_i << S_i, 1 \le i \le n$ 89 | 8. $T$ and $S$ are both function types, and $T << S$ under the rules of [DSS][] 19.5. 90 | 9. $T$ is a function type and $S$ is \cd{Function}. 91 | 10. $T << U$ and $U << S$. 92 | 93 | ## A.2 Feature details: recovering non-null types {#non-null-types} 94 | 95 | To recover the general interpretation of a class type *T* as non-null, we propose the following changes. 96 | 97 | ### A.2.1 `Null` is the static type of `null`{} {#type-of-null} 98 | 99 | We drop the rule that attributes a special static type to `null`, and derive the static type of `null` normally as it would be done for any constant declared of type `Null` ([DSS][] 16.2, "Null"): "[The static type of `null` is $\bot$ (bottom). _(Rationale) The decision to use $\bot$ ... checker_.][del]". 100 | 101 | ### A.2.2 `Null` may be assigned to `void`{} {#null-for-void} 102 | 103 | As explained in [DSS][] 17.12, "Return", functions declared `void` must return _some_ value. (In fact, in production mode, where static type annotations like `void` are irrelevant, a `void` function can return _any_ value.) 104 | 105 | > Comment. Interestingly, a `void` function in [Ceylon][] is considered to have the return type `Anything`, though such functions always return `null`. Identification with `Anything` is to permit reasonable function subtyping ([[Ceylon functions][]). 106 | 107 | In [DartC][] checked mode, `void` functions can either implicitly or explicitly return `null` without a [static warning][] or [dynamic type error][]. As was mentioned, this is because the static type of `null` is taken as $\bot$ in [DartC][]. In [DartNNBD][], we make explicit that `Null` can be _assigned to_ `void`, by establishing that `Null` is more specific than `void` ([A.1.4](#def-subtype)): `Null << void`. 108 | 109 | > Comment. In a sense, this makes explicit the fact that `Null` is being treated as a "carrier type" for `void` in Dart. `Null` is a [unit type][], and hence returning `null` conveys no information. The above also fixes the slight irregularity noted in [A.1.1](#dartc-static-checking): in [DartNNBD][], no [static warning][] will result from a statement like `return $null;` used inside a `void` function (where `$null` is declared as a `const Null`). 110 | 111 | ### A.2.3 Drop other special semantic provisions for `null`{} {#null-not-special} 112 | 113 | Special provisions made for `null` in the [DartC][] semantics are dropped in [DartNNBD][], such as: 114 | 115 | - [DSS][] 16.19, "Assignment": In checked mode, it is a dynamic type error if [*o* is not null and][del] the interface of the class of *o* is not a subtype of the actual type (19.8.1) of *v*. 116 | 117 | - [DSS][] 17.12, "Return", e.g., for a synchronous function: it is a dynamic type error if [$o$ is not `null` and][del] the runtime type of $o$ is not a subtype of the actual return type of $f$. 118 | 119 | We will address other similar ancillary changes to the semantics once a "critical mass" of this proposal's features have gained approval ([7](#alt-and-deliverables)). 120 | 121 | ## A.3 Discussion 122 | 123 | As we do at the end of most parts, we discuss here topics relevant to the changes proposed in this part. 124 | 125 | ### A.3.1 Why non-null *types*? {#why-nn-types} 126 | 127 | Of course, one can appeal to programmer discipline and encourage the use of coding idioms and design patterns as a means of avoiding problems related to `null`. For one thing, an [option type][] can be realized in most languages ([including Dart][Dart pub Optional]), as can the *Null Object* pattern ([Fowler][Null Object pattern, Fowler], [C&C][Null Object pattern, C&C]). 128 | \label{NPE} 129 | Interestingly, Java 8's new `java.util.Optional` type is being promoted as a way of avoiding `null` pointer exceptions (**NPE**s) in this Oracle Technology Network article entitled, "[Tired of Null Pointer Exceptions? Consider Using Java SE 8's Optional!][Use Java 8 Optional]". 130 | 131 | Coding discipline can only go so far. Avoiding problems with `null` is best achieved with proper language support that enables mechanized tooling diagnostics (vs. manual code reviews). Thus, while the use of [option type][]s (or any other discipline/strategy for avoiding `null` described in the [survey](#appendix-1-review)) could be applicable to Dart, we do not give serious consideration to any language feature less expressive than non-null types. Given that there is generally _some_ effort involved on the part of developers who wish nullable and non-null types to be distinguished in their code, support for non-null _types_ offer the \label{ROI}**highest return on investment** (ROI), especially in the presence of [generics](#part-generics). Hence, we have chosen to base this proposal on non-null types rather than, e.g., non-null _declarator_ annotations ([I.3.1](#lang-extensions)(b), [Dart issue #5545][]), which would not impact the type system. Languages like [JML][], for example, which previously only supported nullity assertion constraints and nullity declaration modifiers, evolved to support non-null types and [NNBD][] ([I.3.1](#lang-extensions)). 132 | 133 | It is interesting to note a similar evolution in tool support for *potential "null dereference" errors* in modern (and popular) IDEs like in [IntelliJ][] and the [Eclipse JDT][]. Following conventional terminology, we will refer to such errors as [NPE][]s. As Stephan Herrmann ([Eclipse JDT][] committer) points out ([Herrmann ECE 2014, page 3][]), [NPE][]s remain the most frequent kind of exception in Eclipse. This high rate of occurrence of [NPE][]s is not particular to the [Eclipse][] code base or even to [Java][]. 134 | 135 | [Slide 5 of Stephan Herrmann's *Advanced Null Type Annotations* talk][Herrmann ECE 2014, page 5] summarizes the evolution of support for nullity analysis in the [Eclipse JDT][]. While initial analysis was ad hoc, the advent of Java 5 metadata allowed for the introduction of nullity annotations like `@NonNull` and `@Nullable`. Such annotations were used early on by the [Eclipse JDT][] and the popular Java linter [Findbugs][] to perform intraprocedural analysis. As of Eclipse Luna (4.4), support is provided for non-null *types* (and interprocedural analysis), and options exist for enabling [NNBD][] at various levels of granularity. Such an evolution (from ad hoc, to nullity declarator annotations, to non-null types), seems to be part of a general trend that we are witnessing in programming language evolution ([I.4](#modern-lang-nnbd)), towards features that enable efficient and effective static checking, so as to help uncover coding errors earlier---in particular through the use of non-null types, and in many cases, [NNBD][]. 136 | 137 | ### A.3.2 Embracing non-null types but preserving nullable-by-default? {#nullable-by-default} 138 | 139 | As an alternative to the changes proposed in this part, the nullable-by-default semantics of [DartC][] could be preserved in favor of the introduction of a _non-null_ meta type annotation `!`. Reasons for not doing this are given in the [*Motivation* section](#part-nnbd-motivation) of the next part. 140 | -------------------------------------------------------------------------------- /doc-src/1b-nnbd.md: -------------------------------------------------------------------------------- 1 | # Part B: Non-null by default (NNBD) {- #part-nnbd} 2 | 3 | ## B.1 Motivation: nullable-by-default increases migration effort {#part-nnbd-motivation} 4 | 5 | Several languages (see the [survey](#appendix-1-review)) with nullable-by-default semantics that have been subsequently retrofitted with support for non-null types have achieved this through the introduction of meta type annotations like `?` and `!`, used to indicate the nullable and non-null variants of a type, respectively. 6 | 7 | The simplest adaptation to a language with a nullable-by-default semantics like [DartC][], is to leave the default untouched and require developers to explicitly mark types as non-null using the `!` meta type annotation. 8 | 9 | ```dart 10 | // DartC extended with the meta type annotation `!' 11 | int i = null; // ok 12 | !String s = null; // error 13 | ``` 14 | 15 | Unfortunately, this would unnecessarily burden developers and negatively impact [G0, ease migration](#g0) as we explain next. An [empirical study of Java code][Chalin et al., 2008] established that 80% of declarations (having a reference type) are meant to be non-null, _by design_. An independent study reports 20 nullity annotations per KLOC ([Dietl, 2014][]; [Dietl et al., 2011][]). 16 | 17 | We expect the proportion of non-null vs. nullable declarations in Dart to be similarly high; a claim supported by anecdotal evidence---e.g., [Nystrom, 2011], and our preliminary experiments in translating the Dart SDK libraries ([Part F](#part-libs)). For example, under a variant of [DartC][] extended with `!`, `int.dart` would have to be updated with 38 `!` annotations (that's 86%) against 6 declarations left undecorated. 18 | 19 | ## B.2 Feature details: non-null by default {#nnbd} 20 | 21 | A consequence of dropping the special semantic rules for `null` ([A.2](#non-null-types)) is that all non-`Null` classes except `Object` lose [assignment compatibility][assignment compatible] with `Null`, and hence *naturally recover* their status as *non-null types*. In [DartC][], `Null` directly extends `Object` and so `Null <: Object`. This means that `Null` may still be [assigned to](#def-subtype) `Object`, effectively making `Object` nullable. We ensure that `Object` is non-null as follows. 22 | 23 | ### B.2.1 Ensuring `Object` is non-null: elect `_Anything` as a new root {#new-root} 24 | 25 | We define the internal class `_Anything` as the **new root** of the class hierarchy. Being internal, it cannot be subclassed or instantiated by users. `Object` and `Null` are immediate subclasses of `_Anything`, redeclared as: 26 | 27 | ```dart 28 | abstract class _Anything { const _Anything(); } 29 | 30 | abstract class _Basic extends _Anything { 31 | bool operator ==(other) => identical(this, other); 32 | int get hashCode; 33 | String toString(); 34 | dynamic noSuchMethod(Invocation invocation); 35 | Type get runtimeType; 36 | } 37 | 38 | class Object extends _Anything implements _Basic { 39 | const Object(); 40 | ... // Methods of _Basic are all declared external 41 | } 42 | ``` 43 | 44 | The definition of `Null` is the same as in [DartC][] except that the class extends `_Anything` and implements `_Basic`. The latter declares all members of [DartC][]'s `Object`. Note that the declaration of equality allows a `null` operand (such a definition is needed, e.g., by the [Dart Analyzer][]). 45 | 46 | > Comment. Declaring `_Anything` as a class without methods allows us to provide a conventional definition for `void` as an empty interface, realized only by `Null`: 47 | > 48 | > ```dart 49 | > abstract class void extends _Anything {} 50 | > class Null extends _Anything implements _Basic, void { /* Same as in DartC */ } 51 | > ``` 52 | 53 | The changes proposed in this subsection impact various sections of the language specification, including ([DSS][] 10, "Classes"): "Every class has a single superclass except class [`Object`][del][`_Anything`][ins] which has no superclass". 54 | 55 | As is discussed below ([B.4.1](#ceylon-root)), [Ceylon][] has a class hierarchy like the one proposed here for Dart. 56 | 57 | > Comments: 58 | > 59 | > - `Object` remains the implicit upper bound of classes (i.e., `extends` clause argument). 60 | > - Under this new hierarchy, `Null` is only [assignable to](#def-subtype) the new root, `void` and itself. 61 | 62 | ### B.2.2 Nullable type operator `?`{} {#nullable-type-op} 63 | 64 | The **nullable type operator**, **?**_T_, is used to introduce a nullable variant of a type *T*. 65 | 66 | > Comment. Like other metadata annotations in Dart, `?` is applied as a prefix. 67 | 68 | ### B.2.3 Non-null type operator `!`{} {#non-null-type-op} 69 | 70 | The **!** (bang) **non-null type operator**, can be thought of as an inverse of the nullable type operator `?`. It also acts as an identity function when applied to non-null types. 71 | 72 | ### B.2.4 Resolution of negated type test (`is!`) syntactic ambiguity {#type-test-ambiguity} 73 | 74 | Unfortunately, the choice of `!` syntax introduces an ambiguity into the grammar relative to negated type tests, such as: `o is !`*T*. The ambiguity shall be resolved in favor of the original negated type test production, requiring parentheses for a type test against a non-null type, as in `o is (!`*T*`)`. See [B.4.5](#type-test-ambiguity-alt) for further discussion and an alternative. 75 | 76 | ### B.2.5 Syntax for nullable factory constructors {#factory-constructors} 77 | 78 | It may seem unnecessary to qualify that factory constructors are non-null, but in [DartC][], a factory constructor for a class *T* is permitted to return an instance of _any_ subtype of *T*, including `null` ([DSS][] 10.6.2, "Factories"): 79 | 80 | > In checked mode, it is a dynamic type error if a factory returns a non-`null` object whose type is not a subtype of its actual return type. _(Rationale) It seems useless to allow a factory to return `null`. But it is more uniform to allow it, as the rules currently do._ 81 | 82 | In support of [G0, compatibility](#g0), we propose to extended the syntax of factory constructors so that they can be marked nullable, as is illustrated next. For further discussion and an alternative see [B.4.3](#factory-constructor-alt). 83 | 84 | ```dart 85 | // DartNNBD - part of dart.core; 86 | abstract class int extends num { 87 | external const factory ?int.fromEnvironment(String name, {int defaultValue}); 88 | ... 89 | } 90 | ``` 91 | 92 | ### B.2.6 Syntax for nullable parameters declared using function signature syntax {#nnbd-function-sig} 93 | 94 | A formal parameter can be declared by means of a function signature ([DSS][] 9.2.1, "Required Formals") as is done for `f` in: `int applyTo1(int f(int)) => f(1)`. 95 | 96 | This, in effect, declares an anonymous class type ([DSS][] 19.5, "Function Types") "that implements the class `Function` and implements a `call` method with the same signature as the function". The [NNBD][] rule also applies to such anonymous class types, so special syntax must be introduced to allow them to be marked as nullable. To declare a such a parameter as nullable, the parameter name can be *suffixed* with `?` as in: 97 | 98 | ```dart 99 | int applyTo1(int f?(int)) => f == null ? 1 : f(1); 100 | ``` 101 | 102 | This can be thought of as equivalent to: 103 | 104 | ```dart 105 | typedef int _ANON(int); 106 | int applyTo1(?_ANON f) => f == null ? 1 : f(1); 107 | 108 | ``` 109 | 110 | This syntactic extension to function signatures can only be used in formal parameter declarations, not in other places in which function signatures are permitted by the grammar (e.g., class member declarations and type aliases). 111 | 112 | > Comment. We avoid suggesting the use of `?` as a *prefix* to the function name since that could be interpreted as an implicitly (nullable) `dynamic` return type when no return type is provided. 113 | 114 | ## B.3 Semantics {#nnbd-semantics} 115 | 116 | ### B.3.1 Semantics of `?`{} {#semantics-of-maybe} 117 | 118 | #### (a) Union type interoperability {- #uti} 119 | 120 | While *union types* are not yet a part of Dart, [they have been discussed][DSC 2015/01] by the Dart standards committee, and a [proposal is anticipated][DEP 2015/03/18]. Once introduced, union types and the language features suggested by this proposal---especially the `?` type operator---will need to "interoperate" smoothly. This can be achieved by defining the nullable type operator as: 121 | 122 | > **?**_T_ = *T* | `Null` 123 | 124 | The semantics of `?` then follow naturally from this definition. While the Dart union type proposal has yet to be published, it can be safe to assume that its semantics will be *similar* to that of union types in other languages such as: 125 | 126 | - [TypeScript][], see Section 3.4 of the [TypeScript language specification][TSLS]; or, 127 | - [Ceylon][], see the language specification Section on [Ceylon union types][]. 128 | 129 | From such a semantics it follows that, e.g., `Null <: ?T` and `T <: ?T` for any *T*. 130 | 131 | #### (b) Core properties of `?`{} {- #semantics-of-q} 132 | 133 | This proposal does not *require* union types. In the absence of union types we characterize `?` by its core properties. For any types *T* and *S* that are not `void`: 134 | 135 | 1. `Null` and *T* are _more specific_ than ?*T* ([A.1.4](#def-subtype)): 136 | - `Null` << ?*T*, 137 | - *T* << ?*T*; 138 | 2. ??*T* = ?*T* (idempotence), 139 | 3. ?`Null` = `Null` (fixed point), 140 | 4. ?`dynamic` = `dynamic` (fixed point, [D.2.1](#dynamic-and-type-operators)). 141 | 5. ?*T* << *S* **iff** `Null` << *S* $\land$ *T* << *S*. 142 | 6. *T* << ?*S* $\land$ $\lnot$(`Null` << *T*) implies *T* << *S*. 143 | 144 | Equations 2 to 4 are part of the rewrite rules for the **normalization** of ?*T* expressions ([B.3.3](#shared-type-op-semantics)). 145 | 146 | > Comment. It follows from (1) that
\ 147 | > *T* << ?*S* **if** *T* << `Null` $\lor$ *T* << *S*. 148 | 149 | It is a compile-time error if `?` is applied to `void`. 150 | It is a [static warning][] if an occurrence of ?*T* is not in normal form. 151 | 152 | ### B.3.2 Semantics of `!`{} {#semantics-of-bang} 153 | 154 | When regarding ?*T* as the union type *T* | `Null`, then `!` can be seen as a projection operator that yields the non-`Null` union member *T*. For all non-null class types *T* `<: Object` 155 | 156 | - !?*T* = *T* (inverse of `?`) 157 | - !*T* = *T* (identity over non-null types) 158 | 159 | These equations are part of the rewrite rules for the **normalization** of !*T* expressions ([B.3.3](#shared-type-op-semantics)). 160 | 161 | It is a compile-time error if `!` is applied to `void`. Application of `!` to an element outside its domain is considered a _malformed_ type ([DSS][] 19.1, "Static Types") and "any use of a malformed type gives rise to a static warning. A malformed type is then interpreted as `dynamic` by the static type checker and the runtime unless explicitly specified otherwise". Alternatives are presented in [B.4.4](#semantics-of-bang-alt). 162 | 163 | > Comment. Currently in [DartNNBD][], the only user expressible type outside of the domain of `!` is `Null` since `_Anything` is not accessible to users ([B.2.1](#new-root)). 164 | 165 | ### B.3.3 Runtime representation of type operators and other shared semantics {#shared-type-op-semantics} 166 | 167 | Besides the semantic rules presented in the previous two subsections for their respective type operators, all other checked mode semantics ([static warning][]s or [dynamic type error][]s) for both `?` and `!` follow from those of [DartC][] and the semantics of [DartNNBD][] presented thus far. 168 | 169 | Type expressions involving type operators shall be represented at runtime, in normalized form ([E.1.2](#normalization), for use in: 170 | 171 | - Reflection. 172 | - Reification ([C.4](#semantics-of-generics)). 173 | - Structural type tests of function types ([E.3.4](#function-subtype)). 174 | 175 | ### B.3.4 Default initialization of non-null variables is like [DartC][] {#var-init} 176 | 177 | We make no changes to the rules regarding default variable initialization, even if a variable is statically declared as non-null. In particular, the following rule still applies ([DSS][] 8, "Variables"): "A variable that has not been initialized has the initial value `null`". 178 | 179 | > Comment. The term *variable* refers to a "storage location in memory", and encompasses local variables, library variables, instance variables, etc. ([DSS][] 8). 180 | 181 | Explicit initialization checks are extended to also address cases of implicit initialization with `null`. Thus, generally speaking, explicit or implicit initialization of a variable with a value whose static type cannot be [assigned to](#def-subtype) the variable, will result in: 182 | 183 | - [Static warning][]. 184 | - [Dynamic type error][]. 185 | - No effect on production mode execution. 186 | 187 | Rule details are given next. 188 | 189 | #### (a) Instance variables 190 | 191 | An instance variable *v* that is: 192 | 193 | 1. `final`, or 194 | 2. declared in a non-`abstract` class and for which: `null` cannot be [assigned to](#def-subtype) the (actual) type of *v*; 195 | 196 | then *v* be explicitly initialized (either from a declarator initializer, a field formal parameter, or a constructor field initialization). 197 | 198 | > Comment. Conforming to [DartC][], the above holds true for nullable `final` instance variables even if this is not strictly necessary. In (2) we disregard abstract classes since we cannot easily and soundly determine if all of its uses (e.g. as an interface, mixin or extends clause target) will result in all non-null instance variables being explicitly initialized). 199 | 200 | #### (b) Class (static) and library variables 201 | 202 | A class or library variable that is (1) `const` or `final`, or (2) declared non-null, must be explicitly initialized. 203 | 204 | > Comment. Conforming to [DartC][], the above holds true for nullable `const` or `final` variables even if this is not strictly necessary. 205 | 206 | #### (c) Local variables {#var-local-init} 207 | 208 | (1) A `const` or `final` local variable must be explicitly initialized. 209 | (2) For a non-null local variable, a [static warning][] (and a [dynamic type error][]) will result if there is a path from its declaration to an occurrence of the variable where its value is being read. If a local variable read in inside a closure, then it is assumed to be read at the point of declaration of the closure. Also see [E.3.6](#local-var-analysis). 210 | 211 | ### B.3.5 Adjusted semantics for "assignment compatible" ($\Longleftrightarrow$) {#new-assignment-semantics} 212 | 213 | Consider the following [DartNNBD][] code: 214 | 215 | ```dart 216 | ?int i = 1; // ok 217 | class C { 218 | T i1 = 1; // ok 219 | ?T i2 = 1; // should be ok 220 | } 221 | ``` 222 | 223 | The assignment of `1` to `i1` is valid because $\pg{int} \asgn T^{\pg{int}}$ 224 | 225 | $= \pg{int} \subtype T^{\pg{int}} \lor T^{\pg{int}} \subtype \pg{int}$, by def. of $\asgn$
\ 226 | $= [\bot/\DYNAMIC]\pg{int} \mst T^{\pg{int}} \lor [\bot/\DYNAMIC]T^{\pg{int}} \mst \pg{int}$, by def. of $\subtype$
\ 227 | $= \pg{int} \mst T^{\pg{int}} \lor T^{\pg{int}} \mst \pg{int}$, by simplification. 228 | 229 | Which is true since the right disjunct is an instance of [A.1.4](#def-subtype) `<<` (5). On the other hand, according to the [DartC][] definition of [assignment compatible][] described in [A.1.4](#def-subtype), a [static warning][] should be reported for the initialization of `i2` since `int` is not [assignment compatible][] with `?T`. Here is the derivation of $\pg{int} \asgn \nut{T^{\pg{int}}}$ 230 | 231 | $= \pg{int} \subtype \nut{T^{\pg{int}}} \lor \nut{T^{\pg{int}}} \subtype \pg{int}$, by def. $\asgn$
\ 232 | $= [\bot/\DYNAMIC]\pg{int} \mst \nut{T^{\pg{int}}} \lor [\bot/\DYNAMIC]\nut{T^{\pg{int}}} \mst \pg{int}$, by def. $\subtype$
\ 233 | $= \pg{int} \mst \nut{T^{\pg{int}}} \lor \nut{T^{\pg{int}}} \mst \pg{int}$, by simplification. 234 | 235 | Let us refer to the disjuncts as (L) and (R). (L) is false since `int` and $\nut{T^{\pg{int}}}$ are incomparable. As for (R): 236 | 237 | $\nut{T^{\pg{int}}} \mst \pg{int}$
\ 238 | $= \pg{Null} \mst \pg{int} \land T^{\pg{int}} \mst \pg{int}$, by [B.3.1.b](#semantics-of-q) (5).
\ 239 | $= \pg{false} \land T^{\pg{int}} \mst \pg{int}$, property of `int` and `Null`. 240 | 241 | Hence `int` is not assignable to `?T`. This seems counter intuitive: if `i2` is (at least) a nullable `int`, then it should be valid to assign an `int` to it. The problem is that the definition of [assignment compatible][] is too strong in the presence of union types. Before proposing a relaxed definition we repeat the definition of assignability given in [A.1.4](#def-subtype), along with the associated commentary from ([DSS][] 19.4): 242 | 243 | > An interface type $T$ may be assigned to a type $S$, written $T \asgn S$, iff either $T \subtype S$ or $S \subtype T$. 244 | > _This rule may surprise readers accustomed to conventional type checking. The intent of the $\asgn$ relation is not to ensure that an assignment is correct. Instead, it aims to only flag assignments that are almost certain to be erroneous, without precluding assignments that may work._ 245 | 246 | In the spirit of the commentary, we refine the definition of "[assignment compatible][]" as follows: let $T$, $S$, $V$ and $U$ be any types such that $\nut{V}$ and $\nut{U}$ are in normal form, then we define $\asgn$ by cases: 247 | 248 | 1. $T \asgn \nut{U}$ 249 | **iff** $T \asgn \pg{Null} \lor T \asgn U$, when $T$ is *not* of the form $\nut{V}$ 250 | 2. $\nut{V} \asgn S$ 251 | **iff** $\pg{Null} \asgn S \lor V \asgn S$, when $S$ is *not* of the form $\nut{U}$ 252 | 3. Otherwise the [DartC][] definition holds; i.e.,
\ 253 | $T \asgn S$ **iff** $T \subtype S \lor S \subtype T$. 254 | 255 | > Comment. It follows that $\nut{V} \asgn \nut{U}$ iff $V \asgn U$. An equivalent redefinition of, say (1), would be:
\ 256 | > $T \asgn \nut{U}$ **iff** $T \subtype \nut{U} \lor \nut{U} \subtype T \lor U \subtype T$. 257 | 258 | Under this new relaxed definition of [assignment compatible][], `i2` can be initialized with an `int` in [DartNNBD][]. Outside the context of generics, this new definition also now allows, e.g.: 259 | 260 | ```dart 261 | num n = 1.0; 262 | ?int i = n; // ok 263 | ``` 264 | 265 | ### B.3.6 Static semantics of members of ?T {#multi-members} 266 | 267 | We define the static semantics of the members of ?*T* as if it were an anonymous class with `Null` and *T* as superinterfaces. Then the rules of member inheritance and type overrides as defined in ([DSS][] 11.1.1) apply. 268 | 269 | ### B.3.7 Type promotion {#type-promotion} 270 | 271 | In the context of `if` statements, conditional expressions, and conjunction and disjunction expressions, the following type promotions shall be performed for any expression *e* of type ?*T*: 272 | 273 | | Condition | True context | False context 274 | | ------------ | -------------- | --------------- 275 | | *e* == null | *e* is `Null` | *e* is *T* 276 | | *e* != null | *e* is *T* | *e* is `Null` 277 | | *e* is *T* | *e* is *T* | - 278 | | *e* is! *T* | - | *e* is *T* 279 | 280 | 281 | This applies to function types as well. 282 | 283 | ### B.3.8 Type least upper bound {#lub} 284 | 285 | The least upper bound of `Null` and any non-`void` type *T* is ?*T*. 286 | 287 | ### B.3.9 Null-aware operators {#null-awareoperators} 288 | 289 | > Comment. TODO. 290 | 291 | ## B.4 Discussion 292 | 293 | ### B.4.1 Precedent: [Ceylon][]'s root is `Object` | `Null`{} {#ceylon-root} 294 | 295 | The [Ceylon][] language essentially has the nullity semantics established so far in this proposal but without `!`, i.e.: types are non-null by default, `?` is a (postfix) nullable meta type annotation, and the top of the [Ceylon][] type hierarchy is defined with a structure identical to that proposed in [B.2.1](#new-root) for [DartNNBD][], namely: 296 | 297 | ```dart 298 | abstract class Anything of Object | Null 299 | class Null of null extends Anything 300 | class Object extends Anything 301 | ``` 302 | 303 | Thus, [`Anything`][Ceylon `Anything` API] is defined as the *union type* of `Object` and `Null`. 304 | 305 | ### B.4.2 Default initialization of non-null variables, alternative approaches {#var-init-alt} 306 | 307 | #### (a) Preserving [DartC][] semantics is consistent with JavaScript & TypeScript 308 | 309 | Our main proposal ([B.3.4](#var-init)) preserves the [DartC][] semantics, i.e., a variable not explicitly initialized is set to `null`. In JavaScript, such variables are set to `undefined` ([ES5 8.1][]), and [TypeScript][] conforms to this behavior as well ([TSLS 3.2.6][]). 310 | 311 | For variables statically declared as non-null, some might prefer to see this proposal _mandate_ (i.e., issue a compile-time error) if the variable is not explicitly initialized (with a value assignable to its statically declared type, and hence not `null`) but this would go against [G0, optional types](#g0). 312 | 313 | In our opinion, preserving the default variable initialization semantics of [DartC][] is the only approach that is consistent with [G0, optional types](#g0). Also see [I.3.2](#language-evolution) for a discussion of issues related to soundness. Although Dart's static type system is already unsound by design ([Brandt, 2011][]), this proposal does not contribute to (increase) the unsoundness because of non-null types. [NNBD][] scope and local variables are also discussed in [E.3.2(a)](#local-var-alt). 314 | 315 | Also see [E.3.2(a)](#discussion-nnbd-scope) and [E.3.6](#local-var-analysis). 316 | 317 | #### (b) Implicit type-specific initialization of non-null variables {- #type-specific-init} 318 | 319 | In some other languages (especially in the presence of primitive types), it is conventional to have type-specific default initialization rules---e.g., integers and booleans are initialized to 0 and false, respectively. Due to our desired conformance to [G0, optional types](#g0), it is not possible to infer such type-specific default initialization from a static type annotation _alone_. On the other hand, special declarator syntax, such as (where `T` is a class type and `` represents zero or more type arguments): 320 | 321 | ```dart 322 | !T v; 323 | ``` 324 | could be treated as syntactic sugar for 325 | 326 | ```dart 327 | T v = T.DEFAULT_INIT(); 328 | ``` 329 | 330 | In production mode this would be interpreted as: 331 | 332 | ```dart 333 | var v = T.DEFAULT_INIT(); 334 | ``` 335 | 336 | Any class type `T`, for which this form of initialization is desired, would provide `DEFAULT_INIT()` as a factory constructor, e.g.: 337 | 338 | ```dart 339 | abstract class int extends num { 340 | factory int.DEFAULT_INIT() => 0; 341 | ... 342 | } 343 | ``` 344 | 345 | Although what we are proposing here effectively overloads the meaning of meta type annotation `!`, there is no ambiguity since, in an [NNBD][] context, a class type *T* is already non-null, and hence !*T*---which is not in normal form ([B.3.3](#shared-type-op-semantics))---can be interpreted as a request for an implicit type-specific initialization. This even extends nicely to handle `!T` optional parameter declarations ([E.1.1](#opt-func-param)). 346 | 347 | ### B.4.3 Factory constructors, an alternative {#factory-constructor-alt} 348 | 349 | In [B.3.2](#factory-constructors) we extended the syntax of factory constructors so that they could be marked as nullable. Allowing a factory constructor to return `null` renders _all_ `new`/`const` expressions _potentially nullable_. This is an unfortunate complication in the semantics of Dart (and hence goes against [G0, usability](#g0)). 350 | 351 | As was mentioned earlier, in [DartC][], a factory constructor for a class *T* is permitted to return an instance of _any_ subtype of *T*, including `null` ([DSS][] 10.6.2, "Factories"): "In checked mode, it is a dynamic type error if a factory returns a non-`null` object whose type is not a subtype of its actual return type. _(Rationale) It seems useless to allow a factory to return `null`. But it is more uniform to allow it, as the rules currently do_". From the statement of rationale, it seems that factory constructors have been permitted to return `null` out of a desired uniformity in the application of the semantic constraint on factory results (which is based on subtyping). 352 | 353 | Given that `Null` is no longer a subtype of every type in [DartNNBD][], we could also choose to (strictly) uphold the uniformity of the subtype constraint, thus _disallowing_ a factory _constructor_ from returning `null`---of course, factory _methods_ could be nullable. Unfortunately, this would be a breaking change impacting features of the Dart core library, in particular `const` `factory` constructors like `int.fromEnvironment()` and `String.fromEnvironment()`. Because of the `const` nature of these factories, they have proven useful in "_compile-time dead code elimination_" ([Ladd, 2013][]). We suspect that few other factory constructors return `null` other than in the context of this idiom, and those that do, could provide a non-null default return value. 354 | 355 | There has been some discussions of the possible elimination of `new` and/or `const` as constructor qualifiers (e.g., [Nielsen, 2015]), in which case the attempted distinction made here of factory constructors vs. factory methods would be moot. 356 | 357 | ### B.4.4 Dealing with `!Null`, alternatives {#semantics-of-bang-alt} 358 | 359 | In the absence of generics, `!Null` could simply be reported as a compile-time error. With generics, the issue is more challenging since we must deal with type expressions like `!T` possibly when type parameter `T` is instantiated with `Null` ([Part C](#part-generics)). 360 | 361 | While we proposed, in [B.3.2](#semantics-of-bang), to define !*T* as malformed when *T* is `Null`, alternatives include treating it as (i) $\bot$, or (ii) a distinct empty (error) type that is assignment compatible with no other type. The latter would introduce a new way of handling type errors to Dart, in contrast to the current uniform treatment of such "errored types" as malformed instead. Use of $\bot$ would also be a new feature since, to our knowledge, no type expression can be $\bot$ in [DartC][]. Hence both of these alternatives introduce extra complexity, thus decreasing [G0, usability](#g0) and increasing retooling costs ([G0, ease migration](#g0)). 362 | 363 | ### B.4.5 Resolution of negated type test (`is!`) syntactic ambiguity, an alternative {#type-test-ambiguity-alt} 364 | 365 | Syntactic ambiguity between a negated type test and a type test against a non-null type ([B.2.4](#type-test-ambiguity)) could be avoided by adopting a different symbol, such as `~`, for the non-null type operator, but `!` is conventional. It helps somewhat that there is a lexical convention (enforced by the [Dart Code Formatter][]) of writing the tokens `is` and `!` immediately adjacent to each other. It might further help if the analyzer reported a hint when the tokens `is` and `!` are separated by whitespace, inquiring (something like): "did you intend to write `o is (!`*T*`)`?". 366 | 367 | Note that there is no _class name_ *T* that can be written in a non-null type test `o is (!`*T*`)` because `!Null` is malformed and !*T* will not be in normal form otherwise ([B.3.2](#semantics-of-bang)). But as we shall see in [Part C](#part-generics), it is legal to write !*T* when *T* is a type parameter name. 368 | 369 | ### B.4.6 Encoding `?` and `!` as metadata {#type-anno-alt} 370 | 371 | Use of specialized syntax for meta type annotations `?` and `!` requires changes to Dart tooling front ends, impacting [G0, ease migration](#g0). We can _almost_ do away with such front-end changes by encoding the meta type annotations as metadata such as `@NonNull` and `@Nullable`. We write "almost" because Dart metadata annotations would first need to be (fully) extended to types through an equivalent of [JSR-308][] which extended Java's [metadata facility to types][JSR-308 explained]. Broadened support for type metadata (which was mentioned in the [DEP 2015/03/18][] meeting) could be generally beneficial since nullity type annotations are only one among a variety of useful kinds of type annotation. E.g., the [Checker Framework][], created jointly with JSR itself by the team that realized [JSR-308][], offers 20 checkers as examples, not the least of which is the [Nullness Checker][]. It might also make sense to consider *internally* representing `?` and `!` as type metadata. But then again, special status may make processing of this core feature more efficient in both tooling and runtimes. 372 | 373 | Regardless, the use of the single character meta type annotations `?` and `!` seems to have become quite common: it is certainly much shorter to type and it makes for a less noisy syntax. 374 | 375 | ### B.4.7 Ensuring `Object` is non-null: making `Null` a root too {#object-not-nullable-alt} 376 | 377 | An alternative to creating a new class hierarchy root ([B.2.1](#new-root)) is to create a class hierarchy _forest_ with two roots `Object` and `Null`. This has the advantage of being a less significant change to the class hierarchy, benefiting [G0, ease migration](#g0), though it is less conventional. 378 | 379 | ```diff 380 | class Object { 381 | const Object(); 382 | bool operator ==(other) => identical(this, other); 383 | external int get hashCode; 384 | external String toString(); 385 | external dynamic noSuchMethod(Invocation invocation); 386 | external Type get runtimeType; 387 | } 388 | 389 | - class Null { 390 | + class Null /*no supertype*/ { 391 | factory Null._uninstantiable() { 392 | throw new UnsupportedError('class Null cannot be instantiated'); 393 | } 394 | + external int get hashCode; 395 | String toString() => "null"; 396 | + external dynamic noSuchMethod(Invocation invocation); 397 | + external Type get runtimeType; 398 | } 399 | ``` 400 | 401 | Note that `dynamic` remains the top of the subtype relation. 402 | -------------------------------------------------------------------------------- /doc-src/1c-generics.md: -------------------------------------------------------------------------------- 1 | # Part C: Generics {- #part-generics} 2 | 3 | ## C.1 Motivation: enhanced generics through non-null types 4 | 5 | One of the main benefits of a non-null type system is its potential interplay with generics. It is quite useful, for example, to be able to declare a `List` of non-null elements, and know that list element access will yield non-null instances. 6 | 7 | ## C.2 Design goals for this part {#generics-design-goals} 8 | 9 | ### G1: Support three kinds of formal type parameter {- #generics-g1} 10 | 11 | [G1]: #generics-g1 12 | 13 | Support three kinds of formal type parameter: i.e., formal type parameters that constrain arguments to be 14 | 15 | 1. Non-null. 16 | 2. Nullable. 17 | 3. Either non-null or nullable. 18 | 19 | (We address whether the last two cases should be distinguished in [C.3.2](#generics-g1-2) and [C.5.2](#lower-bound-for-maybe).) 20 | 21 | ### G2: Support three kinds of type parameter expression in a class body {- #generics-g2} 22 | 23 | [G2]: #generics-g2 24 | 25 | Within the body of a generic class, we wish to be able to represent three kinds of type parameter expression for any given formal type parameter: i.e., use of a type parameter name as part of a type expression, occurring in the class body, that is 26 | 27 | 1. Non-null. 28 | 2. Nullable. 29 | 3. Matching the nullity of the argument. 30 | 31 | ### Running example {-} 32 | 33 | Defining and assessing suitable [DartNNBD][] language features in support of Goals [G1][] and [G2][] has been one of the most challenging aspects of this proposal. To help us understand the choices we face, we will use the following Dart code as a running example. Note that this code uses `/*(...)*/` comments to mark those places where we want to come up with appropriate syntax. Each of the three cases of Goal [G2][] is represented in the class body. 34 | 35 | ```dart 36 | class Box< /*(...)*/ T /*extends (...) Object*/ > { 37 | final /*(non-null)*/ T _default; // non-null (G2.1) 38 | /*(matching)*/ T value; // match nullity of type parameter T (G2.3) 39 | 40 | Box(this._default, this.value); 41 | 42 | /*(nullable)*/ T maybeNull() => // nullable (G2.2) 43 | value == _default ? null : value; 44 | 45 | /*(non-null)*/ T neverNull() => value == null ? _default : value; 46 | } 47 | ``` 48 | 49 | Thus, `Box<`*U*`>.value` would have the same nullity as *U*. For example, `Box.value` would be of type `?int` and `Box.value` of type `String`. As defined above, `Box<`*U*`>.maybeNull()` returns `null` when `value` matches `_default`, even if *U* is non-null. Finally, `Box<`*U*`>.neverNull()` always returns a non-null value regardless of the nullity of *U*. 50 | 51 | ## C.3 Feature details: generics {#generics} 52 | 53 | We now work through the three cases of Goal [G1][] in reverse order. 54 | 55 | ### C.3.1 Maybe-nullable formal type parameter, case [G1][].3 {#generic-param-maybe-null} 56 | 57 | Here is an illustration of the base syntax (without any syntactic sugar or abbreviations) for the maybe-nullable formal type parameter case (code inessential to presentation has been elided, "`...`"): 58 | 59 | ```dart 60 | // DartNNBD 61 | class Box { 62 | final !T _default; // non-null (G2.1) 63 | T value; // nullity matching parameter (G2.3) 64 | ?T maybeNull() => ...; // nullable (G2.2) 65 | ... 66 | } 67 | ``` 68 | 69 | ### C.3.2 Nullable formal type parameter, case [G1][].2 {#generics-g1-2} 70 | 71 | Given that Dart generics are covariant and that `T <: ?T`, it would be a significant departure from the current Dart semantics if we were to define static checking rules *requiring* that a type argument be nullable while rejecting non-null arguments. Thus, we propose that cases [G1][].2 and [G1][].3 be indistinguishable in [DartNNBD][]. For an alternative, see [C.5.2](#lower-bound-for-maybe). 72 | 73 | ### C.3.3 Non-null formal type parameter, case [G1][].1 {#generic-param-non-null} 74 | 75 | For a non-null formal type parameter `T` we simply have `T` extend `Object`; again, here is the syntax without any sugar or abbreviations: 76 | 77 | ```dart 78 | // DartNNBD 79 | class Box { 80 | final !T _default; // non-null (G2.1) 81 | T value; // nullity matching parameter (G2.3) 82 | ?T maybeNull() => ...; // nullable (G2.2) 83 | ... 84 | } 85 | ``` 86 | 87 | > Comment. Given that `T` is non-null, the use of `!` could be dropped in the body. 88 | 89 | ### C.3.4 Default type parameter upper bound is `?Object`{} {#default-type-param-bound} 90 | 91 | When no explicit upper bound is provided for a type parameter it is assumed to be `?Object`, thus providing clients of a generic type the most flexibility in instantiating parameters with either a nullable or non-null type (cf. [E.3.2](#discussion-nnbd-scope)). The following are equivalent: 92 | 93 | ```dart 94 | // DartNNBD 95 | class Box {...} 96 | class Box {...} // Implicit upper bound of ?Object. 97 | ``` 98 | 99 | ## C.4 Semantics {#semantics-of-generics} 100 | 101 | While the static and dynamic semantics of generics follow from those of [DartC][] and the semantics of [DartNNBD][] introduced in the previous parts, there are quite a few alternative ways of dealing with certain aspects of generics. These are presented in the next section. 102 | 103 | ## C.5 Discussion 104 | 105 | ### C.5.1 Loss of expressivity due to union type interoperability, an alternative {#nullable-type-op-alt} 106 | 107 | One caveat of "future proofing" the nullable type operator ?*T*, so that its semantics are compatible with the union type *T* | `Null` ([B.3.1](#uti)), is that we lose the ability to statically constrain a generic type parameter to be nullable but _not_ `Null`---we discuss _why_ we might want to do this in [C.5.3](#type-param-not-null). We lose this ability because ?*T* is not a type _constructor_, which would yield a unique (tagged) type, but rather just a type _operator_ mapping *T* to the equivalent of the (untagged) union type *T* | `Null`. Thus, e.g., no distinction is made between `Null` and `?Null`. 108 | 109 | We could alternatively define ?*T* as a type constructor (as if it were introducing a new type like `_$Nullable<`*T*`>`), orthogonal to union types, but there seems to be little to justify this complexity---future interoperability with union types seems more important and would be much more supportive of [G0, usability](#g0) and [G0, ease migration](#g0). 110 | 111 | ### C.5.2 Lower bounds to distinguish nullable/maybe-nullable parameters {#lower-bound-for-maybe} 112 | 113 | The [Checker Framework][] supports case [G1][].2 (nullable type parameter) distinctly from [G1][].3 (maybe-nullable type parameter) by allowing a type parameter lower bound to be defined ([Checker Framework Manual, 23.1.2][Checker Framework generics]) in addition to an upper bound (via `extends`). This is a natural fit for [Java][] since the language already has some support for lower bounds through [lower bounded wildcards][Java, lower bounded wildcards]. 114 | 115 | Without introducing general support for lower bounds, such an approach could be adopted for [DartNNBD][] as well. In our notation, it would look like this: `class Box`, which would require an argument *U* to satisfy `?T <: `*U* ` <: ?Object`, which is only possible if *U* is nullable. 116 | 117 | ### C.5.3 Statically constraining a type parameter to be nullable but _not_ `Null`{} {#type-param-not-null} 118 | 119 | Consider the following code: 120 | 121 | ```dart 122 | // DartNNBD 123 | class C { List list; ... } 124 | var c = new C(); 125 | ``` 126 | 127 | In the current form of the proposal, when a type parameter `T` is instantiated with `Null` then `!T` is considered malformed ([B.3.2](#semantics-of-bang)), as is the case for the type of `c.list` from the code sample above. Ideally, we would like to statically constrain `T` so that it cannot be `Null`. This would inform the clients of such a generic class that `T` should not be instantiated with `Null` and if it is, then a [static warning][] could be reported at the earliest point possible, i.e., instantiation expressions like `new C()`. 128 | 129 | It is possible to statically avoid malformed types that arise from such `!T` type expressions. One way is to adopt a completely different semantics for ?*T* as was presented in [C.5.1](#nullable-type-op-alt). Another approach is to make use of type parameter lower bounds using syntax similar to what was presented in [C.5.2](#lower-bound-for-maybe): e.g., `class Box` would constrain an argument *U* to satisfy `T <: `*U* ` <: ?Object`. The absence of an explicit lower-bound qualifier would be interpreted as `!`. 130 | 131 | ### C.5.4 Parametric nullity abstraction, an alternative approach to generics {#generics-alt} 132 | 133 | There are a few alternatives to the proposal of [C.3](#generics) for handling generics. We mention only one here. It consists of broadening the scope of the [NNBD][] rule to encompass type parameter occurrences inside the body of a generic class; i.e., an _undecorated_ occurrence of a type parameter would _always_ represent a non-null type. Such an alternative is best introduced by an example covering cases [G1][].2 and [G1][].3: 134 | 135 | ```dart 136 | // DartNNBD 137 | class Box<&T extends ?Object> { 138 | final T _default; // non-null (G2.1) 139 | &T value; // nullity matching parameter (G2.3) 140 | ?T maybeNull() => ...; // nullable (G2.2) 141 | ... 142 | } 143 | ``` 144 | 145 | One can think of the type parameter decorator `&` as a symbol acting as a "formal parameter" for the nullity of the corresponding type argument---i.e., as a form of *parametric nullity abstraction*---which will be instantiated as either `?` or `!`. (This is similar in spirit to the [Checker Framework qualifier parameters][].) Thus, `Box` could be instantiated as `Box` or `Box`, with `&` denoting `?` and (an implicit) `!`, respectively. 146 | 147 | Case [G1][].1, for a non-null type parameter, could be written as `class Box<&T extends Object> {...}` or more simply as `class Box {...}`. 148 | 149 | The **main advantage** of this approach is that it upholds *nullity notational consistency* (NNC). That is, just like for class names, 150 | 151 | - An _undecorated_ type parameter name *T* represents a non-null type ([G2][].1), 152 | - ?*T* is its nullable variant ([G2][].2), and 153 | - &*T* matches the nullity of the corresponding type argument ([G2][].3). 154 | 155 | The **main disadvantage** of this alternative is that it introduces a new concept (parametric nullity abstraction) which increases the complexity of the language, impacting [G0, usability](#g0) as well as [G0, ease migration](#g0). Code migration effort is especially impacted because, in practice, case [G2][].3 is most frequent; hence, in porting [DartC][] code to [DartNNBD][], most type parameter uses would need to be annotated with `&` vs. no annotation for our main alternative ([C.3](#generics)). 156 | 157 | ### C.5.5 Generics and nullity in other languages or frameworks {#generics-related-work} 158 | 159 | #### (a) Default type parameter upper bound {-} 160 | 161 | As we have done here, the [Nullness Checker][] of the [Checker Framework][] has `@Nullable Object` as the implicit upper bound for type parameters, following its general [CLIMB-to-top][Checker Framework CLIMB-to-top] principle (which is further discussed in [E.3.2](#discussion-nnbd-scope)). [Ceylon][]'s implicit type parameter upper bound is `Anything`, i.e., `Object | Null`, which is also nullable. 162 | 163 | #### (b) Nullity polymorphism {-} 164 | 165 | Because Java generics are invariant, the [Checker Framework][] [Nullness Checker][] originally resorted to defining a special annotation to handle some common cases of polymorphism in type parameter nullities. E.g., 166 | 167 | ```Java 168 | @PolyNull T m(@PolyNull Object o) { ... } 169 | ``` 170 | 171 | The above constrains the return type of `m` to have a nullity that matches that of `o`. Since February 2015, a new form of polymorphism was introduced into the [Checker Framework][], namely the [qualifier parameters][Checker Framework qualifier parameters] mentioned in [C.5.3](#generics-alt). 172 | 173 | #### (c) [Ceylon][] cannot represent [G2][].1 {-} 174 | 175 | It is interesting to note that case [G2][].1 cannot be represented in [Ceylon][] due to the absence of a non-null type operator `!`: 176 | 177 | ```dart 178 | // Ceylon 179 | class Box { 180 | final T _default; // can't enforce non-null; fall back to nullity matching param. 181 | T value; // nullity matching parameter (G2.3) 182 | ?T maybeNull() => ...; // nullable (G2.2) 183 | ... 184 | } 185 | ``` 186 | -------------------------------------------------------------------------------- /doc-src/1d-dynamic.md: -------------------------------------------------------------------------------- 1 | # Part D: Dealing with `dynamic` and missing static type annotations {- #part-dynamic} 2 | 3 | ## D.1 Type `dynamic` in [DartC][] 4 | 5 | In [DartC][], `dynamic` 6 | 7 | - "denotes the _unknown type_" ([DSS][] 19.6, "Type dynamic"), and 8 | - is a supertype of all types ([DSS][] 19.7, "Type Void"). 9 | 10 | The type `dynamic` is used/assumed when, e.g.: 11 | 12 | - A type is malformed ([DSS][] 19.1, "Static Types"). 13 | - No static type annotation is provided, or type arguments are missing ([DSS][] 19.6, "Type dynamic"). 14 | - An incorrect number of type arguments are provided for a generic class ([DSS][] 19.8, "Parameterized Types"). 15 | 16 | ## D.2 Feature details: dynamic {#dynamic} 17 | 18 | The [DartC][] role and static and dynamic semantics of `dynamic` are preserved in [DartNNBD][]. 19 | 20 | ### D.2.1 `!dynamic` is the unknown non-null type, and `?dynamic` is `dynamic`{} {#dynamic-and-type-operators} 21 | 22 | The authors of [Ceylon][] suggest that its `Anything` type [can be interpreted][Ceylon important types explained] as a union of all possible types. Such an interpretation leads to a natural understanding of the meaning of `dynamic` possibly decorated with the type operators `?` and `!`: 23 | 24 | - `dynamic`, _the_ unknown type, can be interpreted as the union of all types, and hence the supertype of all types. 25 | - `!dynamic` can be interpreted as the union of all _non-null_ types, and hence a supertype of all non-null types. 26 | - `?dynamic` = `dynamic` | `Null` = `dynamic`. 27 | 28 | Thus, `T << !dynamic` precisely when `T << Object` ([A.1.4](#def-subtype)). It follows that `T <: !dynamic` for any class type *T* other than `Null` and `_Anything`. 29 | 30 | > Comment. From another perspective, we can say that `!dynamic` represents an unknown non-null type rooted at `Object`, and `?dynamic` represents an unknown type rooted at `_Anything`. 31 | 32 | ### D.2.2 Defining `!dynamic <:` *S* {#bang-dynamic-subtype-of} 33 | 34 | Let $T$ and $S$ be normalized types ([E.1.2](#normalization)). We introduce, $\botObject$ to represent the bottom element of the non-null type subhierarchy and add the following as one of the conditions to be met for $T << S$ to hold ([A.1.4](#def-subtype)): 35 | 36 | > $T$ is $\botObject$ and $S << \cd{Object}$. 37 | 38 | We refine `<:` in the following backwards compatible manner: $T <: S$ iff 39 | 40 | > $[\bot/\DYNAMIC{}]U << S$ where $U = [\botObject/!\DYNAMIC{}]T$. 41 | 42 | See [D.3.3](#bang-dynamic-subtype-of-alt) for a discussion and alternative. 43 | 44 | ## D.3 Discussion 45 | 46 | ### D.3.1 Clarification of the semantics of `T extends !dynamic`{} {#extends-bang-dynamic} 47 | 48 | As a point of clarification, we note that a generic class declared with a type parameter `T extends !dynamic`: 49 | 50 | - is equivalent to `T extends Object`, except that; 51 | - for the purpose of static checking, *T* is treated as an unknown type. 52 | 53 | This is semantically consistent with the manner in which `T extends dynamic` is treated in [DartC][]. 54 | 55 | ### D.3.2 Semantics for `dynamic`, an alternative {#dynamic-alt} 56 | 57 | The main alternative relevant to this part, consists of interpreting an undecorated occurrence of `dynamic` as `!dynamic`. This would broaden the scope of the [NNBD][] rule to encompass `dynamic`. 58 | 59 | This corresponds to the choice made in the [Kotlin][] language which has types `Any` and `Any?` as representative of "any non-null type", and "any type", respectively. Notice how the unadorned type `Any` is non-null. 60 | 61 | The main disadvantage of this alternative is that [static warning][]s could be reported for programs without any static type annotations---such as for the statement `var o = null`, because the static type of `o` would be `!dynamic`. This goes contrary to [G0, optional types](#g0). 62 | 63 | ### D.3.3 Defining `!dynamic <:` *S*, an alternative {#bang-dynamic-subtype-of-alt} 64 | 65 | The [DartC][] definition of the subtype relation ([A.1.4](#def-subtype)) states that *S* `<:` *T* iff 66 | 67 | > $[\bot/\DYNAMIC{}]S << T$. 68 | 69 | Replacing `dynamic` by $\bot$ ensures that expressions having the static type `dynamic` can "be assigned everywhere without complaint by the static checker" ([DSS][] 16.2, "Null"), and that `dynamic` is a valid type argument for any type parameter. 70 | 71 | The refined definitions of `<<` and `<:` given in [D.2.2](#bang-dynamic-subtype-of) allows `!dynamic` to be: 72 | 73 | - Assigned everywhere a non-`Null` type is expected without complaint by the static checker, and; 74 | - Used as a valid type argument for any non-`Null` type parameter. 75 | 76 | Introducing a new bottom element for the `Object` subhierarchy most accurately captures our needs thought it renders the semantics more complex, decreasing [G0, usability](#g0) and increasing tool reengineering costs. 77 | 78 | An alternative, allowing us to avoid this extra complexity, is to treat `!dynamic` simply as $\bot$. What we lose, are [static warning][]s and/or [dynamic type error][]s when: an expression of the static type `!dynamic` is assigned to variable declared as `Null` and, when `!dynamic` is used as a type argument for a `Null` type parameter. But such uses of `Null` are likely to be rare. 79 | -------------------------------------------------------------------------------- /doc-src/1e-misc.md: -------------------------------------------------------------------------------- 1 | # Part E: Miscellaneous, syntactic sugar and other conveniences {- #part-misc} 2 | 3 | ## E.1 Feature details: miscellaneous 4 | 5 | In this section we cover some features, and present features summaries, that require concepts from all of the previous parts. 6 | 7 | ### E.1.1 Optional parameters are nullable-by-default in function bodies only {#opt-func-param} 8 | 9 | Dart supports positional and named optional parameters, as illustrated here: 10 | 11 | ```dart 12 | int f([int i = 0]) => i; // i is an optional positional parameter 13 | int g({int j : 0}) => j; // j is an optional named parameter 14 | ``` 15 | 16 | Within a function's body, its optional parameters are naturally nullable, since they are initialized to `null` when no default value is provided and corresponding optional arguments are omitted at a point of call. I.e., `null` is used as a default mechanism by which missing optional arguments can be _detected_. 17 | 18 | We adopt a *dual view* for the types of optional parameters as is explained next. Suppose that an optional parameter `p` is declared to be of the normalized type *T* ([E.1.2](#normalization)): 19 | 20 | (a) **Within the scope of the function's body**, `p` will have static type: 21 | 22 | - *T* if `p`: 23 | - is _explicitly_ declared non-null---i.e., *T* is !*U* for some *U*; 24 | - has no meta type annotation, and has a non-null default value (see [E.1.1.1](#non-null-init)); 25 | - is a field parameter (see [E.1.1.2](#field-param)). 26 | - ?*T* otherwise. (Note that if *T* has type arguments, then the 27 | interpretation of the nullity of these type arguments is not affected.) 28 | 29 | (b) **In any other context**, the type of `p` is *T*. 30 | 31 | \label{guideline} 32 | This helps enforce the following **guideline**: from a caller's perspective, an optional parameter can either be _omitted_, or given a value matching its declared type. 33 | 34 | > Comments: 35 | > 36 | > - E.g., one can invoke `f`, defined above, as either `f()` or `f(1)`, but `f(null)` would result in a [static warning][] and [dynamic type error][]. 37 | > - Just like for any other declaration, an optional parameter can be marked as nullable. So `f([?int j])` would permit `f(null)` without warnings or errors. 38 | > - Explicitly marking an optional parameter as non-null, e.g., `int h([!int i = 0]) => i`, makes it non-null in both views. But, if a non-null default value is not provided, then a [static warning][] and [dynamic type error][] will be reported. 39 | > - *T*, the type of `p`, might implicitly be `dynamic` if no static type annotation is given ([D.2](#dynamic)). By the rules above, `p` has type `?dynamic`, i.e., `dynamic` ([D.2.1](#dynamic-and-type-operators)), in the context of the declaring function's body. Hence, a caveat is that we cannot declare `p` to have type `dynamic` in the function body scope and type `!dynamic` otherwise. 40 | > - The dual view presented here is an example of an application of [G0, utility](#g0-utility). This is further discussed, and an alternative is presented, in [E.3.3](#opt-param-alt). 41 | > - Also see [E.3.4](#function-subtype) for a discussion of function subtype tests. 42 | 43 | #### E.1.1.1 Optional parameters with non-null initializers are non-null {#non-null-init} 44 | 45 | In Dart, the initializer of an optional parameter must be a compile time constant ([DSS][] 9.2.2). Thus, in support of [G0, ease migration](#g0), an optional parameter with a non-null default value is considered non-null. 46 | 47 | #### E.1.1.2 Default field parameters are single view {#field-param} 48 | 49 | Dart field constructor parameters can also be optional, e.g.: 50 | 51 | ```dart 52 | class C { 53 | num n; 54 | C([this.n]); 55 | C.fromInt([int this.n]); 56 | } 57 | ``` 58 | 59 | While `this.n` may have a type annotation (as is illustrated for the named constructor `C.fromInt()`), the notion of dual view does not apply to optional field parameters since they do not introduce a new variable into the constructor body scope. 60 | 61 | ### E.1.2 Normalization of type expressions {#normalization} 62 | 63 | A _normalized_ type expression has no _superfluous_ applications of a type operator ([B.3.1](#semantics-of-maybe), [B.3.2](#semantics-of-bang)). 64 | 65 | Let *P* be a type parameter name and *N* a non-null class type, *N* `<: Object`. In all contexts where [NNBD][] applies ([E.3.1](#nnbd-scope)), the following type expressions, used as static type annotations or type arguments, are in _normal form_: 66 | 67 | - *N*, and ?*N* 68 | - *P*, ?*P*, and !*P* 69 | - `dynamic` and `!dynamic` 70 | - `Null` 71 | 72 | In the context of an optional function parameter `p` as viewed from within the scope of the declaring function body ([E.1.1](#opt-func-param)(a)), the following is also a normal form (in addition to the cases listed above): !*N*. 73 | 74 | > Comment. Excluded are `void`, to which type operators cannot be applied ([B.3.1](#semantics-of-maybe), [B.3.2](#semantics-of-bang)), `?dynamic`, `?Null` and various repeated and/or canceling applications of `?` and `!` ([B.3](#nnbd-semantics)). 75 | 76 | ## E.2 Feature details: syntactic sugar and other conveniences {#sugar} 77 | 78 | We define various syntactic sugars and other syntactic conveniences in this section. Being conveniences, they are **not essential to the proposal** and their eventual adoption may be subject to an "applicability survey", in particular through analysis of existing code. 79 | 80 | ### E.2.1 Non-null `var`{} 81 | 82 | While `var x` introduces `x` with static type `dynamic`, we propose that `var !x` be a shorthand for `!dynamic x`. Note that this shorthand is applicable to all kinds of variable declaration as well as function parameters. 83 | 84 | ### E.2.2 Formal type parameters 85 | 86 | In [C.3.4](#default-type-param-bound) we defined the default type parameter upper bound as `?Object`; i.e., `class Box` is equivalent to `class Box`. We define `class Box` as a shorthand for `class Box`. Note that `!` is used as a _suffix_ to `T`; though it is a meta type annotation _prefix_ to the implicit `Object` type upper bound. 87 | 88 | > Comment. We avoid suggesting `class Box` as a sugar because it opens the door to `class Box` and `class Box`. The latter is obviously be an error, and for novices the former might lead to confusion about the meaning of an undecorated type parameter `class Box` (which could quite reasonably arise if there is a lack of understanding of the scope of the [NNBD][] rule). Also, `class Box` would conflict with the use of the same notation for the purpose of excluding `Null` type arguments ([C.5.3](#type-param-not-null)). 89 | 90 | 91 | ### E.2.3 Non-null type arguments 92 | 93 | We define `!` as a shorthand for `!dynamic` when used as a type argument as in 94 | 95 | ```dart 96 | List listOfNullableAny = ... 97 | List listOfNonnullAny = ... 98 | ``` 99 | 100 | ### E.2.4 Non-null type cast 101 | 102 | The following extension of type casts ([DSS][] 16.34, "Type Cast") allows an expression to be projected into its non-null type variant, if it exists. Let *e* have the static type *T*, then *e* `as! Null` has static type !*T*. 103 | 104 | > Comments: 105 | > 106 | > - If *T* is outside the domain of `!`, then !*T* is malformed ([B.3.2](#semantics-of-bang)). 107 | > - Syntactic ambiguity, between `as!` and a cast to a non-null type !*T*, is addressed as it was for type tests ([B.2.4](#type-test-ambiguity)). 108 | > - In the presence of union types, `as!` might be generalized as follows. If the static type of *e* is the (normalized) union type *U* | *T*, then the static type of *e* `as!` *U* could be defined as *T*. 109 | 110 | ## E.3 Discussion 111 | 112 | ### E.3.1 Scope of [NNBD][] in [DartNNBD][] {#nnbd-scope} 113 | 114 | We clarify here the scope of [NNBD][] as defined in this proposal. This will be contrasted with the scope of [NNBD][] in other languages or frameworks in ([E.3.2](#discussion-nnbd-scope)). 115 | 116 | (a) The [NNBD][] rule states that for _all_ class types *T* `<: Object`, it is false that `Null` can be _assigned to_ *T* ([A.1.4](#def-subtype)). This includes class types introduced via function signatures in the context of a 117 | 118 | - Formal parameter declaration---these are anonymous class types 119 | ([B.2.6](#nnbd-function-sig)). 120 | - `typedef`---these are named, possibly generic, class types 121 | ([DSS][] 19.3, "Type Declarations"). 122 | 123 | Thus *T*, unadorned with any type operator, (strictly) represents 124 | instances of *T* (excluding `null`). 125 | 126 | (b) The [NNBD][] rule applies to **class types** _only_. In particular, it does **not** apply to: 127 | 128 | - Type parameters ([Part C](#part-generics)). 129 | - Implicit or explicit occurrences of `dynamic` ([D.2](#dynamic)). 130 | 131 | (c) The [NNBD][] rule applies in _all_ contexts where a class type is _explicitly_ given, _except one_: static type annotations of optional function parameters as viewed from within the scope of the declaring function's body ([E.1.1](#opt-func-param)). 132 | 133 | ### E.3.2 Scope of [NNBD][] in other languages or frameworks {#discussion-nnbd-scope} 134 | 135 | In contrast to this proposal, the scope of the [NNBD][] rule in other languages or frameworks often has more exceptions. This is the case for [Spec#][] ([Fahndrich and Leino, 2003][]), [JML][] ([Chalin et al., 2008][]) and Java enhanced with nullity annotations from the [Checker Framework][]. Next, we compare and contrast [DartNNBD][] with the latter, partly with the purpose of justifying the language design decisions made in this proposal, and implicitly for the purpose of presenting potential alternatives for [DartNNBD][]. 136 | 137 | The Java [Checker Framework][] has a principle named [CLIMB-to-top][Checker Framework CLIMB-to-top] which, in the case of the [Nullness Checker][], means that types are interpreted as _nullable-by-default_ in the following contexts: 138 | 139 | - Casts, 140 | - Locals, 141 | - Instanceof, and 142 | - iMplicit (type parameter) Bounds 143 | 144 | (CLIMB). We adhere to this principle for _implicit_ type parameter bounds ([C.3.4](#default-type-param-bound)) and discuss other cases next. 145 | 146 | #### (a) Local variables {- #local-var-alt} 147 | 148 | When retrofitting a strongly (mandatorily) typed nullable-by-default language (like Java) with [NNBD][] it is common to relax [NNBD][] for local variables since standard flow analysis can determine if a local variable is potentially `null` or not, and to do otherwise would result in the need to annotate many local variables as nullable. Unfortunately, excluding local variables from the scope of [NNBD][] is at the cost of loss of a form of _referential transparency_: consider the following declaration 149 | 150 | ```dart 151 | List guestList; 152 | ``` 153 | 154 | Is `guestList` nullable? In the [Checker Framework][], it is not possible to tell without knowing the context: `guestList` is [NNBD][] if this is a (package) field declaration, but nullable if it is a local variable. 155 | 156 | In contrast, static type annotations are optional in Dart, and a common idiom is to omit them for local variables. This idiom is in fact prescribed in the [Dart Style Guide][] section on [type annotations][Dart Style Guide, var]: 157 | 158 | > PREFER using `var` without a type annotation for local variables. 159 | 160 | In light of this idiom, if a developer goes out of his or her way to write an explicit static type annotation, then we believe that the type should be interpreted literally; it is for this reason that we have chosen to include local variable declarations in the scope of [NNBD][] ([B.3.4](#var-init), [B.4.2](#var-init-alt)(a)). As a benefit, we retain referential transparency for all ([non-optional](#opt-func-param)) variable declaration kinds---in particular instance variables and local variables. 161 | 162 | As applied to local variables, the [NNBD][] rule of this proposal may result in extra warnings when [DartC][] code is migrated to [DartNNBD][], but such warnings will _not_ prevent the code from being executed in production mode---in strongly typed languages like Java, such migrated code would simply _not run_, and so our approach would not be a realistic alternative. Also, in the case of Dart code migration, tooling can contribute to the elimination of such warnings by automatically annotating explicitly typed local variables determined to be nullable ([G0, ease migration](#g0)). The strategy proposed in [E.3.6](#local-var-analysis) can also help reduce warnings. 163 | 164 | #### (b) Type tests {-} 165 | 166 | In [DartC][], the type test expression *e* `is` *T* holds only if the result of evaluating *e* is a value *v* that is an instance of *T* ([DSS][] 16.33, "Type Test"). Hence, in [DartNNBD][], this naturally excludes `null` for all *T* `<: Object`. 167 | 168 | #### (c) Type casts {-} 169 | 170 | Out of the 150K physical [Source Lines Of Code (SLOC)][sloccount] of the Dart SDK libraries, there are only 30 or so occurrences of the `as` operator and most clearly assume that their first operand is non-null. Based on such a usage profile, and for reasons similar to those given for local variables (i.e., explicitly declared types interpreted literally), we have chosen to include Dart type casts in the scope of the [NNBD][] rule. 171 | 172 | #### Broad applicability of [NNBD][] rule for [DartNNBD][] {-} 173 | 174 | While balancing all [G0](#g0) language design goals, we have chosen to make the [NNBD][] rule as broadly applicable as possible, thus making the language simpler and hence increasing [G0, usability](#g0). 175 | 176 | ### E.3.3 Optional parameters are always nullable-by-default, an alternative {#opt-param-alt} 177 | 178 | The "dual view" semantics proposed above ([E.1.1](#opt-func-param)) for optional parameters is an example of a language design feature which is slightly more complex (and hence penalizes [G0, usability](#g0)) but which we believe offers more utility ([G0, utility](#g0-utility)). A simpler alternative is to adopt (a) as the sole view: i.e., optional parameters would be nullable-by-default in all contexts. 179 | 180 | ### E.3.4 Subtype relation over function types unaffected by nullity {#function-subtype} 181 | 182 | In contexts were a function's type might be used to determine if it is a subtype of another type, then optional parameters are treated as [NNBD][] (view [E.1.1](#opt-func-param)(b)). But as we explain next, whether optional parameter semantics are based on a "dual" ([E.1.1](#opt-func-param)) or "single" ([E.3.3](#opt-param-alt)) view, this will have no impact on subtype tests. 183 | 184 | Subtype tests of function types ([DSS][] 19.5 "Function Types") are structural, in that they depend on the types of parameters and return types ([DSS][] 6, "Overview"). Nullity type operators have no bearing on function subtype tests. This is because the subtype relation over function types is defined in terms of the "assign to" ($\Longleftrightarrow$) relation over the parameter and/or return types. The "assign to" relation ([A.1.4](#def-subtype)), in turn, is unaffected by the nullity: if types *S* and *T* differ only in that one is an application of `?` over the other, then either *S* `<:` *T* or *T* `<:` *S* and hence *S* $\Longleftrightarrow$ *T*. Similar arguments can be made for `!`. 185 | 186 | ### E.3.5 Catch target types and meta type annotations {#catch-type-qualification} 187 | 188 | The following illustrates a try-catch statement: 189 | 190 | ```dart 191 | class C {} 192 | main() { 193 | try { 194 | ... 195 | } on C catch (e) { 196 | ... 197 | } 198 | } 199 | ``` 200 | 201 | Given that `null` cannot be thrown ([DSS][] 16.9), it is meaningless to have a catch target type qualified with `?`; a [static warning][] results if `?` is used in this way. Any such qualification is ignored at runtime. Note that because meta type annotations are reified ([C.4](#semantics-of-generics)), they can be meaningfully applied to catch target type arguments as is illustrated above. 202 | 203 | ### E.3.6 Reducing the annotation burden for local variables, an alternative {#local-var-analysis} 204 | 205 | This section expands on [B.3.4.c](#var-local-init).2. We propose as an alternative that standard read-before-write analysis be used for non-null _local variables_ without an explicit initializer, to determine if its default initial value of `null` has the potential of being read before the variable is initialized. 206 | 207 | Consider the following illustration of a common coding idiom: 208 | 209 | ```dart 210 | int v; // local variable left uninitialized 211 | if (...) { 212 | // possibly nested conditionals, each initializing v 213 | } else { 214 | // possibly nested conditionals, each initializing v 215 | } 216 | // v is initialized to non-null by this point 217 | ``` 218 | 219 | Without the feature described in this subsection, `v` would need to be declared nullable. 220 | 221 | ### E.3.7 Dart Style Guide on `Object` vs. `dynamic`{} {#style-guide-object} 222 | 223 | The [Dart Style Guide][] recommends [DO annotate with `Object` instead of `dynamic` to indicate any object is accepted][Dart Style Guide, Object vs dynamic]. Of course, this will need to be adapted to recommend use of `?Object` instead. 224 | -------------------------------------------------------------------------------- /doc-src/1f-lib.md: -------------------------------------------------------------------------------- 1 | # Part F: Impact on Dart SDK libraries {- #part-libs} 2 | 3 | The purpose of this part is to illustrate what some of the Dart SDK libraries might look like in [DartNNBD][] and, in some cases, how they might be adapted to be more useful, through stricter type signatures or other enhancements. 4 | 5 | ## F.1 Examples 6 | 7 | The examples presented in this section are of types migrated to [DartNNBD][] that _only_ require updates through the addition of meta type annotations. Types potentially requiring behavioral changes are addressed in [F.2](#better-libs). 8 | 9 | ### F.1.1 `int.dart`{} {#int-nnbd} 10 | 11 | We present here the `int` class with nullity annotations. There are only 3 nullable meta type annotations out of 44 places were such annotations could be placed (3/44 = 7% are nullable). 12 | 13 | ```dart 14 | // DartNNBD - part of dart.core; 15 | abstract class int extends num { 16 | external const factory ?int.fromEnvironment(String name, {int defaultValue}); 17 | int operator &(int other); 18 | int operator |(int other); 19 | int operator ^(int other); 20 | int operator ~(); 21 | int operator <<(int shiftAmount); 22 | int operator >>(int shiftAmount); 23 | int modPow(int exponent, int modulus); 24 | bool get isEven; 25 | bool get isOdd; 26 | int get bitLength; 27 | int toUnsigned(int width); 28 | int toSigned(int width); 29 | int operator -(); 30 | int abs(); 31 | int get sign; 32 | int round(); 33 | int floor(); 34 | int ceil(); 35 | int truncate(); 36 | double roundToDouble(); 37 | double floorToDouble(); 38 | double ceilToDouble(); 39 | double truncateToDouble(); 40 | String toString(); 41 | String toRadixString(int radix); 42 | external static ?int parse(String source, 43 | {int radix /* = 10 */, 44 | ?int onError(String source) }); 45 | } 46 | ``` 47 | 48 | With the eventual added support for [generic functions][DEP-generic-functions], `parse()` could more usefully redeclared as: 49 | 50 | ```dart 51 | external static I parse(..., {..., I onError(String source)}); 52 | ``` 53 | 54 | Notes: 55 | 56 | - The `source` argument of `parse()` should be non-null, see [dart/runtime/lib/integers_patch.dart#L48][]. 57 | - In conformance to the [guideline of E.1.1](#guideline), the following optional parameters are left as [NNBD][]: 58 | 59 | - `defaultValue` of `factory int.fromEnvironment()`. 60 | - `radix` and `onError` of `parse()`. Since `radix` has a non-null default value, it could be declared as `!int`, though there is little value in doing so given that `parse()` is `external`. 61 | 62 | (In opposition to the guideline, if we declare `defaultValue` and `onError` as nullable, that would make for 5/44 = 11% of declarators with nullable annotations.) 63 | 64 | We have noted that conforming to the [guideline for optional parameters](#guideline) of [E.1.1](#opt-func-param) may result in breaking changes for some functions of SDK types. Other SDK type members explicitly document their adherence to the guideline: e.g., the `List([int length])` [constructor][Dart List constructor API]. 65 | 66 | ### F.1.2 Iterable {#iterable-nnbd} 67 | 68 | The `Iterable` type requires no `?` annotations (thought the optional `separator` parameter of `join()` could be declared as `!String`). 69 | 70 | ```dart 71 | // DartNNBD - part of dart.core; 72 | abstract class Iterable { 73 | const Iterable(); 74 | factory Iterable.generate(int count, [E generator(int index)]); 75 | Iterator get iterator; 76 | Iterable map(f(E element)); 77 | Iterable where(bool f(E element)); 78 | Iterable expand(Iterable f(E element)); 79 | bool contains(Object element); 80 | void forEach(void f(E element)); 81 | E reduce(E combine(E value, E element)); 82 | dynamic fold(var initialValue, 83 | dynamic combine(var previousValue, E element)); 84 | bool every(bool f(E element)); 85 | String join([String separator = ""]); 86 | bool any(bool f(E element)); 87 | List toList({ bool growable: true }); 88 | Set toSet(); 89 | int get length; 90 | bool get isEmpty; 91 | bool get isNotEmpty; 92 | Iterable take(int n); 93 | Iterable takeWhile(bool test(E value)); 94 | Iterable skip(int n); 95 | Iterable skipWhile(bool test(E value)); 96 | E get first; 97 | E get last; 98 | E get single; 99 | E firstWhere(bool test(E element), { E orElse() }); 100 | E lastWhere(bool test(E element), {E orElse()}); 101 | E singleWhere(bool test(E element)); 102 | E elementAt(int index); 103 | String toString(); 104 | } 105 | ``` 106 | 107 | ### F.1.3 `Future` 108 | 109 | We mention in passing that the use of `Future` remains a valid idiom in [DartNNBD][] since the generic class is declared as: 110 | 111 | ```dart 112 | abstract class Future {...} 113 | ``` 114 | 115 | Hence `T` is nullable ([C.3.4](#default-type-param-bound)). 116 | 117 | ## F.2 Suggested library improvements {#better-libs} 118 | 119 | ### F.2.1 Iterator 120 | 121 | #### [DartC][] {-} 122 | 123 | An [`Iterator`][Dart Iterator API] is "an interface for getting items, one at a time, from an object" via the following API: 124 | 125 | ```dart 126 | // DartC - part of dart.core; 127 | abstract class Iterator { 128 | bool moveNext(); 129 | E get current; 130 | } 131 | ``` 132 | 133 | Here is an example of typical use (excerpt from the [API documentation][Dart Iterator API]): 134 | 135 | ```dart 136 | var it = obj.iterator; 137 | while (it.moveNext()) { 138 | use(it.current); 139 | } 140 | ``` 141 | 142 | Dart's API documentation for `current` is nonstandard in that it specifies that `current` shall be `null` "_if the iterator has not yet been moved to the first element, or if the iterator has been moved past the last element_". This has the unfortunate consequence of forcing the return type of `current` to be nullable, even if the element type `E` is non-null. Iterators in other languages (such as Java and .Net languages) either [raise an exception][Iterator API, Java] or document the behavior of `current` as *undefined* under such circumstances---for the latter see, e.g., the [.Net IEnumerator.Current Property API][]. 143 | 144 | #### [DartNNBD][] {-} 145 | 146 | We suggest that that [Dart Iterator API][] documentation be updated to state that the behavior of `current` is unspecified when the last call to `moveNext()` returned false (implicit in this statement is that `moveNext()` *must* be called at least once before `current` is used). This would allow us to usefully preserve the interface definition of `Iterator` as: 147 | 148 | ```dart 149 | // DartNNBD - part of dart.core; 150 | abstract class Iterator { 151 | bool moveNext(); 152 | E get current; 153 | } 154 | ``` 155 | 156 | Note that the type and nullity of `current` matches that of the type parameter. 157 | 158 | Independent of nullity, the behavior of `current` might be adapted so that it throws an exception if it is invoked in situations where its behavior is undefined. But this would be a potentially breaking change (which, thankfully, would not impact uses of iterators in `for`-`in` loops). 159 | 160 | [.Net IEnumerator.Current Property API]: https://msdn.microsoft.com/en-us/library/58e146b7(v=vs.110).aspx 161 | [Iterator API, Java]: https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html 162 | 163 | ### F.2.2 `List` {#list} 164 | 165 | We comment on two members of the [`List`][Dart List API] type. 166 | 167 | #### `factory List([int length])`{} {-} 168 | 169 | In [DartNNBD][], a [dynamic type error][] will be raised if `length` is positive and `E` is non-null. The error message could suggest using `List.filled(int length, E fill)` instead. 170 | 171 | #### `List.length=`{} {-} 172 | 173 | The [`List.length=`][Dart List set length API] setter changes the length of a list. If the new length is greater than the current length, then new entries are initialized to `null`. This will cause a [dynamic type error][] to be issued when `E` is non-null. 174 | 175 | Alternatives to growing a list of non-null elements includes: 176 | 177 | - Define a mechanism by which an "filler field" could be associated with a list. The filler field could then be used by the length setter when growing a list of non-null elements. E.g., 178 | 179 | - Add a `List.setFiller(E filler)` method, or; 180 | - Reuse the filler provided, say, as argument to `List.filled(int length, E fill)`. 181 | 182 | - Add a new mutator, `setLength(int newLength, E filler)`. 183 | 184 | ## F.3 Other classes 185 | 186 | ### Object 187 | 188 | The `Object` class requires no textual modifications: 189 | 190 | ```dart 191 | class Object { 192 | const Object(); 193 | bool operator ==(other) => identical(this, other); 194 | external int get hashCode; 195 | external String toString(); 196 | external dynamic noSuchMethod(Invocation invocation); 197 | external Type get runtimeType; 198 | } 199 | ``` 200 | -------------------------------------------------------------------------------- /doc-src/1g-migration.md: -------------------------------------------------------------------------------- 1 | # Part G: Migration strategy (sketch) {- #part-migration} 2 | 3 | > Comment. An effective migration plan depends on several factors including, for example, whether union types will soon added to Dart or not. Regardless, this part sketches some initial ideas. 4 | 5 | ## G.1 Precedent 6 | 7 | As is mentioned in the survey ([I.3](#retrofit)), both commercial and research languages have successfully migrated from a nullable-by-default to a [NNBD][] semantics. To our knowledge, [Eiffel][], in 2005, was the first commercial language to have successfully made this transition. [JML][], a Java [BISL][], made the transition a few years later ([Chalin et al., 2008][]). More recently, the [Eclipse JDT][] has been allowing developers to enable [NNBD][] at [various levels of granularity][Eclipse help, improve code quality], including at the level of an entire project or workspace, and work is underway to provide nullity annotations for the types in the SDK. 8 | 9 | ## G.2 Migration aids 10 | 11 | It is interesting to note that [Eiffel][] introduced the `!` meta type annotation solely for purpose of code migration. [DartNNBD][] also has `!` at its disposal, though in our case it is a core feature. 12 | 13 | We propose (as has been done in [JML][] and the [Eclipse JDT][]) that the following lexically scoped, non-inherited library, part and class level annotations be made available: `@nullable_by_default` and `@non_null_by_default`. Such annotations establish the default nullity in the scope of the entity thus annotated. 14 | 15 | Within the scope of an `@nullable_by_default` annotation, every type name *T* is taken as implicitly ?*T* except for the following: a type name that names 16 | 17 | - a constructor in a constructor declaration 18 | - a type target to a catch clause 19 | - the argument type of a type test (`is` expression) 20 | 21 | Despite the exclusions above, if any such type name has type arguments then the nullable-by-default rule applies to the type arguments. 22 | 23 | ## G.3 Impact 24 | 25 | Tool impacted include (some common subsystems overlap): 26 | 27 | - [Dart Analyzer][]. 28 | - [Dart Dev Compiler][]. 29 | - [Dart VM][]. 30 | - [dart2js][]. 31 | - [Dart Code Formatter][]. 32 | - [Dart docgen][]. 33 | 34 | ## G.4 Migration steps 35 | 36 | It seems desirable to target Dart 2.0 as a first release under which [NNBD][] would be the _default_ semantics. In Dart 2.0, a command line option could be provided to recover nullable-by-default semantics. Initial steps in preparation of this switch would be accomplished in stages in the remaining releases of the 1.x stream. 37 | 38 | Here is a preliminary list of possible steps along this migration path, not necessarily in this order: 39 | 40 | - (SDK) Create `@nullable_by_default` and `@non_null_by_default` annotations. 41 | - (Tooling) Add support for: 42 | - Meta type annotation _syntax_ (excluding most sugars). 43 | - Static checks. This includes processing of `@*_by_default` annotations. 44 | - Runtime support ([B.3.3](#shared-type-op-semantics)) for nullity type operators, and dynamic checks. 45 | - (SDK) Re-root the class hierarchy ([B.2.1](#new-root)). 46 | - (Tooling) Global option to turn on [NNBD][]. 47 | - ... 48 | 49 | ## G.5 Migration plan details 50 | 51 | > Comment. TODO. 52 | -------------------------------------------------------------------------------- /doc-src/Makefile: -------------------------------------------------------------------------------- 1 | # File: Makefile 2 | 3 | ROOT=.. 4 | 5 | PANDOC=pandoc 6 | OPTS= -s --smart \ 7 | --toc-depth=5 \ 8 | --chapters \ 9 | --atx-headers \ 10 | --latex-engine=xelatex \ 11 | -V documentclass=book \ 12 | -V geometry:margin=1in \ 13 | -V fontsize=10 14 | DOCSRC=$(shell grep -v '\#' 00-files.txt) 15 | FILTER=$(ROOT)/tool/depNonNullFilter 16 | 17 | BUILD_DIR=$(ROOT)/build 18 | DOC_TMP=$(BUILD_DIR)/doc 19 | TARGET=$(ROOT)/doc 20 | DEP_NN=dep-non-null 21 | 22 | PANDOC_PIPE_HD=$(PANDOC) -f markdown $(OPTS) $(DOCSRC) -t json | $(FILTER) 23 | PANDOC_TL=$(PANDOC) $(OPTS) -f json 24 | PANDOC_PIPE=$(PANDOC_PIPE_HD) | $(PANDOC_TL) 25 | 26 | FIX_MATH_SUP=perl -pi -e 's%\^([^\{])|\^\{([^\{]+)\}%\1\2%g' 27 | 28 | #------------------------------------------------------------------------------- 29 | 30 | .PHONY: default prep json latex md pdf 31 | 32 | default: prep json latex md pdf 33 | 34 | #------------------------------------------------------------------------------- 35 | # PDF 36 | 37 | pdf: prep 38 | $(PANDOC_PIPE) --toc -H 00-latex.tex -o $(TARGET)/$(DEP_NN).pdf 39 | 40 | #------------------------------------------------------------------------------- 41 | # Intermediate formats 42 | 43 | latex: prep 44 | $(PANDOC_PIPE) --toc -t latex -H 00-latex.tex -o $(DOC_TMP)/$(DEP_NN).tex 45 | 46 | json: prep 47 | $(PANDOC_PIPE_HD) | python -m json.tool > $(DOC_TMP)/$(DEP_NN).json 48 | 49 | #------------------------------------------------------------------------------- 50 | 51 | prep: $(BUILD_DIR) $(DOC_TMP) 52 | 53 | $(DOC_TMP): 54 | -mkdir $(DOC_TMP) 55 | 56 | $(BUILD_DIR): 57 | -mkdir $(BUILD_DIR) 58 | 59 | #------------------------------------------------------------------------------- 60 | # Consolidated .md file: 61 | 62 | .PHONY: mdgen mdcp 63 | 64 | md: prep mdgen mdcp 65 | 66 | mdgen: $(DOC_TMP)/01-front-matter.md $(DOC_TMP)/toc.md 67 | $(PANDOC_PIPE_HD) --md | $(PANDOC_TL) \ 68 | -H $(DOC_TMP)/01-front-matter.md \ 69 | -H $(DOC_TMP)/toc.md \ 70 | -t markdown_github -o $(DOC_TMP)/$(DEP_NN).md 71 | $(FIX_MATH_SUP) $(DOC_TMP)/$(DEP_NN).md 72 | 73 | mdcp: 74 | -cp $(DOC_TMP)/$(DEP_NN).md $(TARGET)/$(DEP_NN)-AUTOGENERATED-DO-NOT-EDIT.md 75 | 76 | $(DOC_TMP)/01-front-matter.md: 01-front-matter.md 77 | sed -n '1{s/^%/#/;p;q;}' 01-front-matter.md > $(DOC_TMP)/01-front-matter.md 78 | sed -n '2{s/^%/###/;p;q;}' 01-front-matter.md >> $(DOC_TMP)/01-front-matter.md 79 | sed -n '3{s/^%/####/;p;q;}' 01-front-matter.md >> $(DOC_TMP)/01-front-matter.md 80 | 81 | $(DOC_TMP)/toc.md: $(DOC_TMP)/$(DEP_NN)-from-html.md 82 | tail +5 $(DOC_TMP)/$(DEP_NN)-from-html.md \ 83 | | grep -B999 -m1 -e "^$$" > $(DOC_TMP)/toc.md 84 | 85 | $(DOC_TMP)/$(DEP_NN)-from-html.md: $(DOC_TMP)/$(DEP_NN).html 86 | pandoc -f html -s --atx-headers $(DOC_TMP)/$(DEP_NN).html \ 87 | -t markdown_github -o $(DOC_TMP)/$(DEP_NN)-from-html.md 88 | 89 | $(DOC_TMP)/$(DEP_NN).html: prep 90 | $(PANDOC_PIPE) --toc -t html -o $(DOC_TMP)/$(DEP_NN).html 91 | 92 | #------------------------------------------------------------------------------- 93 | -------------------------------------------------------------------------------- /doc-src/file-stats.txt: -------------------------------------------------------------------------------- 1 | # Generated 2015.06.29 (start of day) 2 | 3 | Showing 77 changed files with 5,471 additions and 345 deletions. 4 | 5 | +17 −0 README.md 6 | +87 −0 pkg/analyzer/Makefile 7 | +125 −0 pkg/analyzer/README.md 8 | 9 | +3 −2 pkg/analyzer/lib/src/generated/ast.dart 10 | +5 −3 pkg/analyzer/lib/src/generated/constant.dart 11 | +40 −5 pkg/analyzer/lib/src/generated/element.dart 12 | +53 −9 pkg/analyzer/lib/src/generated/element_resolver.dart 13 | +16 −0 pkg/analyzer/lib/src/generated/engine.dart 14 | +10 −0 pkg/analyzer/lib/src/generated/error.dart 15 | +20 −7 pkg/analyzer/lib/src/generated/error_verifier.dart 16 | +94 −8 pkg/analyzer/lib/src/generated/resolver.dart 17 | +4 −1 pkg/analyzer/lib/src/generated/static_type_analyzer.dart 18 | 19 | +11 −0 pkg/analyzer/lib/src/nullity/ast_part.dart 20 | +275 −0 pkg/analyzer/lib/src/nullity/element_core_part.dart 21 | +34 −0 pkg/analyzer/lib/src/nullity/element_general_part.dart 22 | +138 −0 pkg/analyzer/lib/src/nullity/element_nullity_part.dart 23 | +132 −0 pkg/analyzer/lib/src/nullity/element_resolver_part.dart 24 | +392 −0 pkg/analyzer/lib/src/nullity/element_type_part.dart 25 | +25 −0 pkg/analyzer/lib/src/nullity/error_verifier_non_null_local_var.dart 26 | +122 −0 pkg/analyzer/lib/src/nullity/error_verifier_part.dart 27 | +211 −0 pkg/analyzer/lib/src/nullity/resolver_core_part.dart 28 | +237 −0 pkg/analyzer/lib/src/nullity/resolver_meta_type_annotation_part.dart 29 | 30 | +54 −1 pkg/analyzer/lib/src/generated/testing/test_type_provider.dart 31 | +3,011 −0 pkg/analyzer/test/generated/nullity_test.dart 32 | +24 −0 pkg/analyzer/test/generated/resolver_test.dart 33 | 34 | +17 −17 pkg/expect/lib/expect.dart 35 | +1 −1 pkg/fixnum/lib/src/int64.dart 36 | 37 | +2 −2 sdk/bin/dartanalyzer_sdk 38 | 39 | +1 −1 sdk/lib/async/async_error.dart 40 | +5 −5 sdk/lib/async/broadcast_stream_controller.dart 41 | +2 −2 sdk/lib/async/future.dart 42 | +4 −4 sdk/lib/async/future_impl.dart 43 | +4 −4 sdk/lib/async/schedule_microtask.dart 44 | +14 −14 sdk/lib/async/stream.dart 45 | +8 −8 sdk/lib/async/stream_controller.dart 46 | +17 −17 sdk/lib/async/stream_impl.dart 47 | +3 −3 sdk/lib/async/stream_pipe.dart 48 | +2 −2 sdk/lib/async/stream_transformers.dart 49 | +50 −50 sdk/lib/async/zone.dart 50 | +2 −2 sdk/lib/collection/hash_set.dart 51 | +4 −4 sdk/lib/collection/iterable.dart 52 | +6 −6 sdk/lib/collection/linked_list.dart 53 | +6 −6 sdk/lib/collection/list.dart 54 | +9 −9 sdk/lib/collection/maps.dart 55 | +9 −9 sdk/lib/collection/queue.dart 56 | +12 −12 sdk/lib/collection/set.dart 57 | +25 −25 sdk/lib/collection/splay_tree.dart 58 | +3 −3 sdk/lib/convert/ascii.dart 59 | +1 −1 sdk/lib/convert/encoding.dart 60 | +3 −3 sdk/lib/convert/html_escape.dart 61 | +6 −6 sdk/lib/convert/json.dart 62 | +1 −1 sdk/lib/convert/latin1.dart 63 | +2 −2 sdk/lib/convert/line_splitter.dart 64 | +1 −1 sdk/lib/convert/utf.dart 65 | +19 −0 sdk/lib/core/core.dart 66 | +11 −11 sdk/lib/core/errors.dart 67 | +1 −1 sdk/lib/core/exceptions.dart 68 | +2 −2 sdk/lib/core/function.dart 69 | +2 −2 sdk/lib/core/identical.dart 70 | +3 −3 sdk/lib/core/int.dart 71 | +4 −4 sdk/lib/core/iterable.dart 72 | +1 −1 sdk/lib/core/list.dart 73 | +4 −4 sdk/lib/core/map.dart 74 | +3 −0 sdk/lib/core/null.dart 75 | +3 −3 sdk/lib/core/num.dart 76 | +7 −7 sdk/lib/core/set.dart 77 | +2 −2 sdk/lib/core/stopwatch.dart 78 | +7 −7 sdk/lib/core/string.dart 79 | +3 −3 sdk/lib/core/string_buffer.dart 80 | +1 −1 sdk/lib/core/symbol.dart 81 | +18 −18 sdk/lib/core/uri.dart 82 | +11 −11 sdk/lib/internal/iterable.dart 83 | +3 −3 sdk/lib/internal/list.dart 84 | +1 −1 sdk/lib/internal/print.dart 85 | +2 −2 sdk/lib/io/http_headers.dart 86 | +2 −2 sdk/lib/io/secure_socket.dart 87 | +1 −1 sdk/lib/math/rectangle.dart 88 | -------------------------------------------------------------------------------- /doc-src/x1-review.md: -------------------------------------------------------------------------------- 1 | # Appendix I. Nullity in programming languages, an abridged survey {- #appendix-1-review} 2 | 3 | Problems arising from the presence of `null` have been well articulated over the years. Programming language designers have proposed various approaches to dealing with these problems, including the elimination of `null` entirely. For a survey of the alternate strategies to `null`, and the use of *nullity annotations* and *non-null types* in programming languages circa 2008 see *Chalin et al., 2008*, Section 4 ([IEEE][Chalin et al., 2008 IEEE], [preprint][Chalin et al., 2008]). Below we summarize the survey and include recent developments. 4 | 5 | In `null`-enabled languages, `null` is conveniently used to represent a value of any type *T*, when there is no *T* value at hand. This, in particular, allows for simple initialization rules: any variable not explicitly initialized can be set to `null`. 6 | 7 | ## I.1 Languages without null 8 | 9 | One way to avoid problems with `null` is to avoid making it part of the language. To address the main use case of `null` as _a substitute for a value of type *T* when you don't have a value of type *T*_ many languages without `null` resort to use of [option type][]s. This is the case of: 10 | 11 | - Most functional programming languages, like [ML][] and [Haskell][], as well as 12 | - Some object-oriented languages, like [CLU][], [OCaml][] and, as was mentioned in the [introduction](#precedent), Apple's recently released [Swift][] language. 13 | 14 | ## I.2 Strategies for dealing with null in null-enabled languages {#strategies} 15 | 16 | Most imperative programming languages having reference types also support `null`. This is certainly true for mainstream languages of C descent. Various strategies for dealing with `null` and attempting to detect [NPE][]s are detailed next. 17 | 18 | + **Tools**, such as *linters*, have been used to perform nullity analysis (among other checks) in the hope of detecting potential [NPE][]s. A notable mention is [Splint][], which actually assumes that unannotated reference types are non-null. 19 | 20 | + **Special macros/annotations** allowing developers to mark _declarators_ as non-null, can guide: 21 | 22 | - *Runtime instrumentation* of code so as to eagerly perform runtime 23 | checks---e.g., argument checks on function entry rather than throwing 24 | an [NPE][] at some later point in the call chain. There was early support 25 | for such macros/annotation in, e.g., GNU's gcc and Microsoft's 26 | Source-code Annotation Language (SAL) for C/C++ ([Chalin et al., 2008][]). 27 | - *Static checking*. E.g., [Findbugs][] which makes use of Java 5 metadata 28 | annotations such as `@NonNull` and `@CheckForNull`. Modern IDEs like the 29 | [Eclipse JDT][] and [IntelliJ][] have been systematically improving their 30 | static [NPE][] detection capabilities using such annotations as well, as we 31 | have discussed in [A.3.1](#why-nn-types). 32 | 33 | + **Language subsets** (sometimes qualified as "safe" subsets) have been defined so as to allow tool analysis to be more effective at flagging potential [NPE][]s while reducing false positives. Pure subsets are rare. Most often they are combined with some form of extension. Examples include: 34 | 35 | - [Spark Ada][], which eliminated `null` by eliminating references! 36 | - [Cyclone][], a safe dialect of C, which introduces the concept of safe/never-`null` 37 | pointers. The design of [Rust][], which is still in beta (2015Q2), was influenced 38 | by [Cyclone][]; it also distinguishes safe from raw pointers. 39 | 40 | + **Language extensions** and **language evolution**, which are the topic of the next section. 41 | 42 | ## I.3 Retrofitting a null-enabled language with support for non-null {#retrofit} 43 | 44 | Retrofitting a fielded language is a challenge due to the presence of legacy code. We have seen two main approaches to tackling this challenge: language extensions and language evolution. 45 | 46 | ### I.3.1 Language extensions {#lang-extensions} 47 | 48 | Language extensions are, as the name implies, defined atop (and outside of) a given base language. This means that the base language remains unaffected, and hence extensions have no impact on (standard) language tooling. This also implies that code elements from extensions are often encoded in specially marked comments (e.g., `/*@non_null*/`) or metadata. 49 | 50 | #### (a) Contracts {-} 51 | 52 | One example, is the still very active Microsoft [Code Contracts][] project, which provides a language-agnostic (i.e., library-based) way to express contracts (preconditions, postconditions, and object invariants) in programs written in most of the .Net family of languages. Contracts can be used to constrain fields, parameters and function results to be non-null, as is illustrated by the following excerpt of the [NonNullStack.cs][] example taken from [Fahndrich and Logozzo, 2010][]: 53 | 54 | ```java 55 | public class NonNullStack where T : class { 56 | protected T[] arr; 57 | private int nextFree; 58 | 59 | [ContractInvariantMethod] 60 | void ObjectInvariant() { 61 | Contract.Invariant(arr !=null); 62 | Contract.Invariant(Contract.ForAll(0, nextFree, i => arr[i] != null)); 63 | ... 64 | } 65 | 66 | public void Push(T x) { 67 | Contract.Requires(x != null); 68 | ... 69 | } 70 | 71 | public T Pop() { 72 | Contract.Requires(!this.IsEmpty); 73 | Contract.Ensures(Contract.Result() != null); 74 | ... 75 | } 76 | ... 77 | } 78 | ``` 79 | 80 | Notice the predicates constraining the following elements to be non-null: 81 | 82 | - `arr` field, as well as elements of the `arr` array, in the object invariant, 83 | - `x` parameter of `Push()` in the requires clause, 84 | - return result of `Pop()` in the ensures clause. 85 | 86 | Of course, a contract language like this can be used to do much more than assert that fields, variables and results are non-null. 87 | 88 | #### (b) Non-null declarators {-} 89 | 90 | The same approach to nullity as [Code Contracts][] was originally adopted in \label{JML}[Java Modeling Language][] (**JML**), a [Behavioral Interface Specification Language][BISL paper] (**BISL**) for Java. But nullity constraints were so common that declarator annotations were defined as "syntactic sugar" for corresponding contract clauses. E.g., the `NonNullStack` example from above could have been written as: 91 | 92 | ```java 93 | public class NonNullStack ... { 94 | protected /*@non_null*/ T /*@non_null*/ [] arr; 95 | public void Push(/*@non_null*/T x) { ... } 96 | public /*@non_null*/T Pop() { ... } 97 | ... 98 | } 99 | ``` 100 | 101 | But such an approach actually arose in [JML][] prior to the advent of Java generics. Nullity declarator constraints cannot be used to qualify type parameters, a feature often requested by developers. 102 | 103 | Other languages supporting nullity declarators include the [Larch][] family of [BISL][]s, notably Larch/C (LCL) and [Larch/C++][]. The [Splint][] linter mentioned above ([I.2](#strategies)) grew out of Larch/C work. 104 | 105 | #### (c) Non-null types {-} 106 | 107 | Evolution of language extensions has sometimes been from nullity annotations applied to *declarators* to support for *non-null types*. This has been the case for [JML][] ([Chalin et al., 2008][]). In fact, the need to fully support non-null types in Java led to the creation of [JSR-308][], "[Java Type Annotations][JSR-308 explained]" which extends support for annotations to all places type expressions can appear. There has been hints that a similar extension might be considered useful for Dart as well ([B.4.6](#type-anno-alt)). [JSR-308][] was included in the March 2014 release of Java 8. 108 | 109 | Other language extensions supporting non-null types include: 110 | 111 | - [Eclipse JDT][]. The progression, from support of nullity declarator annotations to non-null types, in the context of the [Eclipse JDT][], was discussed in [A.3.1](#why-nn-types). Not only does the most recent release of the [Eclipse JDT][] support non-null types, but it also allows developers to enable [NNBD][] at [various levels of granularity][Eclipse help, improve code quality], including the project and workspace levels. 112 | - [Nullness Checker][] of the Java [Checker Framework][]. 113 | - [JastAdd][], a "meta-compilation system that supports Reference Attribute Grammars" and one of its instances, the JastAddJ compiler for Java, has an extension supporting [non-null types][JastAddJ non-null extension]. 114 | - [Spec#][] a [BISL][] for [C#][]. 115 | 116 | All of these language extensions, except possibly [JastAdd][] also support [NNBD][]. 117 | 118 | ### I.3.2 Language evolution {#language-evolution} 119 | 120 | As was mentioned earlier, adopting non-null types and/or [NNBD][] can be a challenge for languages with a sufficiently large deployed code base. Having a proper [migration strategy](#part-migration) is key. 121 | 122 | To our knowledge, [Eiffel][] is the first _commercial language_ to have made the switch, in 2005, from non-null types with a nullable-by-default semantics to [NNBD][]. [Eiffel][] introduced the `!` meta type annotation solely for easing migration efforts. 123 | 124 | Adopting non-null types can lead to unsoundness if one is not careful, particularly with respect to the initialization of fields declared non-null ([B.4.2](#var-init-alt))---e.g., due to possible calls to helper methods from within constructors. [Fahndrich and Leino, 2003][], detail the challenges they faced in bringing non-null types to [Spec#][]. [Swift][], in which types are non-null by default, adopts [such a position][Swift on initialization] for classes and structures which: "_must set all of their stored properties to an appropriate initial value by the time an instance of that class or structure is created. Stored properties cannot be left in an indeterminate state_". 125 | 126 | Some feel that the cost of preserving type soundness is too high with respect to language usability. Eberhardt discusses the challenges in writing proper class/structure initialization code in [Swift][] ([Eberhardt, 2014][]). Similar initialization rules are also know to be one of the difficulties facing language designers attempting to add [support for non-null types][C# non-nullable proposal] to [C#][]. In fact, Lippert believes that it is completely impractical to retrofit [C#][] with non-null types and instead proposes use of [Code Contracts][] ([Lippert, 2013][]). 127 | 128 | We mention in passing that in 2005, .Net was extended with support for nullable _primitive_ types. This was done to achieve (uniform) native support for data coming from datasources in which all types are nullable. But this is creating an extra challenge, in terms of notational consistency, for C# language designers who are considering the introduction of [non-null types into C# 7][C# non-nullable proposal], as is illustrated by the following sample declarations: 129 | 130 | ```java 131 | // C# + nullity proposal 132 | int a; //non-nullable value type 133 | int? a; //nullable value type 134 | string! a; //non-nullable reference type 135 | string a; //nullable reference type 136 | ``` 137 | 138 | ## I.4 Modern web/mobile languages with non-null types and [NNBD][] {#modern-lang-nnbd} 139 | 140 | [Fletch][] is an experimental runtime (VM) for Dart "that makes it possible to implement highly concurrent programs in the Dart". Meant to address problems in a similar space, is the [Pony][] language ([@0.1.5][Pony GitHub] 2015Q2), a statically typed actor-based language (with concurrent garbage collection). [Pony][] has non-null types and [NNBD][] with nullable types introduced via a union with special [unit type][] `None`. 141 | 142 | For sake of completeness, we also reproduce here (from [2.2](#precedent)) the list of programming languages, many recently released, that are relevant to web applications (either dialects of [JS][] or that compile to [JS][]) and/or mobile, and that support _non-null types_ and [NNBD][]. 143 | 144 | | Language | About | v1.0? | Nullable via | Reference 145 | | -------------------- | ---------------------------------- | ------ | ------------- | --------- 146 | |[Ceylon][] (Red Hat)|Compiles to [JS][], [Java Bytecode][JB] (**JB**)|2013Q4| *T*? | [Ceylon optional types][] 147 | | [Fantom][] | Compiles to [JS][], [JB][], .Net CLR | 2005 | *T*? | [Fantom nullable types][] 148 | | [Flow][] (Facebook) | [JS][] superset and static checker | 2014Q4 | *T*? | [Flow maybe types][] 149 | | [Kotlin][] (JetBrains)| Compiles to [JS][] and [JB][] | 2011Q3 | *T*? | [Kotlin null safety][] 150 | | [Haste][] | [Haskell][] to [JS][] compiler | @0.4.4 |[option type][]| [Haskell maybe type][] 151 | | [Swift][] (Apple) | iOS/OS X Objective-C successor | 2014Q4 |[option type][]| [Swift optional type][] 152 | 153 | As was mentioned earlier, there is also [discussion](#precedent) of introducing non-null types to [TypeScript][]. 154 | -------------------------------------------------------------------------------- /doc-src/x2-tooling.md: -------------------------------------------------------------------------------- 1 | # Appendix II. Tooling and preliminary experience report {- #appendix-tooling} 2 | 3 | ## II.1 Variant of proposal implemented in the Dart Analyzer {#proposal-variant} 4 | 5 | We describe here a _version_ of this proposal as implemented in the [Dart Analyzer][]. It is "a version" in that we have adopted all core ideas ([8.1](#lang-changes)) but we have made a particular choice of alternatives ([8.3](#alternatives)). Choices have been driven by the need to create a solution that is **fully backwards compatible**, as will be explained below. 6 | 7 | Core language design decisions (cf. [8.1](#lang-changes)) and main alternatives: 8 | 9 | - [A.2](#non-null-types). Drop semantic rules giving special treatment to `null`. 10 | - [B.2](#nnbd). Ensure `Object` is non-null by making `Null` a root ([B.4.7](#object-not-nullable-alt)). 11 | - [B.2](#nnbd). Support meta type annotations 12 | - `@nullable` and `@non_null` ([B.4.6](#type-anno-alt)), and in those places where [DartC][] does not currently support metadata, 13 | - allow the use of specialized comments `/*?*/` and `/*!*/`. 14 | - [C.3](#generics). Support for generics matches the proposal. 15 | - [G.2](#g-2-migration-aids). Support `library`, `part` and `class` level `@nullable_by_default` annotations. 16 | 17 | By our choice of input syntax, [DartNNBD][] annotated code can be **analyzed and executed in DartC** without any impact on DartC tooling. 18 | 19 | ## II.2 Dart Analyzer 20 | 21 | We describe here a realization of this proposal in the [Dart Analyzer][]. 22 | 23 | ### II.2.1 Design outline 24 | 25 | The dart analyzer processes compilation units within a collection of libraries. The processing of each library is done in multiple phases on the Abstract Syntax Tree (AST) provided by the parser. 26 | 27 | #### (a) AST 28 | 29 | No structural changes have been done to the AST types since nullity annotations are represented by metadata and comments. Also, `NullityElement`s, described next, are attached to `TypeName`s via the generic AST property mechanism (a hash map associated with any AST node). 30 | 31 | #### (b) Element model 32 | 33 | - We introduce two `DartType` subtypes, one for each of `?` and `!` meta type annotations, named `UnionWithNullType` and `NonNullType`, respectively. These represent normalized types ([E.1.2](#normalization)). 34 | - The model `Element` representing a `UnionWithNullType` is `UnionWithNullElement`. Its is a representation of a (synthetic) `ClassElement`-like entity that can be queried for members via methods like `lookUpMethod(methodName)`, etc. When queried for a member with a given name *n*, a (synthetic) multi-member is returned which represents the collection of members matching declarations of *n* in `Null` and/or the other type argument of `UnionWithNullType`. 35 | - The dual-view for optional function parameters ([E.1.1](#opt-func-param)) is realized by associating to each optional parameter (`DefaultParameterElementImpl`) a synthetic counterpart (`DefaultParameterElementWithCalleeViewImpl`) representing the declared parameter from the function-body/callee view ([E.1.1(a)](#opt-func-param)). All identifier occurrences within the function body scope have the callee-view parameter instance as an associated static element. 36 | 37 | #### (c) Resolution 38 | 39 | We describe here the _added_ / _adapted_ analyzer processing (sub-)phases: 40 | 41 | 1. Nullity annotation processing: 42 | 43 | (a) Nullity annotation resolution (earlier than would normally be done since nullity annotations impact _types_ in [DartNNBD][]). Note that we currently match annotation names only, regardless of library of origin so as to facilitate experimentation. 44 | (b) `NullityElement`s (see (b) below) are computed in a top-down manner, and attached to the AST nodes that they decorate (e.g., `TypeName`, `LibraryDirective`, etc.). The final nullity of a type name depends on: global defaults (whether [NNBD][] is enabled or not), `@nullable_by_default` nullity scope annotations, and individual declarator annotations. 45 | 46 | 2. Element resolution (via `ElementResolver` and `TypeResolverVisitor`) is enhanced to: 47 | 48 | (a) Adjust the static type associated with a, e.g., a `TypeName` based on its nullities. 49 | (b) Associate a callee view to each default parameter element and suitably adjust its type. 50 | (c) Handle problem reporting for operator and method (including getter and setter) invocation over nullable targets. 51 | 52 | 3. Error verification has been adapted to, e.g., check for invalid implicit initialization of variables with `null`. See [B.3.4](#var-init) for details. 53 | 54 | The [NNBD][] analyzer also builds upon existing [DartC][] flow analysis and type propagation facilities. 55 | 56 | > Caveat excerpt from a code comment: TODO(scheglov) type propagation for instance/top-level fields was disabled because it depends on the order or visiting. If both field and its client are in the same unit, and we visit the client before the field, then propagated type is not set yet. 57 | 58 | ### II.2.2 Source code and change footprint {#analyzer-code-changes} 59 | 60 | The [NNBD][]-enabled analyzer sources are in the author's GitHub Dart SDK fork @[chalin/sdk, dep30 branch][], under `pkg/analyzer`. This SDK fork also contains updates to the SDK library and sample projects which have been subject to nullity analysis (as documented in [II.3](#experience-report)). Note that 61 | 62 | - All code changes are marked with comments containing the token `DEP30` to facilitate identification (and merging of upstream changes from @[dart-lang/sdk][Dart SDK project]). 63 | - Most significant code changes are marked with appropriate references to sections of this proposal for easier feature implementation tracking. 64 | 65 | As of the time of writing, the [Dart Analyzer][] code change footprint (presented as a git diff summary) is: 66 | 67 | ``` 68 | Showing 9 changed files with 245 additions and 35 deletions. 69 | +3 −2 pkg/analyzer/lib/src/generated/ast.dart 70 | +5 −3 pkg/analyzer/lib/src/generated/constant.dart 71 | +40 −5 pkg/analyzer/lib/src/generated/element.dart 72 | +53 −9 pkg/analyzer/lib/src/generated/element_resolver.dart 73 | +16 −0 pkg/analyzer/lib/src/generated/engine.dart 74 | +10 −0 pkg/analyzer/lib/src/generated/error.dart 75 | +20 −7 pkg/analyzer/lib/src/generated/error_verifier.dart 76 | +94 −8 pkg/analyzer/lib/src/generated/resolver.dart 77 | +4 −1 pkg/analyzer/lib/src/generated/static_type_analyzer.dart 78 | ``` 79 | 80 | There is approximately 1K [Source Lines Of Code (SLOC)][sloccount] of new code (or 3K LOC including comments and whitespace). 81 | 82 | ### II.2.3 Status {#analyzer-status} 83 | 84 | Please see the GitHub [DEP #30 Analyzer project page][]. 85 | 86 | ## II.3 Preliminary experience report {#experience-report} 87 | 88 | We stress from the outset that this is a **preliminary** report. 89 | 90 | Our initial objective has been to test run the new analyzer on sample projects. Our first target has been the SDK library Dart sources. We have also used some sample projects found in the Dart SDK `pkg` directory. So far, results are encouraging in that the nullable annotation burden seems to be low as we quantify in detail below. 91 | 92 | ### II.3.1 Nullity annotation density 93 | 94 | [Dietl, 2014][], reports 20 nullity annotations / KLOC (anno/KLOC). So far, nullable annotation density for the SDK sources have been: 95 | 96 | - <1 anno/KLOC for the library core (with <2 line/KLOC of general changes related to nullity); 97 | - 1 anno/KLOC for the samples. 98 | 99 | We attribute such a low annotation count to Dart's relaxed definition of assignability (see [A.1.4][assignment compatible] and [B.3.5](#new-assignment-semantics)), and a judicious choice in the scope of [NNBD][] ([E.3.1](#nnbd-scope)), in particular for optional parameters---namely our dual-view approach and use of compile-time default values to influence the nullability ([E.1.1](#opt-func-param)). 100 | 101 | We are not claiming that such a low annotation count will be typical (it certainly is not the case for the analyzer code itself, in part due to most AST fields being nullable), but results are encouraging. 102 | 103 | ### II.3.2 Dart SDK library 104 | 105 | Our strategy has been to run the [NNBD][] analyzer over the SDK library and address any reported issues. In addition, we added the nullable annotations mentioned in [Part F](#part-libs). Here is a summary, to date, of the changes. 106 | 107 | - `sdk/lib/core/core.dart` updated to include the definition of nullity annotations `@nullable`, `@non_null`, etc. (19 lines). 108 | 109 | - Nullable annotations were added in 70 locations. Most (64) were occurrences of `Object`. 110 | 111 | - The remaining updates (10 lines) were necessary to overcome the limitations in the analyzer's flow analysis capabilities. For example, when an optional nullable parameter is initialized to a non-null value when it is null at the point of call. This is a typical code change of this nature: 112 | 113 | ``` 114 | *** 280,287 **** 115 | static void checkValidIndex(int index, var indexable, 116 | ! [String name, int length, String message]) { 117 | ! if (length == null) length = indexable.length; 118 | // Comparing with `0` as receiver produces better dart2js type inference. 119 | --- 280,287 ---- 120 | * otherwise the length is found as `indexable.length`. 121 | */ 122 | static void checkValidIndex(int index, var indexable, 123 | ! [String name, int _length, String message]) { //DEP30: renamed to _length 124 | ! int length = _length == null ? indexable.length : _length; //DEP30: assign non-const default value 125 | // Comparing with `0` as receiver produces better dart2js type inference. 126 | if (0 > index || index >= length) { 127 | if (name == null) name = "index"; 128 | ``` 129 | 130 | - There remain two false positives related to limitations in the analyzer's flow analysis. 131 | 132 | ### II.3.3 Sample projects 133 | 134 | As a sanity test we have run the [NNBD][] analyzer on itself. As expected, a large number of problems are reported, due the nullable nature of AST class type fields. We have chosen not to tackle the annotation of the full analyzer code itself at the moment. On the other hand, we have annotated the nullity specific code, for which we have a nullity annotation ratio is 10 anno/KLOC. 135 | 136 | As for other projects, to date, we have run the [NNBD][] analyzer over the following SDK `pkg` projects totaling 2K LOC: 137 | 138 | - `expect` 139 | - `fixnum` 140 | 141 | Each projects required only a single nullity annotation. The remaining changes to `expect` were to remove redundant (in [DartC][]) explicit initialization of the optional `String reason` parameter with `null` (16 lines). 142 | -------------------------------------------------------------------------------- /doc-src/xx-history.md: -------------------------------------------------------------------------------- 1 | # Revision History {-} 2 | 3 | Major updates are documented here. 4 | 5 | ## 2016.02.29 (0.6.2) {#rev-062} 6 | 7 | Expansion and consolidation of proposal details concerning: [B.3.4](#var-init), Default initialization of non-null variables is like [DartC][]. Updates to [Appendix II](#appendix-tooling). 8 | 9 | ## 2016.02.26 (0.6.0) {#rev-060} 10 | 11 | **New** 12 | 13 | - [B.3.7](#type-promotion). Type promotion. 14 | - [B.3.8](#lub). Type least upper bound. 15 | - [B.3.9](#null-awareoperators). Null-aware operators. (Placeholder, section TBC) 16 | 17 | ## 2016.02.24 (0.5.0) {#rev-050} 18 | 19 | The main change is the addition of [Appendix II. Tooling and preliminary experience report](#appendix-tooling). In terms of individuals section changes we have: 20 | 21 | **New** 22 | 23 | - [B.3.5](#new-assignment-semantics). Adjusted semantics for "assignment compatible" ($\Longleftrightarrow$). 24 | - [B.3.6](#multi-members). Static semantics of members of ?T. 25 | - [E.1.1.1](#non-null-init). Optional parameters with non-null initializers are non-null. 26 | - [E.1.1.2](#field-param). Default field parameters are single view. 27 | - [E.3.5](#catch-type-qualification). Catch target types and meta type annotations. 28 | - [E.3.6](#local-var-analysis). Reducing the annotation burden for local variables, an alternative. 29 | - [E.3.7](#style-guide-object). Dart Style Guide on `Object` vs. `dynamic`. 30 | 31 | **Updated** 32 | 33 | - [B.2.1](#new-root). Ensuring `Object` is non-null: elect `_Anything` as a new root. Updated `_Basic` declaration and associated prose since the analyzer expects the `==` operator to be defined for `Null`. 34 | - [E.1.1](#opt-func-param). Optional parameters are nullable-by-default in function bodies only. Now makes reference to cases [E.1.1.1](#non-null-init) and [E.1.1.2](#field-param). 35 | - [G.2](#g-2-migration-aids). Adjusted name of nullity-scope annotations. Clarified the extent of the scope of `@nullable_by_default`, and that such annotations can also be applied to `part`s. 36 | -------------------------------------------------------------------------------- /doc-src/zz-references.md: -------------------------------------------------------------------------------- 1 | [BISL paper]: http://research.microsoft.com/en-us/um/people/leino/papers/krml188.pdf "Behavioral Interface Specification Languages" 2 | [BISL]: #BISL "Behavioral Interface Specification Languages" 3 | [Brandt, 2011]: https://www.dartlang.org/articles/why-dart-types "Why Dart Types Are Optional and Unsound" 4 | [C# non-nullable proposal]: https://gist.github.com/olmobrutall/31d2abafe0b21b017d56 "Proposal for C# Non-Nullable Reference Types" 5 | [C#]: https://msdn.microsoft.com/en-us/library/ms228593.aspx 6 | [CLU]: http://en.wikipedia.org/wiki/CLU_(programming_language) 7 | [Ceylon `Anything` API]: http://ceylon-lang.org/documentation/1.0/api/ceylon/language/Anything.type.html 8 | [Ceylon `List` API]: http://modules.ceylon-lang.org/repo/1/ceylon/language/1.1.0/module-doc/api/List.type.html 9 | [Ceylon functions]: http://ceylon-lang.org/documentation/1.1/spec/html/declarations.html#functions 10 | [Ceylon important types explained]: http://ceylon-lang.org/documentation/1.1/reference/structure/type-declaration/#selected_important_type_declarations 11 | [Ceylon optional types]: http://ceylon-lang.org/documentation/1.1/tour/basics/#optional_types 12 | [Ceylon union types]: http://ceylon-lang.org/documentation/1.1/spec/html/typesystem.html#uniontypes 13 | [Ceylon]: http://ceylon-lang.org 14 | [Chalin et al., 2008 IEEE]: http://ieeexplore.ieee.org/xpl/articleDetails.jsp?arnumber=4717283 15 | [Chalin et al., 2008]: https://drive.google.com/file/d/0B9T_03RPCjQRcXBKNVpQN1dZTFk/view?usp=sharing 16 | [Checker Framework CLIMB-to-top]: http://types.cs.washington.edu/checker-framework/current/checker-framework-manual.html#climb-to-top 17 | [Checker Framework generics]: http://types.cs.washington.edu/checker-framework/current/checker-framework-manual.html#generics 18 | [Checker Framework qualifier parameters]: http://types.cs.washington.edu/checker-framework/current/checker-framework-manual.html#qualifier-parameters 19 | [Checker Framework]: http://checkerframework.org 20 | [Chrome V8]: https://developers.google.com/v8/ 21 | [Code Contracts]: http://research.microsoft.com/en-us/projects/contracts 22 | [Cyclone]: http://cyclone.thelanguage.org 23 | [DDC issue #64]: https://github.com/dart-lang/dev_compiler/issues/64 24 | [DEP #30 Analyzer project page]: https://github.com/chalin/sdk/tree/dep30/pkg/analyzer 25 | [DEP #30]: https://github.com/dart-lang/dart_enhancement_proposals/issues/30 26 | [DEP 2015/03/18]: https://github.com/dart-lang/dart_enhancement_proposals/blob/master/Meetings/2015-03-18%20DEP%20Committee%20Meeting.md#more-proposals 27 | [DEP-generic-functions]: https://github.com/leafpetersen/dep-generic-methods 28 | [DEP-metadata-for-type-annotations]: https://github.com/chalin/DEP-metadata-for-type-annotations 29 | [DEP-non-null]: https://github.com/chalin/DEP-non-null 30 | [DEP]: https://github.com/dart-lang/dart_enhancement_proposals 31 | [DSC 2015/01]: http://news.dartlang.org/2015/01/dart-language-evolution-discussed-in.html 32 | [DSS]: http://www.ecma-international.org/publications/standards/Ecma-408.htm 33 | [Dart Analyzer]: https://www.dartlang.org/tools/analyzer 34 | [Dart CHANGELOG]: https://github.com/dart-lang/sdk/blob/master/CHANGELOG.md 35 | [Dart Code Formatter]: https://www.dartlang.org/tools/dartfmt 36 | [Dart Dev Compiler]: https://github.com/dart-lang/dev_compiler 37 | [Dart Iterator API]: https://api.dartlang.org/apidocs/channels/be/dartdoc-viewer/dart:core.Iterator 38 | [Dart List API]: https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:core.List 39 | [Dart List constructor API]: https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:core.List#id_List- 40 | [Dart List set length API]: https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:core.List#id_length= 41 | [Dart Optional Types, overview]: https://www.dartlang.org/articles/optional-types/#overview 42 | [Dart Optional Types]: https://www.dartlang.org/articles/optional-types 43 | [Dart SDK Analyzer project]: https://github.com/dart-lang/sdk/tree/master/pkg/analyzer 44 | [Dart SDK project]: https://github.com/dart-lang/sdk/ 45 | [Dart Style Guide, Object vs dynamic]: https://www.dartlang.org/articles/style-guide/#do-annotate-with-object-instead-of-dynamic-to-indicate-any-object-is-accepted 46 | [Dart Style Guide, var]: https://www.dartlang.org/articles/style-guide/#prefer-using-var-without-a-type-annotation-for-local-variables 47 | [Dart Style Guide]: https://www.dartlang.org/articles/style-guide 48 | [Dart VM]: https://www.dartlang.org/tools/dart-vm 49 | [Dart docgen]: https://www.dartlang.org/tools/dartdocgen 50 | [Dart for the Entire Web]: http://news.dartlang.org/2015/03/dart-for-entire-web.html 51 | [Dart issue #22]: https://code.google.com/p/dart/issues/detail?id=22 "Support non-nullable types" 52 | [Dart issue #5545]: https://code.google.com/p/dart/issues/detail?id=5545 "Add non-null accepting type annotations" 53 | [Dart pub Optional]: https://pub.dartlang.org/packages/optional 54 | [DartC]: #terms "Classic (i.e., current) Dart" 55 | [DartNNBD]: #terms "Dart as defined in this proposal with Non-Null By Default semantics" 56 | [Dietl et al., 2011]: http://homes.cs.washington.edu/~mernst/pubs/pluggable-checkers-icse2011.pdf "Building and using pluggable type-checkers, ICSE'11" 57 | [Dietl, 2014]: http://cs.au.dk/~amoeller/tapas2014/dietl.pdf 58 | [ES5 8.1]: http://ecma-international.org/ecma-262/5.1/#sec-8.1 59 | [Eberhardt, 2014]: http://blog.scottlogic.com/2014/11/20/swift-initialisation.html 60 | [Eclipse JDT, Null External Annotations]: https://wiki.eclipse.org/JDT_Core/Null_Analysis/External_Annotations 61 | [Eclipse JDT]: https://eclipse.org/jdt 62 | [Eclipse help, improve code quality]: http://help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-improve_code_quality.htm 63 | [Eclipse]: https://eclipse.org 64 | [Eiffel]: https://www.eiffel.com 65 | [Experiments with Strengthening JavaScript]: https://developers.google.com/v8/experiments 66 | [Fahndrich and Leino, 2003]: http://doi.acm.org/10.1145/949305.949332 "Declaring and checking non-null types in an object-oriented language, OOPSLA'03" 67 | [Fahndrich and Logozzo, 2010]: http://research.microsoft.com/apps/pubs/default.aspx?id=138696 "Clousot: Static Contract Checking with Abstract Interpretation, FoVeOOS'10" 68 | [Fantom nullable types]: http://fantom.org/doc/docLang/TypeSystem#nullableTypes 69 | [Fantom]: http://fantom.org 70 | [Findbugs]: http://findbugs.sourceforge.net 71 | [Fletch]: https://github.com/dart-lang/fletch 72 | [Flow maybe types]: http://flowtype.org/docs/nullable-types.html 73 | [Flow]: http://flowtype.org 74 | [Haskell maybe type]: https://wiki.haskell.org/Maybe 75 | [Haskell]: https://www.haskell.org 76 | [Haste]: http://haste-lang.org/ 77 | [Herrmann ECE 2014, page 3]: https://www.eclipsecon.org/europe2014/sites/default/files/slides/DeepDiveIntoTheVoid.pdf#page=3 78 | [Herrmann ECE 2014, page 5]: https://www.eclipsecon.org/europe2014/sites/default/files/slides/DeepDiveIntoTheVoid.pdf#page=5 79 | [IntelliJ]: https://www.jetbrains.com/idea 80 | [JB]: http://en.wikipedia.org/wiki/Java_bytecode "Java Bytecode" 81 | [JML]: #JML "Java Modeling Language" 82 | [JSExperimentalDirections]: https://drive.google.com/file/d/0B1v38H64XQBNT1p2XzFGWWhCR1k/view 83 | [JSR-308 explained]: http://www.oracle.com/technetwork/articles/java/ma14-architect-annotations-2177655.html 84 | [JSR-308]: https://jcp.org/en/jsr/detail?id=308 85 | [JS]: https://developer.mozilla.org/en-US/docs/Web/JavaScript "JavaScript" 86 | [JastAddJ non-null extension]: http://jastadd.org/web/jastaddj/extensions.php 87 | [JastAdd]: http://jastadd.org 88 | [Java Modeling Language]: http://www.jmlspecs.org "JML" 89 | [Java, lower bounded wildcards]: https://docs.oracle.com/javase/tutorial/java/generics/lowerBounded.html 90 | [JavaScript]: https://developer.mozilla.org/en-US/docs/Web/JavaScript 91 | [Java]: http://java.com 92 | [Kotlin null safety]: http://kotlinlang.org/docs/reference/null-safety.html 93 | [Kotlin]: http://kotlinlang.org 94 | [Ladd, 2013]: http://blog.sethladd.com/2013/12/compile-time-dead-code-elimination-with.html 95 | [Larch/C++]: http://www.eecs.ucf.edu/~leavens/larchc++.html 96 | [Larch]: http://www.sds.lcs.mit.edu/spd/larch 97 | [Lippert, 2013]: http://blog.coverity.com/2013/11/20/c-non-nullable-reference-types/#.VUJhoNNVhBd 98 | [ML]: http://en.wikipedia.org/wiki/ML_%28programming_language%29 99 | [NNBD]: #part-nnbd "Non-Null By Default" 100 | [NPE]: #why-nn-types "Potential null error" 101 | [Nielsen, 2015]: https://groups.google.com/a/dartlang.org/d/msg/core-dev/4gMYdO-5BIg/AaHVYR3xXHQJ "Dart Core Development › const/new" 102 | [NonNullStack.cs]: https://github.com/Microsoft/CodeContracts/blob/master/Demo/Stack/NonNullStack.cs 103 | [Null Object pattern, C&C]: http://c2.com/cgi/wiki?NullObject 104 | [Null Object pattern, Fowler]: http://refactoring.com/catalog/introduceNullObject.html 105 | [Nullness Checker]: http://types.cs.washington.edu/checker-framework/current/checker-framework-manual.html#nullness-checker 106 | [Nystrom, 2011]: http://journal.stuffwithstuff.com/2011/10/29/a-proposal-for-null-safety-in-dart 107 | [OCaml]: https://ocaml.org 108 | [Patrice Chalin]: https://plus.google.com/+PatriceChalin 109 | [Pony GitHub]: https://github.com/CausalityLtd/ponyc 110 | [Pony]: http://www.ponylang.org 111 | [ROI]: #ROI "Return On Investment" 112 | [Rust]: http://www.rust-lang.org 113 | [SO104799]: http://stackoverflow.com/questions/104799/why-arent-java-collections-remove-methods-generic 114 | [Spark Ada]: http://www.spark-2014.org 115 | [Spec#]: http://research.microsoft.com/en-us/projects/specsharp 116 | [Splint]: http://www.splint.org 117 | [Swift on initialization]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID203 118 | [Swift optional type]: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID330 119 | [Swift reference]: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/index.html 120 | [Swift]: https://developer.apple.com/swift/ 121 | [TS issue #1265]: https://github.com/Microsoft/TypeScript/issues/1265 122 | [TS issue #185]: https://github.com/Microsoft/TypeScript/issues/185 123 | [TS issue #3003]: https://github.com/Microsoft/TypeScript/issues/3003 124 | [TSLS 3.2.6]: https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.2.6 125 | [TSLS]: http://www.typescriptlang.org/Content/TypeScript%20Language%20Specification.pdf 126 | [TypeScript]: http://www.typescriptlang.org 127 | [Use Java 8 Optional]: http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html 128 | [assignment compatible]: #assignment-compatible 129 | [chalin/analyzer_cli]: https://github.com/chalin/analyzer_cli/tree/dep30 130 | [chalin/sdk, dep30 branch]: https://github.com/chalin/sdk/tree/dep30 131 | [chalin/sdk]: https://github.com/chalin/sdk 132 | [chalin]: https://github.com/chalin 133 | [dart/runtime/lib/integers_patch.dart#L48]: https://github.com/dart-lang/sdk/blob/master/runtime/lib/integers_patch.dart#L48 134 | [dart2js]: https://www.dartlang.org/tools/dart2js 135 | [del]: #terms "DEL: Text removed from the DSS" 136 | [dynamic type error]: #terms "A type error reported in checked mode" 137 | [ins]: #terms "INS: Text added or updated in the DSS" 138 | [option type]: http://en.wikipedia.org/wiki/Option_type 139 | [runtime error]: #terms "An exception raised during execution" 140 | [sloccount]: http://www.dwheeler.com/sloccount 141 | [static warning]: #terms "A problem reported by the static checker" 142 | [unit type]: http://en.wikipedia.org/wiki/Unit_type 143 | [usability]: http://en.wikipedia.org/wiki/Usability 144 | -------------------------------------------------------------------------------- /doc/dep-non-null.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chalin/DEP-non-null/3dac7f692d4425f73e945d92bdedfe2f39fa9fcc/doc/dep-non-null.pdf -------------------------------------------------------------------------------- /example/doc/box.dart: -------------------------------------------------------------------------------- 1 | class Box< /*(...)*/ T /*extends (...) Object*/ > { 2 | final /*(non-null)*/ T _default; // non-null (G2.1) 3 | /*(matching)*/ T value; // match nullity of type parameter T (G2.3) 4 | 5 | Box(this._default, this.value); 6 | 7 | /*(nullable)*/ T maybeNull() => // nullable (G2.2) 8 | value == _default ? null : value; 9 | 10 | /*(non-null)*/ T neverNull() => value == null ? _default : value; 11 | } 12 | -------------------------------------------------------------------------------- /example/doc/null.dart: -------------------------------------------------------------------------------- 1 | const Null $null = null; 2 | 3 | void main() { 4 | int i = null, 5 | j = $null, 6 | k = "a-string"; 7 | print("i = $i, j = $j, k = $k"); 8 | print("i is ${i.runtimeType}, j is ${j.runtimeType}"); 9 | 10 | //--- 11 | print(null is int); 12 | // Output in production mode: 13 | // i = null, j = null, k = a-string 14 | // i is Null, j is Null 15 | // false 16 | } 17 | -------------------------------------------------------------------------------- /example/doc/optional-parameters.dart: -------------------------------------------------------------------------------- 1 | int f([int i = 0]) => i; // i is an optional positional parameter 2 | int g({int j : 0}) => j; // j is an optional named parameter 3 | -------------------------------------------------------------------------------- /example/doc/point.dart: -------------------------------------------------------------------------------- 1 | class Point { 2 | final num x, y; 3 | Point(this.x, this.y); 4 | Point operator +(Point other) => new Point(x+other.x, y+other.y); 5 | String toString() => "x: $x, y: $y"; 6 | } 7 | 8 | void main() { 9 | Point p1 = new Point(0, 0); 10 | Point p2 = new Point(10, 10); 11 | 12 | print("p1 + p2 = ${p1 + p2}"); 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DEP-non-null", 3 | "version": "0.1.0", 4 | "description": "Dart DEP for Non-null Types and Non-null By Default (NNBD)", 5 | "homepage": "https://github.com/chalin/DEP-non-null", 6 | "bugs": "https://github.com/chalin/DEP-non-null", 7 | "contributors": { 8 | "Patrice Chalin": "chalin@dsrg.org" 9 | }, 10 | "license": "Apache-2.0", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/chalin/DEP-non-null.git" 14 | }, 15 | "dependencies": { 16 | "pandoc-filter": "*" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: DEP_non_null 2 | dev_dependencies: 3 | unittest: any 4 | -------------------------------------------------------------------------------- /tool/depNonNullFilter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | // Pandoc filter for DEP-non-null. 6 | // 7 | // Handles: 8 | // - Special "links" for ins/del. 9 | // - Format "Comment" as \sf. 10 | 11 | var pandoc = require('pandoc-filter'); 12 | var Str = pandoc.Str; 13 | var optMd = process.argv.length > 2 && process.argv[2] === '--md'; 14 | 15 | if (typeof String.prototype.startsWith != 'function') { 16 | // see below for better implementation! 17 | String.prototype.startsWith = function (str){ 18 | return this.indexOf(str) === 0; 19 | }; 20 | } 21 | 22 | function isDssChangeCmd(type, value, str) { 23 | // Link([Inline], Target) 24 | // Target = [URL,title] 25 | 26 | var linkTarget = value[1]; 27 | if (type !== 'Link' || !linkTarget || linkTarget.length < 2) 28 | return false; 29 | 30 | return linkTarget[1].startsWith(str); 31 | } 32 | 33 | function action(type, value, format, meta) { 34 | if (isDssChangeCmd(type, value, 'INS:')) { 35 | var list = value[0].slice(); 36 | list.unshift(Str("[[")); 37 | list.push(Str("]]")); 38 | return pandoc.Link(list, value[1]); 39 | } 40 | 41 | if (isDssChangeCmd(type, value, 'DEL:')) 42 | return pandoc.Strikeout(value[0]); 43 | 44 | if (type === 'Str' 45 | && (value.startsWith('Comment.') || value.startsWith('Comments:')) ) { 46 | return [ pandoc.RawInline('tex','\\sf{}'), Str(value) ]; 47 | } 48 | 49 | // Dart syntax highlighting isn't supported; use Java instead. 50 | if (type === 'CodeBlock') { 51 | var codeStyle = value[0][1][0]; 52 | if (codeStyle !== 'dart') return; 53 | 54 | var attr = value[0].slice(); 55 | var code = value[1]; 56 | var codeStyleEtc = attr[1].slice(); 57 | codeStyleEtc[0] = 'java'; 58 | attr[1] = codeStyleEtc; 59 | return pandoc.CodeBlock(attr, code); 60 | } 61 | 62 | // Ensure custom header ids get embedded into generated markdown 63 | if (type === 'Header' && value[1].length > 0) { 64 | var name = value[1][0]; 65 | return [ 66 | pandoc.RawBlock('html',''), 67 | pandoc.Header(value[0], value[1], value[2]) 68 | ]; 69 | } 70 | 71 | if (optMd && type === 'RawInline') { 72 | var rawText = value[1]; 73 | var text = replaceLatex(rawText); 74 | return rawText === text 75 | ? undefined // don't change the value 76 | : Str(text); 77 | } 78 | 79 | if (optMd && type === 'Math') { 80 | var mathText = replaceLatex(value[1]); 81 | return (mathText.match(/^[^a-zA-Z]$/)) // don't emph lone non-letter symbols like ⊥ 82 | ? Str(mathText) 83 | : pandoc.Emph([Str(mathText)]); 84 | } 85 | } 86 | 87 | function replaceLatex(rawLatex) { 88 | return rawLatex 89 | .replace(/\\cd{([^}]+)}/g, '$1') 90 | .replace(/\\pg{([^}]+)}/g, '$1') 91 | .replace(/\\nut{([^}]+)}/g, '?$1') 92 | .replace(/\\bot\b/g, '⊥') 93 | .replace(/\\asgn\b/g, '⟺') 94 | .replace(/\\Longleftrightarrow\b/g, '⟺') 95 | .replace(/\\DYNAMIC\b/g, 'dynamic') 96 | .replace(/\\ldots\b/g, '...') 97 | .replace(/\\le\b/g, '≤') 98 | .replace(/\\subtype\b/g, '<:') 99 | .replace(/\\mst\b/g, '<<') 100 | .replace(/\\qn\b/g, '?') 101 | .replace(/\\land\b/g, '∧') 102 | .replace(/\\lor\b/g, '∨') 103 | .replace(/\\lnot\b/g, '¬') 104 | .replace(/\\impliedby\b/g, '⟸') // ⇐ 105 | .replace(/\\inttype\b/g, 'int') 106 | .replace(/\\true\b/g, 'true') 107 | .replace(/\\false\b/g, 'false') 108 | .replace(/\\botObject\b/g, '⊥_Object') // x~subscript~ 109 | .replace(/{}/g, '') 110 | ; 111 | } 112 | 113 | pandoc.stdio(action); 114 | --------------------------------------------------------------------------------