Как вы уже заметили, 0 | 0 | E.X
связывается как (0 | 0) | E.X
.
Эрик отметил, что компилятор не следует спецификации для 0 | 0 | E.X
:
После того, как у нас есть полное дерево разбора, мы проходим через дерево разбора, чтобы убедиться, что все типы работают. К сожалению, начальная передача типа довольно странно выполняет арифметическую оптимизацию. Он обнаруживает 0 | что-то и агрессивно заменяет его просто чем-то, так что для компилятора шестой случай такой же, как и второй, что допустимо. Argh!
Эрик отмечает в комментариях:
но (7-7) | E.X правильно выдает ошибку
Кажется, что Roslyn немного умнее насчет констант сворачивания, чем нативный компилятор. Вероятно, они стремились к эффективности здесь, не заботясь о сохранении поведения «ошибка за ошибкой» в крайнем случае.
Похоже, теперь та же самая проблема относится к 7 - 7
или любому другому выражению, которое компилятор может вычислить до 0
во время этого начального прохода привязки типа по той же причине.
Я думаю, что происходит постоянное свертывание здесь :
newValue = FoldNeverOverflowBinaryOperators(kind, valueLeft, valueRight);
if (newValue != null)
{
return ConstantValue.Create(newValue, resultType);
}
Как видите, создается новый ConstantValue
. Так что (0 | 0) | E.X
складывается в 0 | E.X
, где это первое 0
является константой Когда компилятор сворачивает 0 | E.X
, он не осознает, что 0
не является литералом 0
в исходном источнике, а является константой, сгенерированной компилятором, и поэтому сворачивает ее, как если бы вы написали 0 | E.X
первоначально.
Точно так же происходит и с другими вашими примерами, и я думаю, что это делается с помощью того же куска кода. 1 - 1
складывается в константу 0
, как и остальные. Это произойдет с любым выражением, которое компилятор может вычислить до 0
во время компиляции.