"?" тип modifer перед логическим и оператором (&) и адресом оператора (&)

Update: Кажется, что я не совсем понимаю, что именно я спрашиваю (и, поскольку вопрос был разработан с течением времени, я также немного потерял трек), поэтому вот версия tl; dr:

var test1 = a is byte & b;    // compiles
var test2 = a is byte? & b;   // does not compile
var test3 = a is byte? && b;  // compiles

Это означает, что, как я понимаю, модификатор типа ? имеет более низкий приоритет (поскольку это не оператор, это может быть не лучшее слово), чем оператор &, но выше, чем оператор &&. Это так? Где это описано в стандарте?


И исходный вопрос:

Пытаясь выяснить ответ на вторую головоломку от отличного blogpost от Jon Skeet, "Сказка о двух головоломках" , я столкнулся с проблемой

unsafe private void Test<T>(T a, bool b) 
{
    var test1 = a is byte? & b;         // does not compile
    var test2 = a is byte? && b;        // compiles
    var test3 = a is byte ? & b : & b;  // compiles
}

Здесь я использую контекст unsafe, так как моя фактическая цель требует его (например, третья строка), но нет необходимости воспроизводить проблему, которую я поднимаю. (Однако это может иметь эффект, поскольку он вводит адрес-оператора в качестве альтернативы для символа &.)

Первая строка не компилируется (другие делают), она дает следующее сообщение об ошибке:

Syntax error, ':' expected

Это означает, что в этом случае компилятор видит строку как

var test1 = (a is byte) ? &b [: missing part that it complains about];

Пока во второй строке это выглядит как:

var test2 = (a is byte?) && (b);

Я проверил приоритет оператора (здесь), а порядок (от самого высокого до самого низкого) выглядит следующим образом: &, &&, ?:, поэтому это один не объясняет, почему первая строка не компилируется, пока вторая делает (или, по крайней мере, не для меня - может быть, это то, где я ошибаюсь...) Изменить: Я понимаю, почему второй компилирует, поэтому, пожалуйста, не концентрируйтесь на этом в своих ответах.

Моя следующая догадка заключалась в том, что каким-то образом приоритет (если есть такая вещь) для модификатора типа ? находится где-то между этими двумя (или фактически тремя) операторами (& и &&). Неужели так? Если бы кто-то не смог объяснить точное поведение, которое я испытываю? Является ли порядок оценки модификатора типа ? четко описанным где-то в стандарте?

Изменить: Я также знаю, что есть унарный адрес оператора (на самом деле это трюк, который я пытаюсь использовать для решения...), который играет здесь роль, но мой вопрос по-прежнему остается прежним.

В том же unsafe контексте они с радостью компилируются:

var test1 = true & b;
var test2 = true && b;
// or
var test1 = a is byte & b;
var test2 = a is byte && b;

Поэтому я думаю, что он должен быть связан с ? модификатором/оператором, а не только с приоритетом адреса оператора (иначе две строки test1 не будут компилироваться).

P.S.: Я знаю, что я могу добавить круглые скобки в свой код, поэтому он будет компилироваться, но я хочу этого избежать:

var test = (a is byte?) & b;   // compiles

Update: Я немного экспериментировал с Рослином, и я подумал, что было бы неплохо приложить АСТ к различным утверждениям:

var test1 = a is byte & b;

AST for test1 line

var test2 = a is byte? & b;

AST for test2 line

var test3 = a is byte? && b;

AST for test3 line


Я хочу подчеркнуть, что я не ищет решение исходной проблемы в связанной статье (конечно, я ищу ее, но я прошу вас не давать ответ здесь, пожалуйста, поскольку я хотел бы это узнать самостоятельно.) Кроме того, пожалуйста, не комментируйте, если я нахожусь на совершенно неправильном пути в поиске решения, я только упомянул загадку, чтобы дать некоторый контекст для моей конкретной проблемы, и предоставить достаточно хорошее оправдание для написания кода, подобного этому.

Ответ 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 есть много тестов...)

Спасибо всем за отзыв.

Ответ 2

Ключ здесь - ключевое слово unsafe. На самом деле существуют два разных оператора: побитовое-И и оператор, к которому вы привыкли, а также адрес-оператор и оператор, который имеет не только более высокий приоритет, но и оценивается справа налево, как и все унарные операторы.

Это означает, что сначала выполняется оценка &b и приводит к значению указателя. Остальная часть утверждения, как компилятор, жалуется, не имеет аналогов. Это либо (a is byte?) (address), либо (поскольку компилятор пытается разобрать его) (a is byte) ? (address) и отсутствует :.

Я получаю ту же ошибку компиляции при замене и с помощью + или -, оба символа, которые могут быть либо унарными, либо двоичными операторами.

Причина, по которой второй оператор компилируется, заключается в том, что нет унарного, правостороннего права на приоритет && & Оператор.