Skip to content

Commit

Permalink
Merge pull request apache#2319 from junichi11/php80-union-types-2.0
Browse files Browse the repository at this point in the history
[NETBEANS-4443] PHP 8.0 Support: Union Types 2.0
  • Loading branch information
tmysik authored and junichi11 committed Oct 15, 2020
2 parents 53db4f4 + 6857261 commit 6741d2a
Show file tree
Hide file tree
Showing 573 changed files with 27,257 additions and 16,423 deletions.
33 changes: 29 additions & 4 deletions php/php.editor/src/org/netbeans/modules/php/editor/CodeUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import org.netbeans.modules.php.editor.parser.astnodes.TypeDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.UnaryOperation;
import org.netbeans.modules.php.editor.parser.astnodes.UnaryOperation.Operator;
import org.netbeans.modules.php.editor.parser.astnodes.UnionType;
import org.netbeans.modules.php.editor.parser.astnodes.UseStatement;
import org.netbeans.modules.php.editor.parser.astnodes.Variable;
import org.netbeans.modules.php.editor.parser.astnodes.Variadic;
Expand Down Expand Up @@ -228,10 +229,12 @@ public static Identifier extractUnqualifiedIdentifier(Expression typeName) {
}

/**
* Extract unqualified name for Identifier, NamespaceName, and NullableType.
* Extract unqualified name for Identifier, NamespaceName, NullableType, and
* UnionType.
*
* @param typeName The type name
* @return The type name. If it is a nullable type, the name is returned with "?"
* @return The type name. If it is a nullable type, the name is returned
* with "?" If it's union type, type names separated by "|" are returned
*/
@CheckForNull
public static String extractUnqualifiedName(Expression typeName) {
Expand All @@ -242,6 +245,16 @@ public static String extractUnqualifiedName(Expression typeName) {
return extractUnqualifiedName((NamespaceName) typeName);
} else if (typeName instanceof NullableType) {
return NULLABLE_TYPE_PREFIX + extractUnqualifiedName(((NullableType) typeName).getType());
} else if (typeName instanceof UnionType) {
UnionType unionType = (UnionType) typeName;
StringBuilder sb = new StringBuilder();
for (Expression type : unionType.getTypes()) {
if (sb.length() > 0) {
sb.append(Type.SEPARATOR);
}
sb.append(extractUnqualifiedName(type));
}
return sb.toString();
}

//TODO: php5.3 !!!
Expand All @@ -250,10 +263,12 @@ public static String extractUnqualifiedName(Expression typeName) {
}

/**
* Extract qualified name for Identifier, NamespaceName, and NullableType.
* Extract qualified name for Identifier, NamespaceName, NullableType, and
* UnionType.
*
* @param typeName The type name
* @return The type name. If it is a nullable type, the name is returned with "?"
* @return The type name. If it is a nullable type, the name is returned
* with "?". If it's union type, type names separated by "|" are returned
*/
@CheckForNull
public static String extractQualifiedName(Expression typeName) {
Expand All @@ -265,6 +280,16 @@ public static String extractQualifiedName(Expression typeName) {
} else if (typeName instanceof NullableType) {
NullableType nullableType = (NullableType) typeName;
return NULLABLE_TYPE_PREFIX + extractQualifiedName(nullableType.getType());
} else if (typeName instanceof UnionType) {
UnionType unionType = (UnionType) typeName;
StringBuilder sb = new StringBuilder();
for (Expression type : unionType.getTypes()) {
if (sb.length() > 0) {
sb.append(Type.SEPARATOR);
}
sb.append(extractQualifiedName(type));
}
return sb.toString();
}
assert false : typeName.getClass();
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.netbeans.modules.php.editor.parser.astnodes.Identifier;
import org.netbeans.modules.php.editor.parser.astnodes.NamespaceName;
import org.netbeans.modules.php.editor.parser.astnodes.NullableType;
import org.netbeans.modules.php.editor.parser.astnodes.UnionType;
import org.openide.util.Parameters;

/**
Expand Down Expand Up @@ -111,6 +112,7 @@ private static QualifiedName getRemainingName(final QualifiedName fullName, fina
return (fullName.toFullyQualified().equals(test.toFullyQualified())) ? retval : null;
}

@CheckForNull
public static QualifiedName createUnqualifiedNameInClassContext(Expression expression, ClassScope clsScope) {
if (expression instanceof Identifier) {
return createUnqualifiedNameInClassContext((Identifier) expression, clsScope);
Expand Down Expand Up @@ -141,6 +143,17 @@ public static QualifiedName create(Expression expression) {
return null;
}

public static List<QualifiedName> create(UnionType unionType) {
List<QualifiedName> qualifiedNames = new ArrayList<>();
for (Expression type : unionType.getTypes()) {
QualifiedName qualifiedName = create(type);
if (qualifiedName != null) {
qualifiedNames.add(qualifiedName);
}
}
return qualifiedNames;
}

public static QualifiedName create(boolean isFullyQualified, List<String> segments) {
return new QualifiedName(isFullyQualified, segments);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ public Collection<TypeResolver> getReturnTypes() {
return getRealFunction().getReturnTypes();
}

@Override
public boolean isReturnUnionType() {
return getRealFunction().isReturnUnionType();
}

@Override
public boolean isAnonymous() {
return getRealFunction().isAnonymous();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ enum PrintAs {

List<ParameterElement> getParameters();
Collection<TypeResolver> getReturnTypes();
/**
* Check whether return type is a union type.
*
* @return {@code true} if not phpdoc but actual return type is a union
* type, {@code false} otherwise
*/
boolean isReturnUnionType();
String asString(PrintAs as);
String asString(PrintAs as, TypeNameResolver typeNameResolver);
String asString(PrintAs as, TypeNameResolver typeNameResolver, PhpVersion phpVersion);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public interface ParameterElement {
String asString(OutputType outputType, TypeNameResolver typeNameResolver);
boolean isReference();
boolean isVariadic();
boolean isUnionType();
Set<TypeResolver> getTypes();
@CheckForNull
String getDefaultValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import org.netbeans.modules.php.editor.parser.astnodes.SingleFieldDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.TraitDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.TypeDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.UnionType;
import org.netbeans.modules.php.editor.parser.astnodes.Variable;
import org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultVisitor;
import org.openide.filesystems.FileObject;
Expand Down Expand Up @@ -361,11 +362,15 @@ private String getPropertyType(FieldsDeclaration fieldsDeclaration, SingleFieldD
type = getPropertyType(singleFieldDeclaration);
} else {
// PHP 7.4 or newer
QualifiedName qualifiedName = QualifiedName.create(fieldsDeclaration.getFieldType());
if (qualifiedName != null) {
type = qualifiedName.toString();
if (fieldsDeclaration.getFieldType() instanceof NullableType) {
type = CodeUtils.NULLABLE_TYPE_PREFIX + type;
if (fieldsDeclaration.getFieldType() instanceof UnionType) {
type = VariousUtils.getUnionType((UnionType) fieldsDeclaration.getFieldType());
} else {
QualifiedName qualifiedName = QualifiedName.create(fieldsDeclaration.getFieldType());
if (qualifiedName != null) {
type = qualifiedName.toString();
if (fieldsDeclaration.getFieldType() instanceof NullableType) {
type = CodeUtils.NULLABLE_TYPE_PREFIX + type;
}
}
}
assert !type.isEmpty() : "couldn't get the qualified name from the field type(" + fieldsDeclaration.getFieldType() + ")"; // NOI18N
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.netbeans.modules.php.editor.model.VariableName;
import org.netbeans.modules.php.editor.model.VariableScope;
import org.netbeans.modules.php.editor.NavUtils;
import org.netbeans.modules.php.editor.model.impl.Type;
import org.netbeans.modules.php.editor.parser.PHPParseResult;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
Expand Down Expand Up @@ -115,7 +116,7 @@ private String getNextVariableType() {
List<? extends VariableName> variables = ModelUtils.filter(declaredVariables, varName);
VariableName first = ModelUtils.getFirst(variables);
if (first != null) {
String typeNames = StringUtils.implode(getUniqueTypeNames(first, offset), "|"); // NOI18N
String typeNames = Type.asUnionType(getUniqueTypeNames(first, offset));
if (!StringUtils.hasText(typeNames)) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.netbeans.modules.php.editor.NavUtils;
import org.netbeans.modules.php.editor.lexer.LexUtilities;
import org.netbeans.modules.php.editor.lexer.PHPTokenId;
import org.netbeans.modules.php.editor.model.impl.Type;
import org.netbeans.modules.php.editor.parser.PHPParseResult;
import org.netbeans.modules.php.editor.parser.api.Utils;
import org.netbeans.modules.php.editor.parser.astnodes.ASTError;
Expand All @@ -59,6 +60,8 @@ final class CompletionContextFinder {
private static final String MULTI_CATCH_EXCEPTION_TOKENS = "MULTI_CATCH_EXCEPTION_TOKENS"; //NOI18N
private static final String COMBINED_USE_STATEMENT_TOKENS = "COMBINED_USE_STATEMENT_TOKENS"; //NOI18N
private static final String CONST_STATEMENT_TOKENS = "CONST_STATEMENT_TOKENS"; //NOI18N
private static final String FIELD_UNION_TYPE_TOKENS = "FIELD_UNION_TYPE_TOKENS"; //NOI18N
private static final String FIELD_MODIFIERS_TOKENS = "FIELD_MODIFIERS_TOKENS"; //NOI18N
private static final String TYPE_KEYWORD = "TYPE_KEYWORD"; //NOI18N
private static final PHPTokenId[] COMMENT_TOKENS = new PHPTokenId[]{
PHPTokenId.PHP_COMMENT_START, PHPTokenId.PHP_COMMENT, PHPTokenId.PHP_LINE_COMMENT, PHPTokenId.PHP_COMMENT_END};
Expand Down Expand Up @@ -155,14 +158,10 @@ final class CompletionContextFinder {
new Object[]{PHPTokenId.PHP_FUNCTION, PHPTokenId.WHITESPACE},
new Object[]{PHPTokenId.PHP_FUNCTION, PHPTokenId.WHITESPACE, PHPTokenId.PHP_STRING});
private static final List<Object[]> FIELD_TYPE_TOKENCHAINS = Arrays.asList(
new Object[]{PHPTokenId.PHP_PRIVATE, PHPTokenId.WHITESPACE, NAMESPACE_FALSE_TOKEN},
new Object[]{PHPTokenId.PHP_PROTECTED, PHPTokenId.WHITESPACE, NAMESPACE_FALSE_TOKEN},
new Object[]{PHPTokenId.PHP_PUBLIC, PHPTokenId.WHITESPACE, NAMESPACE_FALSE_TOKEN},
new Object[]{PHPTokenId.PHP_STATIC, PHPTokenId.WHITESPACE, NAMESPACE_FALSE_TOKEN},
new Object[]{PHPTokenId.PHP_VAR, PHPTokenId.WHITESPACE, NAMESPACE_FALSE_TOKEN},
new Object[]{PHPTokenId.PHP_TOKEN}, // ? (nullable)
new Object[]{PHPTokenId.PHP_TOKEN, PHPTokenId.PHP_STRING},
new Object[]{PHPTokenId.PHP_TOKEN, NAMESPACE_FALSE_TOKEN}
new Object[]{FIELD_MODIFIERS_TOKENS, PHPTokenId.WHITESPACE, NAMESPACE_FALSE_TOKEN},
new Object[]{FIELD_MODIFIERS_TOKENS, PHPTokenId.WHITESPACE, PHPTokenId.PHP_TOKEN},
new Object[]{FIELD_MODIFIERS_TOKENS, PHPTokenId.WHITESPACE, PHPTokenId.PHP_TOKEN, NAMESPACE_FALSE_TOKEN},
new Object[]{FIELD_MODIFIERS_TOKENS, FIELD_UNION_TYPE_TOKENS}
);
private static final List<Object[]> CLASS_CONTEXT_KEYWORDS_TOKENCHAINS = Arrays.asList(
new Object[]{PHPTokenId.PHP_PRIVATE},
Expand Down Expand Up @@ -505,6 +504,16 @@ private static boolean acceptTokenChain(TokenSequence tokenSequence, Object[] to
accept = false;
break;
}
} else if (tokenID == FIELD_MODIFIERS_TOKENS) {
if (!isFieldModifier(tokenSequence.token())) {
accept = false;
break;
}
} else if (tokenID == FIELD_UNION_TYPE_TOKENS) {
if (!consumeFieldUnionType(tokenSequence)) {
accept = false;
break;
}
} else if (tokenID == COMBINED_USE_STATEMENT_TOKENS) {
if (!consumeClassesInCombinedUse(tokenSequence)) {
accept = false;
Expand Down Expand Up @@ -601,6 +610,40 @@ private static boolean consumeClassesConstFunctionInGroupUse(TokenSequence token
return hasCurlyOpen;
}

private static boolean consumeFieldUnionType(TokenSequence tokenSequence) {
if (tokenSequence.token().id() == PHPTokenId.WHITESPACE) {
// e.g. private int ^, private int|string ^, private const ^
if (!tokenSequence.movePrevious()) {
return false;
}
if (isType(tokenSequence.token())
|| isFieldModifier(tokenSequence.token())
|| tokenSequence.token().id() == PHPTokenId.PHP_CONST
|| consumeNameSpace(tokenSequence)) {
return false;
}
}
boolean first = true;
boolean isFieldType = true;
while (tokenSequence.movePrevious()) {
// "|", " ", "Foo", "int", "\Foo\Bar", etc.
if (!isVerticalBar(tokenSequence.token())
&& tokenSequence.token().id() != PHPTokenId.WHITESPACE
&& tokenSequence.token().id() != PHPTokenId.PHP_STRING
&& !isType(tokenSequence.token())
&& !consumeNameSpace(tokenSequence)) {
if (first) {
isFieldType = false;
}
break;
}
if (first) {
first = false;
}
}
return isFieldType;
}

private static boolean consumeMultiCatchExceptions(TokenSequence tokenSequence) {
if (tokenSequence.token().id() != PHPTokenId.PHP_OPERATOR
&& tokenSequence.token().id() != PHPTokenId.PHP_TOKEN
Expand Down Expand Up @@ -877,7 +920,9 @@ private static CompletionContext getParamaterContext(Token<PHPTokenId> token, in
// check "..." (is it really operator?)
if (!isReference(cToken)
&& !isVariadic(cToken)
&& !isInitilizerToken(cToken)) { // ($param = '')
&& !isInitilizerToken(cToken) // ($param = '')
&& !isVerticalBar(cToken) // int|false
&& !isOrOperator(cToken)) { // || (int|^|float)
break;
}
}
Expand Down Expand Up @@ -977,7 +1022,7 @@ private static boolean isEqualSign(Token<PHPTokenId> token) {
}

private static boolean isParamSeparator(Token<PHPTokenId> token) {
return isComma(token) || isLeftBracket(token); //NOI18N
return isComma(token) || isLeftBracket(token) || isVerticalBar(token) || isOrOperator(token);
}

private static boolean isReturnTypeSeparator(Token<PHPTokenId> token) {
Expand All @@ -987,7 +1032,12 @@ private static boolean isReturnTypeSeparator(Token<PHPTokenId> token) {

private static boolean isVerticalBar(Token<PHPTokenId> token) {
return token.id() == PHPTokenId.PHP_OPERATOR
&& TokenUtilities.textEquals(token.text(), "|"); // NOI18N
&& TokenUtilities.textEquals(token.text(), Type.SEPARATOR);
}

private static boolean isOrOperator(Token<PHPTokenId> token) {
return token.id() == PHPTokenId.PHP_OPERATOR
&& TokenUtilities.textEquals(token.text(), "||"); // NOI18N
}

private static boolean isNullableTypesPrefix(Token<PHPTokenId> token) {
Expand Down Expand Up @@ -1023,14 +1073,29 @@ private static boolean isAcceptedPrefix(Token<PHPTokenId> token) {
|| isType(token);
}

private static boolean isFieldModifier(Token<PHPTokenId> token) {
return token.id() == PHPTokenId.PHP_PRIVATE
|| token.id() == PHPTokenId.PHP_PROTECTED
|| token.id() == PHPTokenId.PHP_PUBLIC
|| token.id() == PHPTokenId.PHP_STATIC
|| token.id() == PHPTokenId.PHP_VAR;
}

private static boolean isType(Token<PHPTokenId> token) {
PHPTokenId id = token.id();
return id.equals(PHPTokenId.PHP_TYPE_BOOL)
|| id.equals(PHPTokenId.PHP_TYPE_FLOAT)
|| id.equals(PHPTokenId.PHP_TYPE_INT)
|| id.equals(PHPTokenId.PHP_TYPE_STRING)
|| id.equals(PHPTokenId.PHP_TYPE_VOID)
|| id.equals(PHPTokenId.PHP_TYPE_OBJECT);
return id == PHPTokenId.PHP_TYPE_BOOL
|| id == PHPTokenId.PHP_TYPE_FLOAT
|| id == PHPTokenId.PHP_TYPE_INT
|| id == PHPTokenId.PHP_TYPE_STRING
|| id == PHPTokenId.PHP_TYPE_VOID
|| id == PHPTokenId.PHP_TYPE_OBJECT
|| id == PHPTokenId.PHP_SELF
|| id == PHPTokenId.PHP_PARENT
|| id == PHPTokenId.PHP_NULL
|| id == PHPTokenId.PHP_FALSE
|| id == PHPTokenId.PHP_ARRAY
|| id == PHPTokenId.PHP_ITERABLE
|| id == PHPTokenId.PHP_CALLABLE;
}

private static boolean isComma(Token<PHPTokenId> token) {
Expand Down
Loading

0 comments on commit 6741d2a

Please sign in to comment.