目录

词汇表

以下是 Dart 文档中使用的术语定义。

Assist

#

Assist 是一个自动化的、局部代码编辑,旨在对代码进行常见的改进。 Assist 的示例包括将 switch 语句转换为 switch 表达式,反转 if 语句中的 thenelse 块,以及将小部件插入小部件结构。

相关文档和资源:

Constant context

#

常量上下文 是一个代码区域,其中不需要包含 const 关键字,因为该区域内的所有内容都必须是常量,所以它是隐含的。 以下位置是常量上下文:

  • 使用 const 关键字前缀的列表、映射或集合字面量内的所有内容。例如:

    dart
    var l = const [/*constant context*/];
  • 常量构造函数调用的参数。例如:

    dart
    var p = const Point(/*constant context*/);
  • 使用 const 关键字前缀的变量的初始化程序。例如:

    dart
    const v = /*constant context*/;
  • 注释。

  • case 子句中的表达式。例如:

    dart
    void f(int e) {
      switch (e) {
        case /*constant context*/:
          break;
      }
    }

相关文档和资源:

Definite assignment

#

确定赋值分析是确定代码中每个局部变量在每个点的以下哪个条件为真的过程:

  • 变量已被明确赋值(明确赋值)。
  • 变量肯定未被赋值(明确未赋值)。
  • 变量可能已被赋值也可能未被赋值,这取决于到达该点的执行路径。

确定赋值分析有助于查找代码中的问题,例如可能未被赋值的变量被引用的位置,或者只能赋值一次的变量在可能已被赋值后再次被赋值的位置。

例如,在以下代码中,变量 s 在作为参数传递给 print 时肯定未赋值:

dart
void f() {
  String s;
  print(s);
}

但在以下代码中,变量 s 已明确赋值:

dart
void f(String name) {
  String s = 'Hello $name!';
  print(s);
}

即使存在多个可能的执行路径,确定赋值分析也能判断变量是否已被明确赋值(或未赋值)。 在以下代码中,如果执行经过 if 语句的真或假分支,则调用 print 函数,但由于无论哪个分支被执行,s 都被赋值,因此在它传递给 print 之前,它已被明确赋值:

dart
void f(String name, bool casual) {
  String s;
  if (casual) {
    s = 'Hi $name!';
  } else {
    s = 'Hello $name!';
  }
  print(s);
}

在流分析中,if 语句的结尾被称为 连接——两个或多个执行路径合并在一起的地方。 在有连接的地方,分析表明,如果变量在所有正在合并的路径中都被明确赋值,则该变量被明确赋值;如果变量在所有路径中都被明确未赋值,则该变量被明确未赋值。

有时变量在一个路径上被赋值,但在另一个路径上没有被赋值,在这种情况下,变量可能已被赋值也可能未被赋值。 在下面的示例中,if 语句的真分支可能被执行也可能不被执行,因此变量可能已被赋值也可能未被赋值:

dart
void f(String name, bool casual) {
  String s;
  if (casual) {
    s = 'Hi $name!';
  }
  print(s);
}

如果存在一个不为 s 赋值的假分支,情况也是如此。

循环的分析稍微复杂一些,但它遵循相同的基本推理。 例如,while 循环中的条件总是被执行,但主体可能被执行也可能不被执行。因此,就像 if 语句一样,在while 语句的结尾处,在条件为 true 的路径和条件为 false 的路径之间存在一个连接。

相关文档和资源:

Function

#

一个总称,用于指代顶层函数、局部函数、静态方法和实例方法。

相关文档和资源:

Irrefutable pattern

#

不可反驳的模式 是总是匹配的模式。 不可反驳的模式是唯一可以出现在 不可反驳的上下文 中的模式:[声明][] 和 [赋值][] 模式上下文。

相关文档和资源:

Mixin application

#

mixin 应用 是将 mixin 应用到类时创建的类。 例如,考虑以下声明:

dart
class A {}

mixin M {}

class B extends A with M {}

B 是将 M 应用到 A 的 mixin 应用的子类,有时命名为 A+M。类 A+MA 的子类,并且具有从 M 复制的成员。

可以通过将其定义为以下方式来为 mixin 应用提供实际名称:

dart
class A {}

mixin M {}

class A_M = A with M;

给定 A_M 的此声明,B 的以下声明等同于原始示例中 B 的声明:

dart
class B extends A_M {}

相关文档和资源:

Override inference

#

覆盖推断是根据被覆盖方法或方法的对应类型推断方法声明中任何缺失类型的方法。

如果候选方法(缺少类型信息的方法)覆盖单个继承方法,则推断被覆盖方法的对应类型。 例如,考虑以下代码:

dart
class A {
  int m(String s) => 0;
}

class B extends A {
  @override
  m(s) => 1;
}

Bm 的声明是一个候选对象,因为它缺少返回类型和参数类型。 因为它覆盖了一个单一方法(A 中的 m 方法),所以将使用被覆盖方法中的类型来推断缺失的类型,这就像 B 中的方法被声明为 int m(String s) => 1; 一样。

如果候选方法覆盖多个方法,并且其中一个被覆盖方法 Ms 的函数类型是所有其他被覆盖方法的函数类型的超类型,则使用 Ms 来推断缺失的类型。 例如,考虑以下代码:

dart
class A {
  int m(num n) => 0;
}

class B {
  num m(int i) => 0;
}

class C implements A, B {
  @override
  m(n) => 1;
}

Cm 的声明是覆盖推断的候选对象,因为它缺少返回类型和参数类型。 它覆盖了 A 中的 mB 中的 m,因此编译器需要从中选择一个来推断缺失的类型。 但由于 Am 的函数类型 (int Function(num)) 是 Bm 的函数类型 (num Function(int)) 的超类型,因此使用 A 中的函数来推断缺失的类型。结果与在 C 中将方法声明为 int m(num n) => 1; 相同。

如果被覆盖的方法中没有一个的函数类型是所有其他被覆盖方法的超类型,则会发生错误。

相关文档和资源:

Part file

#

部分文件是一个包含 part of 指令的 Dart 源文件,并使用 part 指令包含在库中。

相关文档和资源:

Potentially non-nullable

#

如果一个类型显式非空或是一个类型参数,则该类型为 潜在非空

如果类型名称后没有问号 (?),则该类型显式非空。 请注意,有一些类型总是可空的,例如 Nulldynamic,并且 FutureOr 只有在后面没有问号 并且 类型参数是非空(例如 FutureOr<String>)时才是非空。

类型参数可能是潜在非空的,因为实际运行时类型(作为类型参数指定的类型)可能是非空的。 例如,给定 class C<T> {} 的声明,类型 C 可以与非空类型参数一起使用,如 C<int>

相关文档和资源:

Public library

#

公共库是位于包的 lib 目录中但不在 lib/src 目录内的库。

相关文档和资源:

Quick fix

#

一个自动化的、局部代码编辑,旨在修复特定诊断报告的问题。

相关文档和资源:

Refactor

#

重构是一个代码编辑,针对非局部修改或需要用户交互的修改。 重构的示例包括重命名、删除或提取代码。

相关文档和资源:

Refutable pattern

#

可反驳模式 是可以针对值进行测试以确定模式是否与值匹配的模式。 如果没有,模式就会_反驳_或否定匹配。 可反驳模式出现在 [匹配上下文][] 中。

相关文档和资源:

Subclass

#

子类 是通过使用 extends 关键字或通过 [mixin 应用][] 来继承另一个类实现的类。

dart
// A 是 B 的子类;B 是 A 的超类。
class A extends B {}

// B1 的超类是 `A with M`,其超类是 A。
class B1 extends A with M {}

子类关系也暗示了相关的 子类型 关系。 例如,class A 隐式定义了一个关联类型 A,该类的实例 A 位于其中。 因此,class A extends B 不仅声明类 AB 的子类,而且还确定 类型 A 是类型 B子类型

子类关系是子类型关系的子集。 当文档说明“S 必须是 T 的子类型”时,S 可以是 T 的子类。 但是,反过来并非如此:并非所有子类型都是子类。

相关文档和资源:

Subtype

#

子类型 关系是指某个类型的的值可以在需要另一个类型(超类型)的值的地方进行替换。 例如,如果 ST 的子类型,那么你可以在需要 T 类型的值的地方替换 S 类型的的值。

子类型支持其超类型的所有操作(以及可能的一些额外操作)。 在实践中,这意味着你可以将子类型的赋值给任何期望超类型的位置,并且超类型的所有方法都可以在子类型上使用。

这至少在静态上是正确的。 具体 API 可能不允许在运行时进行替换,这取决于其操作。

一些子类型关系基于类型的结构,例如可空类型(例如,intint? 的子类型)和函数类型(例如,String Function()void Function() 的子类型)。

子类型也可以通过 实现继承(直接或间接)为类引入:

dart
// A 是 B 的子类型,但不是 B 的子类。
class A implements B {}

// C 是 D 的子类型和子类。
class C extends D {}

相关文档和资源:

Variance and variance positions

#

如果类型的整体随实际类型参数“共同变化”,则称类的类型参数(或其他类型声明,如 mixin)是_协变_的。 换句话说,如果用子类型替换类型参数,则类型整体也是子类型。

例如,类 List 的类型参数是协变的,因为列表类型与其类型参数共同变化:List<int>List<Object> 的子类型,因为 intObject 的子类型。

在 Dart 中,所有类、mixin、mixin 类和枚举声明的所有类型参数都是协变的。

但是,函数类型有所不同: 函数类型在其返回类型中是协变的,但在其参数类型中是相反的(称为_逆变_)。 例如,类型 int Function(int) 是类型 Object Function(int) 的子类型,但它是 int Function(Object) 的超类型。

如果您考虑它们的 可替换性,这很有意义。 如果您使用静态类型为 int Function(int) 的函数调用一个函数,则该函数实际上可以在运行时为 int Function(Object) 类型。 根据静态类型,您期望能够向其传递一个 int。 这将是可以的,因为该函数实际上接受任何 Object,这包括每个 int 类型的对象。 同样,返回的结果将是 int 类型,这也是您根据静态类型所期望的。

因此,int Function(Object)int Function(int) 的子类型。

请注意,参数类型的所有内容都颠倒了。 特别是,函数类型之间的这种子类型关系要求参数类型存在_相反的_子类型关系。 例如,void Function(Object)void Function(int) 的子类型,因为 intObject 的子类型。

对于像 List<void Function(int)> 这样的更复杂的类型,您必须考虑类型的_位置_。 为此,将类型的其中一部分转换为占位符,然后考虑将不同的类型放置在该位置时类型会发生什么变化。

例如,将 List<void Function(_)> 视为类型的模板,您可以在其中用不同的类型替换占位符 _。 此类型在该占位符出现的位置上是逆变的。

以下是通过用 Objectint 代替 _ 来说明这一点。 List<void Function(Object)>List<void Function(int)> 的子类型,因为 void Function(Object)void Function(int) 的子类型,因为 voidvoid 的子类型(返回类型),并且 intObject 的子类型(参数类型,顺序相反)。 因此,_ 处的类型与类型 List<void Function(_)> 的整体变化方向相反,根据定义,这个“相反方向”使其成为_逆变位置_。

协变位置 的定义类似。 例如,在类型 List<_>中,_ 位于协变位置,在类型 _ Function(int) 中,_ 也位于协变位置。

还有一种称为_不变_的位置,但它出现得少得多,因此此处省略细节。

在实践中,通常只需要知道类、mixin 等的类型参数位于协变位置,函数类型的返回类型也是如此,但是参数类型位于逆变位置。

相关文档和资源: