Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for implicit `Self` in `Initialize` and `Finalize` operators, introduced in Delphi 13.
- Support for `DCCARM64EC` toolchain, introduced in Delphi 13.1.
- `NoreturnContract` analysis rule, which flags `noreturn` routines that return normally.
- Support for `if` expressions (e.g. `X := if Foo then Bar else Baz;`), introduced in Delphi 13.

## [1.18.3] - 2025-11-11

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ tokens {
TkArgument;
TkAnonymousMethod;
TkAnonymousMethodHeading;
TkIfExpression;
TkLessThanEqual;
TkGreaterThanEqual;
}
Expand Down Expand Up @@ -920,6 +921,10 @@ attribute : (ASSEMBLY ':')? expression (':' expression)*
//----------------------------------------------------------------------------
expression : relationalExpression
| anonymousMethod
| ifExpression
;
ifExpression : IF expression THEN expression ELSE expression
-> ^(TkIfExpression<IfExpressionNodeImpl> IF expression THEN expression ELSE expression)
;
// ANTLR sets the begin and end tokens for nested binary expression nodes
// in relationalOperator, not relationalExpression, meaning that their
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Sonar Delphi Plugin
* Copyright (C) 2026 Integrated Application Development
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package au.com.integradev.delphi.antlr.ast.node;

import au.com.integradev.delphi.antlr.ast.visitors.DelphiParserVisitor;
import au.com.integradev.delphi.symbol.resolve.ExpressionTypeResolver;
import javax.annotation.Nonnull;
import org.antlr.runtime.Token;
import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.IfExpressionNode;
import org.sonar.plugins.communitydelphi.api.type.Type;

public final class IfExpressionNodeImpl extends ExpressionNodeImpl implements IfExpressionNode {
private String image;

public IfExpressionNodeImpl(Token token) {
super(token);
}

public IfExpressionNodeImpl(int tokenType) {
super(tokenType);
}

@Override
public <T> T accept(DelphiParserVisitor<T> visitor, T data) {
return visitor.visit(this, data);
}

@Override
public ExpressionNode getGuardExpression() {
return (ExpressionNode) getChild(1);
}

@Override
public ExpressionNode getThenExpression() {
return (ExpressionNode) getChild(3);
}

@Override
public ExpressionNode getElseExpression() {
return (ExpressionNode) getChild(5);
}

@Override
public String getImage() {
if (image == null) {
image =
"if "
+ getGuardExpression().getImage()
+ " then "
+ getThenExpression().getImage()
+ " else "
+ getElseExpression().getImage();
}
return image;
}

@Override
@Nonnull
protected Type createType() {
return new ExpressionTypeResolver(getTypeFactory()).resolve(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.sonar.plugins.communitydelphi.api.ast.ExceptBlockNode;
import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.ForStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.IfExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.IfStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.RepeatStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.StatementNode;
Expand Down Expand Up @@ -90,6 +91,19 @@ public Data visit(IfStatementNode statement, Data data) {
return data;
}

@Override
public Data visit(IfExpressionNode expression, Data data) {
data.increaseComplexityByNesting();
expression.getGuardExpression().accept(this, data);

++data.nesting;
expression.getThenExpression().accept(this, data);
expression.getElseExpression().accept(this, data);
--data.nesting;

return data;
}

@Override
public Data visit(ExceptBlockNode exceptBlock, Data data) {
if (exceptBlock.hasHandlers()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.sonar.plugins.communitydelphi.api.ast.BinaryExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.CaseItemStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.ForStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.IfExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.IfStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.RepeatStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.RoutineImplementationNode;
Expand Down Expand Up @@ -80,6 +81,12 @@ public Data visit(IfStatementNode statement, Data data) {
return DelphiParserVisitor.super.visit(statement, data);
}

@Override
public Data visit(IfExpressionNode expression, Data data) {
++data.complexity;
return DelphiParserVisitor.super.visit(expression, data);
}

@Override
public Data visit(BinaryExpressionNode expression, Data data) {
BinaryOperator operator = expression.getOperator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import org.sonar.plugins.communitydelphi.api.ast.GotoStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.HelperTypeNode;
import org.sonar.plugins.communitydelphi.api.ast.IdentifierNode;
import org.sonar.plugins.communitydelphi.api.ast.IfExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.IfStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.ImplementationSectionNode;
import org.sonar.plugins.communitydelphi.api.ast.ImportClauseNode;
Expand Down Expand Up @@ -622,6 +623,10 @@ default T visit(AnonymousMethodNode node, T data) {
return visit((ExpressionNode) node, data);
}

default T visit(IfExpressionNode node, T data) {
return visit((ExpressionNode) node, data);
}

default T visit(ArrayConstructorNode node, T data) {
return visit((ExpressionNode) node, data);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.sonar.plugins.communitydelphi.api.ast.ForStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.ForToStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.GotoStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.IfExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.IfStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.IntegerLiteralNode;
import org.sonar.plugins.communitydelphi.api.ast.LabelStatementNode;
Expand Down Expand Up @@ -277,6 +278,22 @@ public ControlFlowGraphBuilder visit(IfStatementNode node, ControlFlowGraphBuild
return buildCondition(builder, node.getGuardExpression(), thenBlock, elseBlock);
}

@Override
public ControlFlowGraphBuilder visit(IfExpressionNode node, ControlFlowGraphBuilder builder) {
ProtoBlock after = builder.getCurrentBlock();

builder.addBlockBefore(after);
build(node.getElseExpression(), builder);
ProtoBlock elseBlock = builder.getCurrentBlock();

builder.addBlockBefore(after);
build(node.getThenExpression(), builder);
ProtoBlock thenBlock = builder.getCurrentBlock();

builder.addBlock(ProtoBlockFactory.branch(node, thenBlock, elseBlock));
return buildCondition(builder, node.getGuardExpression(), thenBlock, elseBlock);
}

private ControlFlowGraphBuilder buildCondition(
ControlFlowGraphBuilder builder,
ExpressionNode node,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.sonar.plugins.communitydelphi.api.ast.CommonDelphiNode;
import org.sonar.plugins.communitydelphi.api.ast.DelphiNode;
import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.IfExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.NameReferenceNode;
import org.sonar.plugins.communitydelphi.api.ast.Node;
import org.sonar.plugins.communitydelphi.api.ast.PrimaryExpressionNode;
Expand Down Expand Up @@ -93,6 +94,58 @@ public Type resolve(UnaryExpressionNode expression) {
}
}

public Type resolve(IfExpressionNode expression) {
Type thenType = expression.getThenExpression().getType();
Type elseType = expression.getElseExpression().getType();

if (thenType.isUnknown()) {
return elseType;
}
if (elseType.isUnknown()) {
return thenType;
}

if (thenType.is(elseType)) {
return thenType;
}

EqualityType thenAsCommon = TypeComparer.compare(elseType, thenType);
EqualityType elseAsCommon = TypeComparer.compare(thenType, elseType);

if (thenAsCommon == EqualityType.INCOMPATIBLE_TYPES
&& elseAsCommon == EqualityType.INCOMPATIBLE_TYPES) {
return findCommonAncestor(thenType, elseType);
}

if (thenAsCommon == EqualityType.INCOMPATIBLE_TYPES) {
return elseType;
}
if (elseAsCommon == EqualityType.INCOMPATIBLE_TYPES) {
return thenType;
}

return elseAsCommon.ordinal() >= thenAsCommon.ordinal() ? elseType : thenType;
}

private static Type findCommonAncestor(Type left, Type right) {
if (!left.isStruct() || !right.isStruct()) {
return unknownType();
}

for (Type leftAncestor = left.parent();
!leftAncestor.isUnknown();
leftAncestor = leftAncestor.parent()) {
for (Type rightAncestor = right.parent();
!rightAncestor.isUnknown();
rightAncestor = rightAncestor.parent()) {
if (leftAncestor.is(rightAncestor)) {
return leftAncestor;
}
}
}
return unknownType();
}

public Type resolve(PrimaryExpressionNode expression) {
Type type = unknownType();
boolean regularArrayProperty = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Sonar Delphi Plugin
* Copyright (C) 2026 Integrated Application Development
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.plugins.communitydelphi.api.ast;

public interface IfExpressionNode extends ExpressionNode {
ExpressionNode getGuardExpression();

ExpressionNode getThenExpression();

ExpressionNode getElseExpression();
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ void testParseAnonymousMethods() {
assertParsed("AnonymousMethods.pas");
}

@Test
void testParseConditionalExpressions() {
assertParsed("ConditionalExpressions.pas");
}

@Test
void testParseGenerics() {
assertParsed("Generics.pas");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import org.sonar.plugins.communitydelphi.api.ast.BinaryExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.CaseStatementNode;
import org.sonar.plugins.communitydelphi.api.ast.CommonDelphiNode;
import org.sonar.plugins.communitydelphi.api.ast.IfExpressionNode;
import org.sonar.plugins.communitydelphi.api.ast.DelphiNode;
import org.sonar.plugins.communitydelphi.api.ast.FinallyBlockNode;
import org.sonar.plugins.communitydelphi.api.ast.ForInStatementNode;
Expand Down Expand Up @@ -342,6 +343,20 @@ void testEmptyIfElse() {
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
}

@Test
void testIfExpression() {
test(
List.of("X: Integer"),
"X := if A then Foo else Bar;",
checker(
block(element(NameReferenceNode.class, "A"))
.branchesTo(3, 2)
.withTerminator(IfExpressionNode.class),
block(element(NameReferenceNode.class, "Foo")).succeedsTo(1),
block(element(NameReferenceNode.class, "Bar")).succeedsTo(1),
block(element(NameReferenceNode.class, "X")).succeedsTo(0)));
}

@Test
void testLocalVarDeclaration() {
test(
Expand Down
Loading
Loading