Skip to content

Implement is not and not in operators - #454

Open
cirras wants to merge 3 commits into
masterfrom
delphi-13-operators
Open

Implement is not and not in operators#454
cirras wants to merge 3 commits into
masterfrom
delphi-13-operators

Conversation

@cirras

@cirras cirras commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

This PR implements the new binary operators added in Delphi 13.

In terms of parsing, operator precedence, and type resolution, this wasn't a difficult implementation. There were other challenges.

"Compound" operators with multiple tokens make previous AST modeling of binary and unary expression nodes a little tricky. The operators in these expressions are now modeled as their own typed child nodes.

This required me to build binary expressions a little differently, which made me drop the resetBinaryExpressionTokens fix introduced in 041a0d9. In doing so, I had to wrestle with node text range calculation for (probably) the 27th time. The new shape seems to work well.

Closes #408, #409.

@cirras
cirras requested a review from fourls August 10, 2026 06:19
@cirras cirras linked an issue Aug 10, 2026 that may be closed by this pull request
2 tasks
result.setText(null);
} else {
// The tokens straddle an include file boundary, so there is no single source span to read.
StringBuilder text = new StringBuilder();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we know the exact size of the string, let's set the StringBuilder capacity here:

Suggested change
StringBuilder text = new StringBuilder();
StringBuilder text = new StringBuilder(count);

@fourls fourls Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these changes to support NOT IN in the ExpressionParser look good, but IS and IS NOT also need to be supported - they are valid in compiler directives, though seem to blindly return true. The following compiles and runs, and prints both lines.

program foo;

var
  Obj: TObject;
begin
  {$if Obj is TObject}
  WriteLn('is');
  {$endif}

  {$if Obj is not TObject}
  WriteLn('is not');
  {$endif}

  ReadLn;
end.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't expect this at all since it makes no real sense at compile-time, which is why is isn't supported to begin with.
I'll take a closer look.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's an odd one...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a very neat little nightmare that forced me to split unevaluable expressions into 2 categories based on a mix of compiler behavior probing and respecting the SonarDelphi canon (fanfiction preprocessor implementation).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and I also randomly found out that we weren't handling string ordering comparisons, so that's been fixed in a separate commit as well.

Comment thread CHANGELOG.md
- **API:** `DelphiTokenType.IS_NOT` token type.
- **API:** `DelphiTokenType.NOT_IN` token type.
- **API:** `BinaryOperator.IS_NOT` enum value.
- **API:** `BinaryOperator.NOT_IN` enum value.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changelog should also include the addition of BinaryExpressionNode::getOperatorNode().

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

@Override
public DelphiCheckContext visit(BinaryExpressionNode node, DelphiCheckContext context) {
if (node.getOperator() == BinaryOperator.IN) {
if (node.getOperator() == BinaryOperator.IN || node.getOperator() == BinaryOperator.NOT_IN) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this is a necessary change... This rule is making up for if not A in B then looking correct to people without a strong knowledge of Delphi precedence. I don't think if not A not in B then really has the same problem - to me that actually does read correctly as a bitwise not followed by a logical not. It's sort of a different class of mistake and including it in this rule muddies the waters a little bit.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reasonable take, I agree and will back this one out.

cirras added 3 commits August 13, 2026 14:16
Delphi treats a non-constant `{$IF}` condition expression as true, but
we were treating every unevaluable expression as false.

Unevaluable expressions now come in 2 flavors:
- `UNKNOWN` for badly-formed expressions or unresolvable references.
  Evaluates to false.
- `NON_CONSTANT` for well-formed non-constant expressions.
  Evaluates to true.
String ordering comparisons like `'foo' > 'bar'` previously evaluated to
an UNKNOWN expression in preprocessor directive expressions.
In terms of parsing, operator precedence, and type resolution, this
wasn't a difficult implementation. There were other challenges.

"Compound" operators with multiple tokens make previous AST modeling of
binary and unary expression nodes a little tricky. The operators in
these expressions are now modeled as their own typed child nodes.

This required me to build binary expressions a little differently, which
made me drop the `resetBinaryExpressionTokens` fix introduced in
041a0d9. In doing so, I had to wrestle with node text range calculation
for (probably) the 27th time. The new shape seems to work well.
@cirras
cirras force-pushed the delphi-13-operators branch from 4eecf1d to d933342 Compare August 13, 2026 04:26
@cirras
cirras requested a review from fourls August 13, 2026 04:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support not in operator Support is not operator

2 participants