Ответ 1
Честно говоря, я не совсем уверен, должен ли я опубликовать это как ответ или добавить эту информацию в вопрос - уже довольно многословный, но я, наконец, нашел, почему он ведет себя таким образом. (Но я все еще думаю, что это явно не описано в стандарте и что это фактически ограничение текущей реализации компилятора.)
Кроме того, я не собираюсь принимать свой собственный ответ некоторое время, надеясь, что кто-то сможет дать лучшую альтернативу ответа.
Я провел немного времени с Roslyn, и я отлаживал лексирование и разбор различных операторов из этого кода:
var test1 = a is byte & b;
var test2 = a is byte? & b;
var test3 = a is byte? && b;
Точные деревья синтаксиса уже добавлены в вопрос, поэтому я не буду повторять их здесь.
Разница между утверждениями возникает из этой части процесса компиляции (из LanguageParser.cs):
private TypeSyntax ParseTypeCore(
bool parentIsParameter,
bool isOrAs,
bool expectSizes,
bool isArrayCreation)
{
var type = this.ParseUnderlyingType(parentIsParameter);
if (this.CurrentToken.Kind == SyntaxKind.QuestionToken)
{
var resetPoint = this.GetResetPoint();
try
{
var question = this.EatToken();
// Comment added by me
// This is where the difference occurs
// (as for '&' the IsAnyUnaryExpression() returns true)
if (isOrAs && (IsTerm() || IsPredefinedType(this.CurrentToken.Kind) || SyntaxFacts.IsAnyUnaryExpression(this.CurrentToken.Kind)))
{
this.Reset(ref resetPoint);
Debug.Assert(type != null);
return type;
}
question = CheckFeatureAvailability(question, MessageID.IDS_FeatureNullable);
type = syntaxFactory.NullableType(type, question);
}
finally
{
this.Release(ref resetPoint);
}
}
// Check for pointer types (only if pType is NOT an array type)
type = this.ParsePointerTypeMods(type);
// Now check for arrays.
if (this.IsPossibleRankAndDimensionSpecifier())
{
var ranks = this.pool.Allocate<ArrayRankSpecifierSyntax>();
try
{
while (this.IsPossibleRankAndDimensionSpecifier())
{
bool unused;
var rank = this.ParseArrayRankSpecifier(isArrayCreation, expectSizes, out unused);
ranks.Add(rank);
expectSizes = false;
}
type = syntaxFactory.ArrayType(type, ranks);
}
finally
{
this.pool.Free(ranks);
}
}
Debug.Assert(type != null);
return type;
}
И тот же результат будет иметь место в случае символов после части byte?
, для которой эта функция возвращает что-либо, кроме SyntaxKind.None
:
public static SyntaxKind GetPrefixUnaryExpression(SyntaxKind token)
{
switch (token)
{
case SyntaxKind.PlusToken:
return SyntaxKind.UnaryPlusExpression;
case SyntaxKind.MinusToken:
return SyntaxKind.UnaryMinusExpression;
case SyntaxKind.TildeToken:
return SyntaxKind.BitwiseNotExpression;
case SyntaxKind.ExclamationToken:
return SyntaxKind.LogicalNotExpression;
case SyntaxKind.PlusPlusToken:
return SyntaxKind.PreIncrementExpression;
case SyntaxKind.MinusMinusToken:
return SyntaxKind.PreDecrementExpression;
case SyntaxKind.AmpersandToken:
return SyntaxKind.AddressOfExpression;
case SyntaxKind.AsteriskToken:
return SyntaxKind.PointerIndirectionExpression;
default:
return SyntaxKind.None;
}
}
Таким образом, проблема заключается в том, что после оператора is
(или as
), когда мы сталкиваемся с токеном ?
, тогда мы проверяем, можно ли интерпретировать следующий токен как унарный оператор, и если да: мы не заботятся о возможности того, что токен ?
является модификатором типа, мы просто возвращаем тип перед ним и будем разбирать остальные соответственно (есть еще несколько условий, которые необходимо выполнить, но это соответствующая информация о моем вопросе). Ирония заключается в том, что символ &
не может быть даже унарным оператором, только в небезопасном контексте, но это никогда не принимается во внимание.
Как отмечали другие комментаторы, возможно, эта проблема может быть решена, если мы посмотрим еще немного, например: в этом конкретном случае мы могли бы проверить, есть ли подходящий :
для токена ?
а если нет, то игнорировать возможность унарного оператора &
и рассматривать ?
как модификатор типа. Если у меня будет время, я попытаюсь реализовать обходное решение и посмотреть, где это вызовет еще большие проблемы:) (К счастью, в решении Roslyn есть много тестов...)
Спасибо всем за отзыв.