Skip to content

Commit

Permalink
SONARPHP-1533 Support Disjunctive Normal Form Types (PHP 8.2 feature)
Browse files Browse the repository at this point in the history
  • Loading branch information
rudy-regazzoni-sonarsource committed Nov 5, 2024
1 parent b6e30f6 commit dc90dec
Show file tree
Hide file tree
Showing 19 changed files with 538 additions and 18 deletions.
26 changes: 25 additions & 1 deletion php-frontend/src/main/java/org/sonar/php/parser/PHPGrammar.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import org.sonar.plugins.php.api.tree.declaration.ClassPropertyDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.ConstantDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.DeclaredTypeTree;
import org.sonar.plugins.php.api.tree.declaration.DnfIntersectionTypeTree;
import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree;
import org.sonar.plugins.php.api.tree.declaration.EnumDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.FunctionDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.IntersectionTypeTree;
Expand Down Expand Up @@ -607,9 +609,31 @@ public IntersectionTypeTree INTERSECTION_TYPE() {
b.oneOrMore(f.newTuple(b.token(PHPPunctuator.AMPERSAND), TYPE()))));
}

/**
* We don't want to match UNION_TYPE as DNF_TYPE, so we need this more complex grammar to ensure we have a mix of union and intersection:
* DNF_TYPE -> (TYPE "|")* DNF_INTERSECTION_TYPE ("|" (TYPE | DNF_INTERSECTION_TYPE))*
*/
public DnfTypeTree DNF_TYPE() {
return b.<DnfTypeTree>nonterminal(PHPLexicalGrammar.DNF_TYPE).is(
b.firstOf(
f.dnfType(
b.zeroOrMore(f.newTuple(TYPE(), b.token(PHPPunctuator.OR))),
DNF_INTERSECTION_TYPE(),
b.zeroOrMore(f.newTuple(b.token(PHPPunctuator.OR), b.firstOf(TYPE(), DNF_INTERSECTION_TYPE()))))));
}

public DnfIntersectionTypeTree DNF_INTERSECTION_TYPE() {
return b.<DnfIntersectionTypeTree>nonterminal(PHPLexicalGrammar.DNF_INTESECTION_TYPE).is(
f.dnfIntersectionType(
b.token(PHPPunctuator.LPARENTHESIS),
TYPE(),
b.oneOrMore(f.newTuple(b.token(PHPPunctuator.AMPERSAND), TYPE())),
b.token(PHPPunctuator.RPARENTHESIS)));
}

public DeclaredTypeTree DECLARED_TYPE() {
return b.<DeclaredTypeTree>nonterminal(PHPLexicalGrammar.DECLARED_TYPE).is(
b.firstOf(UNION_TYPE(), INTERSECTION_TYPE(), TYPE()));
b.firstOf(DNF_TYPE(), UNION_TYPE(), INTERSECTION_TYPE(), TYPE()));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public enum PHPLexicalGrammar implements GrammarRuleKey {
UNION_TYPE,
INTERSECTION_TYPE,
DECLARED_TYPE,
DNF_TYPE,
DNF_INTESECTION_TYPE,

/**
* Lexical
Expand Down
33 changes: 30 additions & 3 deletions php-frontend/src/main/java/org/sonar/php/parser/TreeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
import org.sonar.php.tree.impl.declaration.ClassPropertyDeclarationTreeImpl;
import org.sonar.php.tree.impl.declaration.CombinedTypeTreeImpl;
import org.sonar.php.tree.impl.declaration.ConstantDeclarationTreeImpl;
import org.sonar.php.tree.impl.declaration.DnfIntersectionTypeTreeImpl;
import org.sonar.php.tree.impl.declaration.DnfTypeTreeImpl;
import org.sonar.php.tree.impl.declaration.EnumDeclarationTreeImpl;
import org.sonar.php.tree.impl.declaration.FunctionDeclarationTreeImpl;
import org.sonar.php.tree.impl.declaration.MethodDeclarationTreeImpl;
Expand Down Expand Up @@ -151,6 +153,8 @@
import org.sonar.plugins.php.api.tree.declaration.ClassPropertyDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.ConstantDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.DeclaredTypeTree;
import org.sonar.plugins.php.api.tree.declaration.DnfIntersectionTypeTree;
import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree;
import org.sonar.plugins.php.api.tree.declaration.EnumDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.FunctionDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.IntersectionTypeTree;
Expand Down Expand Up @@ -1872,13 +1876,13 @@ public ExecutionOperatorTree executionOperator(ExpandableStringLiteralTree liter
return new ExecutionOperatorTreeImpl(literal);
}

private static SeparatedList<TypeTree> combinedTypes(TypeTree type1, List<Tuple<SyntaxToken, TypeTree>> rest) {
List<TypeTree> types = new ArrayList<>();
private static <T extends DeclaredTypeTree> SeparatedList<T> combinedTypes(T type1, List<Tuple<SyntaxToken, T>> rest) {
List<T> types = new ArrayList<>();
List<SyntaxToken> separators = new ArrayList<>();

types.add(type1);

for (Tuple<SyntaxToken, TypeTree> tuple : rest) {
for (Tuple<SyntaxToken, T> tuple : rest) {
separators.add(tuple.first);
types.add(tuple.second);
}
Expand All @@ -1894,6 +1898,29 @@ public IntersectionTypeTree intersectionType(TypeTree type1, List<Tuple<SyntaxTo
return new CombinedTypeTreeImpl.IntersectionTypeTreeImpl(combinedTypes(type1, rest));
}

public DnfTypeTree dnfType(Optional<List<Tuple<TypeTree, SyntaxToken>>> simpleTypes, DnfIntersectionTypeTree intersectionTypeTree,
Optional<List<Tuple<SyntaxToken, DeclaredTypeTree>>> rest) {
List<DeclaredTypeTree> types = new ArrayList<>();
List<SyntaxToken> separators = new ArrayList<>();

for (Tuple<TypeTree, SyntaxToken> tuple : simpleTypes.or(Collections.emptyList())) {
types.add(tuple.first);
separators.add(tuple.second);
}
types.add(intersectionTypeTree);
for (Tuple<SyntaxToken, DeclaredTypeTree> tuple : rest.or(Collections.emptyList())) {
separators.add(tuple.first);
types.add(tuple.second);
}

var separatedList = new SeparatedListImpl<>(types, separators);
return new DnfTypeTreeImpl(separatedList);
}

public DnfIntersectionTypeTree dnfIntersectionType(SyntaxToken openParenthesis, TypeTree type1, List<Tuple<SyntaxToken, TypeTree>> rest, SyntaxToken closedParenthesis) {
return new DnfIntersectionTypeTreeImpl(openParenthesis, combinedTypes(type1, rest), closedParenthesis);
}

public CallArgumentTree functionCallArgument(Optional<Tuple<NameIdentifierTree, InternalSyntaxToken>> optional, ExpressionTree firstOf) {
return new CallArgumentTreeImpl(optional.orNull(), firstOf);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* SonarQube PHP Plugin
* Copyright (C) 2010-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* 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 02110-1301, USA.
*/
package org.sonar.php.tree.impl.declaration;

import java.util.Iterator;
import org.sonar.php.tree.impl.PHPTree;
import org.sonar.php.utils.collections.IteratorUtils;
import org.sonar.plugins.php.api.tree.SeparatedList;
import org.sonar.plugins.php.api.tree.Tree;
import org.sonar.plugins.php.api.tree.declaration.DnfIntersectionTypeTree;
import org.sonar.plugins.php.api.tree.declaration.TypeTree;
import org.sonar.plugins.php.api.tree.lexical.SyntaxToken;
import org.sonar.plugins.php.api.visitors.VisitorCheck;

public class DnfIntersectionTypeTreeImpl extends PHPTree implements DnfIntersectionTypeTree {
private final SyntaxToken openParenthesisToken;
private final SeparatedList<TypeTree> types;
private final SyntaxToken closedParenthesisToken;

public DnfIntersectionTypeTreeImpl(SyntaxToken openParenthesisToken, SeparatedList<TypeTree> types, SyntaxToken closedParenthesisToken) {
this.openParenthesisToken = openParenthesisToken;
this.types = types;
this.closedParenthesisToken = closedParenthesisToken;
}

@Override
public Iterator<Tree> childrenIterator() {
return IteratorUtils.concat(
IteratorUtils.iteratorOf(openParenthesisToken),
types.elementsAndSeparators(),
IteratorUtils.iteratorOf(closedParenthesisToken));
}

@Override
public SyntaxToken openParenthesisToken() {
return openParenthesisToken;
}

@Override
public SeparatedList<TypeTree> types() {
return types;
}

@Override
public SyntaxToken closedParenthesisToken() {
return closedParenthesisToken;
}

@Override
public boolean isSimple() {
return false;
}

@Override
public void accept(VisitorCheck visitor) {
visitor.visitDnfIntersectionType(this);
}

@Override
public Kind getKind() {
return Kind.DNF_INTERSECTION_TYPE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* SonarQube PHP Plugin
* Copyright (C) 2010-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* 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 02110-1301, USA.
*/
package org.sonar.php.tree.impl.declaration;

import java.util.Iterator;
import org.sonar.php.tree.impl.PHPTree;
import org.sonar.plugins.php.api.tree.SeparatedList;
import org.sonar.plugins.php.api.tree.Tree;
import org.sonar.plugins.php.api.tree.declaration.DeclaredTypeTree;
import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree;
import org.sonar.plugins.php.api.visitors.VisitorCheck;

public class DnfTypeTreeImpl extends PHPTree implements DnfTypeTree {

private final SeparatedList<DeclaredTypeTree> types;

public DnfTypeTreeImpl(SeparatedList<DeclaredTypeTree> types) {
this.types = types;
}

@Override
public Iterator<Tree> childrenIterator() {
return types.elementsAndSeparators();
}

@Override
public boolean isSimple() {
return false;
}

@Override
public void accept(VisitorCheck visitor) {
visitor.visitDnfType(this);
}

@Override
public Kind getKind() {
return Kind.DNF_TYPE;
}

@Override
public SeparatedList<DeclaredTypeTree> types() {
return types;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import org.sonar.plugins.php.api.tree.declaration.ClassDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.ClassPropertyDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.ConstantDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.DnfIntersectionTypeTree;
import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree;
import org.sonar.plugins.php.api.tree.declaration.EnumDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.FunctionDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.IntersectionTypeTree;
Expand Down Expand Up @@ -307,6 +309,16 @@ enum Kind implements GrammarRuleKey {
*/
INTERSECTION_TYPE(IntersectionTypeTree.class),

/**
* {@link DnfTypeTree}
*/
DNF_TYPE(DnfTypeTree.class),

/**
* {@link DnfIntersectionTypeTree}
*/
DNF_INTERSECTION_TYPE(DnfIntersectionTypeTree.class),

/**
* {@link NamespaceNameTree}
*/
Expand Down Expand Up @@ -1000,7 +1012,7 @@ enum Kind implements GrammarRuleKey {
STATIC_STATEMENT(StaticStatementTree.class),

/**
* {@link SyntaxToken}
* {@link SyntaxTrivia}
*/
TRIVIA(SyntaxTrivia.class),

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* SonarQube PHP Plugin
* Copyright (C) 2010-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* 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 02110-1301, USA.
*/
package org.sonar.plugins.php.api.tree.declaration;

import org.sonar.plugins.php.api.tree.SeparatedList;
import org.sonar.plugins.php.api.tree.lexical.SyntaxToken;

/**
* Represent intersection in Disjunctive Normal Form (DNF) of types.
* <pre>
* {@link DnfTypeTree}
* </pre>
*
* @since 3.39
*/
public interface DnfIntersectionTypeTree extends DeclaredTypeTree {

/**
* The open parenthesis token, e.g., <code>(</code> in <code>(int|string)</code>.
* @return the open parenthesis token
*/
SyntaxToken openParenthesisToken();

/**
* The list of elements and separators, e.g., <code>int</code>, <code>|</code> and <code>string</code> in <code>(int|string)</code>.
* @return the list of elements and separators
*/
SeparatedList<TypeTree> types();

/**
* The closed parenthesis token, e.g., <code>)</code> in <code>(int|string)</code>.
* @return the closed parenthesis token
*/
SyntaxToken closedParenthesisToken();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* SonarQube PHP Plugin
* Copyright (C) 2010-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* 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 02110-1301, USA.
*/
package org.sonar.plugins.php.api.tree.declaration;

import org.sonar.plugins.php.api.tree.SeparatedList;

/**
* <a href="https://wiki.php.net/rfc/dnf_types">DNF Types</a>
*
* @since 3.39
*/
public interface DnfTypeTree extends DeclaredTypeTree {

/**
* The list of elements and separators, e.g., <code>int</code>, <code>|</code> and <code>(A&amp;B)</code> in <code>int|(A&amp;B)</code>.
* @return the list of elements and separators
*/
SeparatedList<DeclaredTypeTree> types();
}
Loading

0 comments on commit dc90dec

Please sign in to comment.