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 authored Nov 6, 2024
1 parent 93dc62d commit 717d0fc
Show file tree
Hide file tree
Showing 19 changed files with 545 additions and 23 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_INTERSECTION_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_INTERSECTION_TYPE,

/**
* Lexical
Expand Down
45 changes: 37 additions & 8 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,26 +1876,51 @@ 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 firstType, List<Tuple<SyntaxToken, T>> rest) {
List<T> types = new ArrayList<>();
List<SyntaxToken> separators = new ArrayList<>();

types.add(type1);
types.add(firstType);

for (Tuple<SyntaxToken, TypeTree> tuple : rest) {
for (Tuple<SyntaxToken, T> tuple : rest) {
separators.add(tuple.first);
types.add(tuple.second);
}

return new SeparatedListImpl<>(types, separators);
}

public UnionTypeTree unionType(TypeTree type1, List<Tuple<SyntaxToken, TypeTree>> rest) {
return new CombinedTypeTreeImpl.UnionTypeTreeImpl(combinedTypes(type1, rest));
public UnionTypeTree unionType(TypeTree firstType, List<Tuple<SyntaxToken, TypeTree>> rest) {
return new CombinedTypeTreeImpl.UnionTypeTreeImpl(combinedTypes(firstType, rest));
}

public IntersectionTypeTree intersectionType(TypeTree type1, List<Tuple<SyntaxToken, TypeTree>> rest) {
return new CombinedTypeTreeImpl.IntersectionTypeTreeImpl(combinedTypes(type1, rest));
public IntersectionTypeTree intersectionType(TypeTree firstType, List<Tuple<SyntaxToken, TypeTree>> rest) {
return new CombinedTypeTreeImpl.IntersectionTypeTreeImpl(combinedTypes(firstType, 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 firstType, List<Tuple<SyntaxToken, TypeTree>> rest, SyntaxToken closedParenthesis) {
return new DnfIntersectionTypeTreeImpl(openParenthesis, combinedTypes(firstType, rest), closedParenthesis);
}

public CallArgumentTree functionCallArgument(Optional<Tuple<NameIdentifierTree, InternalSyntaxToken>> optional, ExpressionTree 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 717d0fc

Please sign in to comment.