Ответ 1
Это действительно ошибка, которая была исправлена by этот запрос на растяжение, который должен сделать его выпуском Swift 4.2, все будет хорошо.
Если кто-то заинтересован в кажущихся странными требованиями воспроизвести его, то есть (не совсем) краткий обзор того, что происходит...
Вызовы стандартной функции библиотеки type(of:)
разрешаются как особый случай с помощью проверки типа; Они заменяются в АСТ специальным "выражением динамического типа". Я не исследовал, как обрабатывался предшественник dynamicType
, но я подозреваю, что он сделал что-то подобное.
При испускании промежуточного представления (SIL для конкретного) для такого выражения компилятор проверяет, будет ли вид, если получившийся метатип "толстый" (для экземпляров класса и протокола), и если это так испускает подвыражение (т.е. переданный аргумент) и получает его динамический тип.
Однако, если полученный метатип "тонкий" (для структур и перечислений), компилятор знает значение метатипа во время компиляции. Поэтому подвыражение нужно оценивать только в том случае, если оно имеет побочные эффекты. Такое выражение испускается как "проигнорированное выражение".
Проблема заключалась в том, что логика в испускании игнорируемых выражений также была lvalues (выражение, которое может быть присвоено и передано как inout
).
Swift lvalues может состоять из нескольких компонентов (например, для доступа к свойству, выполнения развертывания силы и т.д.). Некоторые компоненты являются "физическими", что означает, что они создают адрес для работы, а другие компоненты являются "логическими", что означает, что они состоят из геттера и сеттера (точно так же, как вычисленные переменные).
Проблема заключалась в том, что физические компоненты ошибочно считались свободными от побочных эффектов; однако разворачивание силы является физическим компонентом и не является свободным от побочных эффектов (выражение ключевого пути также является нечистым физическим компонентом).
Таким образом, игнорируемое выражение lvalues с компонентами разворота силы неверно не оценит развертывание силы, если они созданы только из физических компонентов.
Давайте посмотрим на пару случаев, которые в настоящее время вылетают (в Swift 4.0.3), и объясняют, почему ошибка была вставлена в сторону и разворот силы был правильно оценен:
let foo: String? = nil
print(type(of: foo!)) // crash!
Здесь foo
не является lvalue (как объявлено let
), поэтому мы просто получаем его значение и силу разворачиваем.
class C {} // also crashes if 'C' is 'final', the metatype is still "thick"
var foo: C? = nil
let x = type(of: foo!) // crash!
Здесь foo
является lvalue, но компилятор видит, что полученный метатип "толстый", и поэтому зависит от значения foo!
, поэтому загружается lvalue и поэтому вычисляется разгрузка силы.
Давайте также рассмотрим этот интересный случай:
class Foo {
var bar: Bar?
}
struct Bar {}
var foo = Foo()
print(type(of: foo.bar!)) // crash!
Он сработает, но это не будет, если foo
отмечен как final
. Получающийся метатип "тонкий" в любом случае, поэтому какая разница foo
составляет final
make?
Ну, когда foo
не является окончательным, компилятор не может просто ссылаться на свойство bar
по адресу, поскольку он может быть переопределен подклассом, который вполне может повторно реализовать его как вычисленное свойство. Таким образом, lvalue будет содержать логический компонент (вызов bar
s getter), поэтому компилятор выполнит нагрузку, чтобы оценить потенциальные побочные эффекты этого геттер-вызова (и разворот силы будет также оцениваться в нагрузки).
Однако, когда foo
является final
, доступ к объекту bar
может быть смоделирован как физический компонент, то есть его можно называть адресом. Поэтому компилятор неправильно предположил, что, поскольку все компоненты lvalue являются физическими, он может пропустить оценку.
В любом случае, эта проблема исправлена. Надеюсь, кто-то найдет вышеперечисленное полезное и/или интересное:)