“表达式解析为未使用的l值”与“表达式未使用”

时间:2018-08-21 21:25:46

标签: swift compiler-errors lvalue

考虑以下代码:

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
    }
}

为什么消息不同,为什么只有一个错误?我理解它们的含义,但我不明白为什么编译器以不同的方式处理它们。

2 个答案:

答案 0 :(得分:2)

自初始化期间,编译器在初始化器中将self.bar视为l值的原因即使您已声明,也可以在初始化期间的任何时候将值分配给常量属性将其作为常量,因此编译器会将其视为错误,因为有机会修改常量...

self.bar函数中的test()被视为r值,因为该属性被声明为常量,并且编译器知道您无法对其进行修改,因此不会产生任何错误,并且将被视为r值,未使用返回值。

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

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
    }
}

答案 1 :(得分:0)

诊断方面的差异是由于编译器将表达式视为左值还是右值而导致的。这些是用户不需要知道的内部编译器术语,但实际上,左值主要用于表示可变表达式-即可以使用=或传递的{{ 1}}。左值由组件组成,例如表达式inout可以表示为具有变量基数a.b[],成员访问组件a和下标组件.b的左值。

可以左加载左值以生成右值,该右值通常称为值,并且是不可变的。例如,进行函数调用[]将需要加载左值foo(a.b[])才能作为参数传递。

由于要编译器执行加载(这可能会引发副作用,例如调用变量g​​etter),或者是否要存储,所以模棱两可的事实会导致未使用的左值错误一个值,但尚未写出a.b[](这不会称为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

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

尽管

class Foo { let bar = "Hello world!" init () { self.bar // Error: Expression resolves to an unused l-value } } 是不可变的,但仍被视为左值。还记得上面的内容,当我说过一个lvalue主要是用来表示可变表达式的吗?嗯,事实证明,它们还可以用于表示适合左值模型的不可变表达式(即可以使用左值分量建模的表达式,例如self.bar)。

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

self

由于以下事实:struct S { let x: Int = 1 let y: Int init() { self.y = self.x } } 如果被视为右值,则会在完全初始化之前执行self.x的加载。

通过被视为左值,DefiniteInitialization(诊断无效初始化的编译器遍历)能够识别出self是对已初始化属性的读取,因此允许代码进行编译。但是,话虽如此,DefiniteInitialization不能真正识别出self.x的加载方式以及随后对已初始化属性的成员访问之后,才没有真正的原因。最终,self在此处被视为左值是实现细节。