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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Support for `if` conditional expressions, introduced in Delphi 13.

## [1.19.0] - 2026-06-09

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,7 @@ attribute : (ASSEMBLY ':')? expression (':' expression)*
//----------------------------------------------------------------------------
expression : relationalExpression
| anonymousMethod
| ifExpression
;
// ANTLR sets the begin and end tokens for nested binary expression nodes
// in relationalOperator, not relationalExpression, meaning that their
Expand Down Expand Up @@ -984,6 +985,11 @@ anonymousMethodHeading : PROCEDURE routineParameters? ((';')? interfaceDir
| FUNCTION routineParameters? routineReturnType ((';')? interfaceDirective)*
-> ^(TkAnonymousMethodHeading<AnonymousMethodHeadingNodeImpl> FUNCTION routineParameters? routineReturnType ((';')? interfaceDirective)*)
;
// Conditional expression ("inline if"), introduced in Delphi 13.
// Unlike ifStatement, both branches are required since the construct must always yield a value.
// See: https://docwiki.embarcadero.com/RADStudio/Florence/en/Conditional_Operators_(Delphi)
ifExpression : IF<IfExpressionNodeImpl>^ expression THEN expression ELSE expression
;
expressionOrRange : expression ('..'<RangeExpressionNodeImpl>^ expression)?
;
expressionList : (expression (','!)?)+
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Sonar Delphi Plugin
* Copyright (C) 2019 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);
}

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

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

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

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

@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 @@ -636,6 +637,10 @@ default T visit(BinaryExpressionNode node, T data) {
return visit((ExpressionNode) node, data);
}

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

default T visit(ParenthesizedExpressionNode 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 @@ -123,6 +124,30 @@ public ControlFlowGraphBuilder visit(RangeExpressionNode node, ControlFlowGraphB
return build(node.getLowExpression(), builder);
}

/*
* Modeled the same way as an `if` statement: only one branch is actually evaluated at runtime,
* so the guard's two outcomes are mutually-exclusive paths rather than a single linear block.
*
* ┌─> true ──> `ThenBlock` ─┐
* `Guard` ─────┤ ├─> after
* └─> false ─> `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);
}

@Override
public ControlFlowGraphBuilder visit(ArrayConstructorNode node, ControlFlowGraphBuilder builder) {
return build(node.getElements(), builder);
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 All @@ -57,6 +58,7 @@
import org.sonar.plugins.communitydelphi.api.type.Type;
import org.sonar.plugins.communitydelphi.api.type.Type.ClassReferenceType;
import org.sonar.plugins.communitydelphi.api.type.Type.CollectionType;
import org.sonar.plugins.communitydelphi.api.type.Type.IntegerType;
import org.sonar.plugins.communitydelphi.api.type.Type.PointerType;
import org.sonar.plugins.communitydelphi.api.type.Type.ProceduralType;
import org.sonar.plugins.communitydelphi.api.type.TypeFactory;
Expand Down Expand Up @@ -93,6 +95,141 @@ public Type resolve(UnaryExpressionNode expression) {
}
}

public Type resolve(IfExpressionNode expression) {
return commonType(
expression.getThenExpression().getType(), expression.getElseExpression().getType());
}

/**
* Resolves the type of an {@code if} expression as the "least upper bound" of its {@code then}
* and {@code else} branch types, mirroring the type combinations accepted by the Delphi compiler.
* This is not the same computation used for operator/parameter overload resolution: the
* compiler's promotion buckets can produce a result type that is neither branch's type (e.g.
* {@code Byte} combined with {@code Word} produces {@code Integer}), so a pairwise "which side
* converts more cleanly to the other" comparison (as used for overload resolution) is not
* sufficient here.
*
* @see <a href="https://docwiki.embarcadero.com/RADStudio/en/Conditional_Operators_(Delphi)">
* Conditional Operators (Delphi)</a>
*/
private Type commonType(Type thenType, Type elseType) {
if (thenType.isUnknown()) {
return elseType;
}
if (elseType.isUnknown()) {
return thenType;
}
if (thenType.is(elseType)) {
return thenType;
}
if (thenType.isInteger() && elseType.isInteger()) {
return integerCommonType((IntegerType) thenType, (IntegerType) elseType);
}
if (isNumeric(thenType) && isNumeric(elseType)) {
return realCommonType(thenType, elseType);
}
if (isTextual(thenType) && isTextual(elseType)) {
return textualCommonType(thenType, elseType);
}
if (thenType.isStruct() && elseType.isStruct()) {
return structCommonType(thenType, elseType);
}
return unknownType();
}

/**
* Two integers widen to at least {@code Integer}; mixing a 32-bit unsigned ({@code Cardinal})
* with any signed integer, or involving any 64-bit integer, widens to {@code Int64}.
*/
private Type integerCommonType(IntegerType thenType, IntegerType elseType) {
boolean any64Bit = thenType.size() >= 8 || elseType.size() >= 8;
boolean unsigned32Bit =
(!thenType.isSigned() && thenType.size() == 4)
|| (!elseType.isSigned() && elseType.size() == 4);
boolean anySigned = thenType.isSigned() || elseType.isSigned();

if (any64Bit || (unsigned32Bit && anySigned)) {
return typeFactory.getIntrinsic(IntrinsicType.INT64);
}
return typeFactory.getIntrinsic(IntrinsicType.INTEGER);
}

/**
* A real combined with another real or an integer collapses to {@code Double}, unless both sides
* are integer-valued ({@code Comp}, {@code Currency}, or an integer), in which case it is {@code
* Comp}.
*/
private Type realCommonType(Type thenType, Type elseType) {
if (isIntegerValued(thenType) && isIntegerValued(elseType)) {
return typeFactory.getIntrinsic(IntrinsicType.COMP);
}
return typeFactory.getIntrinsic(IntrinsicType.DOUBLE);
}

private boolean isIntegerValued(Type type) {
return type.isInteger()
|| type.is(typeFactory.getIntrinsic(IntrinsicType.COMP))
|| type.is(typeFactory.getIntrinsic(IntrinsicType.CURRENCY));
}

/**
* Characters and strings that involve a {@code UnicodeString} promote to {@code UnicodeString};
* other combinations are left unknown.
*/
private Type textualCommonType(Type thenType, Type elseType) {
Type unicodeString = typeFactory.getIntrinsic(IntrinsicType.UNICODESTRING);
if (thenType.is(unicodeString) || elseType.is(unicodeString)) {
return unicodeString;
}
return unknownType();
}

/**
* When one struct is assignable to the other (e.g. a descendant and its ancestor), the less
* derived type wins; otherwise the nearest shared ancestor is used.
*/
private static Type structCommonType(Type thenType, Type elseType) {
boolean thenToElse =
TypeComparer.compare(thenType, elseType) != EqualityType.INCOMPATIBLE_TYPES;
boolean elseToThen =
TypeComparer.compare(elseType, thenType) != EqualityType.INCOMPATIBLE_TYPES;

if (thenToElse && !elseToThen) {
return elseType;
}
if (elseToThen && !thenToElse) {
return thenType;
}
return findCommonAncestor(thenType, elseType);
}

private static boolean isNumeric(Type type) {
return type.isInteger() || type.isReal();
}

private static boolean isTextual(Type type) {
return type.isChar() || type.isString();
}

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
Loading