【问题标题】:"Expression resolves to an unused l-value" vs "Expression is unused"“表达式解析为未使用的左值”与“表达式未使用”
【发布时间】:2018-08-21 21:25:46
【问题描述】:

考虑以下代码:

class Foo {
    let bar = "Hello world!"
    init () {
        self // Warning: Expression of type 'Foo' is unused
        self.bar // Error: Expression resolves to an unused l-value
    }
    func test () {
        self.bar // Warning: Expression of type 'String' is unused
    }
}

为什么消息不同,为什么只有一个是错误的?我明白它们的意思,我只是不明白为什么编译器会以不同的方式处理它们。

【问题讨论】:

  • @Hamish 关于您的最后一条评论,难道不是只有尚未为其赋值的情况吗?
  • @Hamish 所以如果我没看错,这只是编译器在检查 l 值时不检查已经初始化的 lets 的限制?

标签: swift compiler-errors lvalue


【解决方案1】:

编译器在初始化过程中将 self.bar 视为左值的原因 您可以在初始化过程中的任何时候为常量属性分配一个值,即使您已将其声明为一个常量,因此编译器会将其视为错误,因为有机会修改常量...

虽然 test() 中的 self.bar 函数被视为 r 值,因为该属性被声明为常量并且编译器知道您无法修改它,因此不会生成错误并将其视为r-value,返回值未使用..

如果你需要更好的理解尝试将你的属性改为变量而不是常量,你会发现即使test()方法中的self.bar语句也会出错,因为编译器猜测这个值也可以是一个左值。

class Foo {
    var bar = "Hello world!"
    init () {
        self // Warning: Expression of type 'Foo' is unused
        self.bar  // Error: Expression resolves to an unused l-value
    }
    func test () {
        self.bar // Changed to : Error: Expression resolves to an unused l-value
    }
}

【讨论】:

  • 当我将其更改为 var 时会发生什么,您是对的,我没有考虑到这一点,它在某种程度上解释了它。但是当我尝试在初始化程序中将它分配为let 时,我得到“不可变值'self.bar'可能只被初始化一次”,所以这部分没有意义,除非由于某种原因编译器不是弄清楚它已经在我原来的情况下被初始化了。
  • 是的,你是对的,我认为编译器可能会在检查存储的属性是否已初始化或已使用初始值初始化之前检查 l 值!也许当一切顺利并且在完成初始化之前,编译器想要检查是否设置了所有存储的属性,所以这里会发现该属性已经被分配了一个初始值......
【解决方案2】:

诊断的差异是由于编译器将表达式视为左值还是右值的差异。这些是用户不需要知道的内部编译器术语,但本质上,左值主要用于表示可变表达式——即可以使用= 分配给或传递inout 的表达式。左值由组件组成,例如表达式a.b[] 可以表示为具有变量基a、成员访问组件.b 和下标组件[] 的左值。

一个左值可以被加载来产生一个右值,它通常被称为一个值,并且是不可变的。例如,进行函数调用 foo(a.b[]) 将需要加载左值 a.b[] 以便作为参数传递。

由于您是否希望编译器执行加载(这可能触发副作用,例如对变量 getter 的调用)或您是否打算存储一个值,但尚未写入 = ...(不会调用 getter)。您只会收到未使用的右值的警告,因为它不是模棱两可的。

好的,让我们来谈谈你提出的每个例子:

class Foo {
  let bar = "Hello world!"
  func test () {
    self.bar // Warning: Expression of type 'String' is unused
  }
}

self.bar 在这里被视为右值,因为它是一个不可变的表达式,因为属性 bar 是一个已经初始化的常量。


class Foo {
  init () {
    self // Warning: Expression of type 'Foo' is unused
  }
}

self 在这里被视为右值,因为self 在类声明的主体中是不可变的。


好的,现在进入有趣的:

class Foo {
  let bar = "Hello world!"
  init () {
    self.bar // Error: Expression resolves to an unused l-value
  }
}

(请注意,最近#14227 改进了此诊断)

self.bar 在这里被视为左值,尽管它是不可变的。还记得上面我说过左值主要用于表示可变表达式吗?好吧,事实证明,它们也可以用来表示适合左值模型的不可变表达式(即可以使用左值组件建模的表达式,例如self.bar)。

那么为什么 self.bar 在这里被视为左值呢?结果是it is in order to avoid triggering a load of the base expressionself,这将被视为对self的读取,因此使以下代码无效:

struct S { 
  let x: Int = 1
  let y: Int

  init() {
    self.y = self.x 
  } 
}

由于self.x 将在完全初始化之前执行self 的加载,如果它被视为右值。

通过被视为左值,DefiniteInitialization(诊断无效初始化的编译器传递)能够识别self.x 是对已初始化属性的读取,因此允许代码编译。但是话虽这么说,但没有真正的原因 DefiniteInitialization 无法识别 self 被加载的模式,然后是对已初始化属性的成员访问。最终,self.x 在这里被视为左值是一个实现细节。

【讨论】:

    猜你喜欢
    • 2016-04-22
    • 1970-01-01
    • 2015-06-15
    • 2015-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-30
    相关资源
    最近更新 更多