Оценка Swift @autoclosure зависит от аннотаций типов? (ошибка компилятора?)

Сегодня я снова поигрался со Swift и нуждался в функции undefined(). В основном это функция, которая может быть любого типа, но вылетает, когда она действительно запускается / оценивается. Это полезно, если у вас еще не было времени на реализацию определенного выражения, но вы хотите проверить, проверяет ли тип программы.

Я также реализовал Result<T> и (сомнительную) функцию chain, которая возвращает левую Result<> в случае неудачи, в противном случае - правую Result<>. Следовательно, подпись chain<L,R>(l : Result<L>, r : @autoclosure () -> Result<R>) -> Result<R>. Я определил правое Result<> как @autoclosure, потому что нет необходимости оценивать его, если и только если левый является неудачным.

Меня не интересует полезность (или улучшения) чего-либо из этого, меня просто интересует, почему моя программа вылетает в строке, отмеченной [L3].

Обратите внимание, что

  • как и ожидалось [L1] работает нормально (|| лениво оценивается)
  • [L2] работает нормально (chain тоже ленив в правом аргументе)

Но как ни странно

  • [L3] вызывает сбой программы при оценке undefined()

Насколько я понимаю, L2 и L3 должны быть эквивалентны во время выполнения: L3 просто сообщает средству проверки типов то, что он уже знает ... Тот же эффект, кстати, произойдет, если вы измените тип undefined на () -> Result<T> (вместо () -> T), тогда он все работает даже без as Result<String>.

Это весь мой код:

import Foundation

enum Result<T> {
    case Success(@autoclosure () -> T);
    case Failure(@autoclosure () -> NSError)
}

func undefined<T>(file:StaticString=__FILE__, line:UWord=__LINE__) -> T {
    fatalError("undefined", file:file, line:line)
}

func chain<L,R>(l : Result<L>, r : @autoclosure () -> Result<R>) -> Result<R> {
    switch(l) {
    case .Failure(let f):
        return .Failure(f())
    case .Success(let _):
        return r()
    }
}

func testEvaluation() {
    let error = NSError(domain: NSPOSIXErrorDomain, code: Int(ENOENT), userInfo: nil)
    let failure : Result<String> = .Failure(error)
    assert(true || undefined() as Bool) // [L1]: works
    let x : Result<String> = chain(failure, undefined() as Result<String>) // [L2]: works
    print("x = \(x)\n")
    let y : Result<String> = chain(failure, undefined()) // [L3]: CRASHES THE PROGRAM EVALUATING undefined()
    print("y = \(y)\n")
}

testEvaluation()

Вы не поверите, но результат работы программы:

x = (Enum Value)
fatal error: undefined: file main.swift, line 27
Illegal instruction: 4

Этого не может быть! Почему as Result<String> (это единственное различие между L2 и L3) изменит вывод программы? Это должно полностью обрабатываться в средстве проверки типов, а это означает, что во время компиляции, нет? Ошибка компилятора?

Самый быстрый способ воспроизвести это:

  1. скопируйте весь мой код в буфер обмена
  2. cd /tmp
  3. pbpaste > main.swift
  4. xcrun -sdk macosx swiftc main.swift
  5. ./main

фактический выход:

x = (Enum Value)
fatal error: undefined: file main.swift, line 27
Illegal instruction: 4

ожидаемый результат:

x = (Enum Value)
y = (Enum Value)

person Johannes Weiss    schedule 17.01.2015    source источник
comment
Пахнет жучком. Если вы добавите println(file) прямо перед fatalError, он напечатает ‹EXPR› без сбоев.   -  person qwerty_so    schedule 17.01.2015
comment
@ThomasKilian, добавил print("--> \(file):\(line)\n") и теперь вылетает как x = (Enum Value)\n --> main.swift:28\n fatal error: undefined: file main.swift, line 28   -  person Johannes Weiss    schedule 18.01.2015
comment
точно так же, за исключением того, что строка 27 теперь является строкой 28, когда я добавил печать ...   -  person Johannes Weiss    schedule 18.01.2015
comment
Я не правильно сказал, что печать не вылетает, а ошибка fatalError. Так что, должно быть, что-то в fatalError вызвало заминку. Извините за плохую формулировку.   -  person qwerty_so    schedule 18.01.2015
comment
Похоже, что на [L3] компилятор определяет тип T из undefined() как () -> Result<String>, а не как Result<String>, а затем вообще не помещает его в закрытие. - Кстати, с chain(failure, { undefined() }() ) работает как положено   -  person Martin R    schedule 18.01.2015
comment
@MartinR, но даже если он выводит его как () -> Result<String> (что является закрытием), он все равно не должен его запускать. И да, я тоже пробовал `chain (failure, {undefined ()} ()), и она работает ...   -  person Johannes Weiss    schedule 18.01.2015
comment
Я имел в виду, что возвращаемое значение undefined() (которое является значением заполнителя типа <T>) выводится как () -> Result<String>. Так что по какой-то причине undefined() не завернут в автозакрытие. Компилятор только генерирует вызов для undefined() и передает возвращаемое значение (которое является () -> Result<String> функции chain(). Если вы установите точку останова в undefined(), то вы увидите, что T это что-то вроде (Builtin.RawPointer) () -> Module.Result<String>.   -  person Martin R    schedule 18.01.2015
comment
ах, спасибо @MartinR, теперь я понимаю, в этом есть смысл! Тем не менее, это ошибка ...   -  person Johannes Weiss    schedule 18.01.2015
comment
Да, я так думаю. Я попытался воспроизвести проблему в более простом случае, но безуспешно. Передача undefined () функции с параметром автоматического закрытия обычно не дает такого эффекта.   -  person Martin R    schedule 18.01.2015
comment
Удивлен, что undefined вообще компилируется без атрибута noreturn: developer.apple.com/library/prerelease/ios/documentation/Swift/.   -  person Joseph Lord    schedule 18.01.2015
comment
@JosephLord, noreturn находится в fatalError, вот почему. Но нормальное определение undefined в любом случае undefined = undefined, поэтому исходная реализация (и единственная допустимая без noreturn) была func undefined<T>(file:StaticString=__FILE__, line:UWord=__LINE__) -> T { fatalError("undefined", file:file, line:line); return undefined() }. Но тогда, поскольку у нас есть noreturn, return не нужен.   -  person Johannes Weiss    schedule 18.01.2015
comment
подан радар сейчас: rdar: // 19510188 || openradar.appspot.com/radar?id=4811986399395840   -  person Johannes Weiss    schedule 18.01.2015
comment
FWIW, Я также полностью удалил fatalError и сделал func undefined<T>(file:StaticString=__FILE__, line:UWord=__LINE__) -> T { return undefined() }. Поведение такое же, за исключением того, что происходит сбой при переполнении стека, а не при утверждении. Но все же: он оценивает выражение, когда его не должно быть.   -  person Johannes Weiss    schedule 18.01.2015


Ответы (1)


Подтверждено как ошибка разработчиком Swift Джо Гроффом, через Twitter.

person Johannes Weiss    schedule 19.01.2015