Skip to content

Commit

Permalink
PHP 8.3 Support: Typed class constants (Part 6)
Browse files Browse the repository at this point in the history
- #6701
- https://wiki.php.net/rfc/typed_class_constants
- Fix the formatter (don't add spaces within parens of DNF types)
- Fix the `UnusableTypeHintError`
- Add unit tests
  • Loading branch information
junichi11 committed Dec 8, 2023
1 parent be57ef0 commit 450cb20
Show file tree
Hide file tree
Showing 13 changed files with 685 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ public enum Kind {
WHITESPACE_WITHIN_ATTRIBUTE_BRACKETS,
WHITESPACE_WITHIN_ATTRIBUTE_DECL_PARENS,
WHITESPACE_WITHIN_TYPE_CAST_PARENS,
WHITESPACE_WITHIN_DNF_TYPE_PARENS, // (A&B)|C
WHITESPACE_WITHIN_DYNAMIC_NAME_BRACES, // {$example}
WHITESPACE_BEFORE_COMMA,
WHITESPACE_AFTER_COMMA,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1080,13 +1080,14 @@ public void visit(ConstantDeclaration node) {
}
}
scan(node.getAttributes());
while (ts.moveNext() && ts.token().id() != PHPTokenId.PHP_STRING) {
while (ts.moveNext() && !isConstTypeToken(ts.token())) {
addFormatToken(formatTokens);
}
ts.movePrevious();
FormatToken lastWhitespace = formatTokens.remove(formatTokens.size() - 1);
formatTokens.add(new FormatToken(FormatToken.Kind.WHITESPACE_AFTER_MODIFIERS, lastWhitespace.getOffset(), lastWhitespace.getOldText()));
addFormatToken(formatTokens);
formatTokens.add(new FormatToken.IndentToken(node.getStartOffset(), options.continualIndentSize));
scan(node.getConstType());
scan(node.getNames());
if (node.getNames().size() == 1) {
while (ts.moveNext()
Expand Down Expand Up @@ -2499,6 +2500,8 @@ public void visit(DeclareStatement node) {
@Override
public void visit(UnionType node) {
processUnionOrIntersectionType(node.getTypes());
// add ")" if it exists e.g. (A&B)|(B&C)
addAllUntilOffset(node.getEndOffset());
}

@Override
Expand Down Expand Up @@ -2729,6 +2732,9 @@ private void addFormatToken(List<FormatToken> tokens) {
tokens.add(new FormatToken(FormatToken.Kind.WHITESPACE_BEFORE_ARRAY_DECL_PAREN, ts.offset()));
tokens.add(new FormatToken(FormatToken.Kind.TEXT, ts.offset(), ts.token().text().toString()));
tokens.add(new FormatToken(FormatToken.Kind.WHITESPACE_AFTER_ARRAY_DECL_LEFT_PAREN, ts.offset() + ts.token().length()));
} else if (parent instanceof UnionType) {
tokens.add(new FormatToken(FormatToken.Kind.TEXT, ts.offset(), ts.token().text().toString()));
tokens.add(new FormatToken(FormatToken.Kind.WHITESPACE_WITHIN_DNF_TYPE_PARENS, ts.offset() + ts.token().length()));
} else {
tokens.add(new FormatToken(FormatToken.Kind.TEXT, ts.offset(), ts.token().text().toString()));
}
Expand Down Expand Up @@ -2767,6 +2773,9 @@ private void addFormatToken(List<FormatToken> tokens) {
} else if (parent instanceof ArrayCreation) {
tokens.add(new FormatToken(FormatToken.Kind.WHITESPACE_BEFORE_ARRAY_DECL_RIGHT_PAREN, ts.offset()));
tokens.add(new FormatToken(FormatToken.Kind.TEXT, ts.offset(), ts.token().text().toString()));
} else if (parent instanceof UnionType) {
tokens.add(new FormatToken(FormatToken.Kind.WHITESPACE_WITHIN_DNF_TYPE_PARENS, ts.offset()));
tokens.add(new FormatToken(FormatToken.Kind.TEXT, ts.offset(), ts.token().text().toString()));
} else {
tokens.add(new FormatToken(FormatToken.Kind.TEXT, ts.offset(), ts.token().text().toString()));
}
Expand Down Expand Up @@ -3349,7 +3358,11 @@ private boolean isFirstUseTraitStatementInBlock(ASTNode parentNode, UseTraitStat

private boolean isFieldTypeOrVariableToken(Token<PHPTokenId> token) {
return PHPTokenId.PHP_VARIABLE == token.id()
|| PHPTokenId.PHP_STRING == token.id()
|| isConstTypeToken(token);
}

private boolean isConstTypeToken(Token<PHPTokenId> token) {
return PHPTokenId.PHP_STRING == token.id()
|| PHPTokenId.PHP_ARRAY == token.id()
|| PHPTokenId.PHP_ITERABLE == token.id()
|| PHPTokenId.PHP_PARENT == token.id()
Expand All @@ -3362,9 +3375,11 @@ private boolean isFieldTypeOrVariableToken(Token<PHPTokenId> token) {
|| PHPTokenId.PHP_NULL == token.id()
|| PHPTokenId.PHP_FALSE == token.id()
|| PHPTokenId.PHP_NS_SEPARATOR == token.id() // \
|| (PHPTokenId.PHP_TOKEN == token.id() && TokenUtilities.textEquals(token.text(), "(")) // NOI18N
|| (PHPTokenId.PHP_TOKEN == token.id() && TokenUtilities.textEquals(token.text(), "?")) // NOI18N
|| PHPTokenId.PHP_TYPE_VOID == token.id() // not supported type but just check it
|| PHPTokenId.PHP_CALLABLE == token.id() // not supported type but just check it
|| PHPTokenId.PHP_TYPE_NEVER == token.id() // not supported type but just check it
;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,10 @@ && countOfNewLines(formatTokens.get(index + 1).getOldText()) > 0) {
case WHITESPACE_WITHIN_TYPE_CAST_PARENS:
countSpaces = docOptions.spaceWithinTypeCastParens ? 1 : 0;
break;
case WHITESPACE_WITHIN_DNF_TYPE_PARENS:
// change here if we add the option for it
countSpaces = 0;
break;
case WHITESPACE_WITHIN_DYNAMIC_NAME_BRACES:
// change here if we add the option for it
countSpaces = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.netbeans.modules.php.editor.parser.PHPParseResult;
import org.netbeans.modules.php.editor.parser.astnodes.ASTNode;
import org.netbeans.modules.php.editor.parser.astnodes.ArrowFunctionDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.ConstantDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.Expression;
import org.netbeans.modules.php.editor.parser.astnodes.FieldsDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.FormalParameter;
Expand Down Expand Up @@ -128,7 +129,19 @@ public void visit(FieldsDeclaration node) {
}
Expression fieldType = node.getFieldType();
if (fieldType != null) {
checkFieldType(fieldType, false);
checkFieldAndConstType(fieldType, false);
}
super.visit(node);
}

@Override
public void visit(ConstantDeclaration node) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
Expression constType = node.getConstType();
if (constType != null) {
checkFieldAndConstType(constType, false);
}
super.visit(node);
}
Expand Down Expand Up @@ -239,11 +252,11 @@ public void visit(NullableType nullableType) {
super.visit(nullableType);
}

private void checkFieldType(@NullAllowed Expression fieldType, boolean isInUnionType) {
private void checkFieldAndConstType(@NullAllowed Expression declaredType, boolean isInUnionType) {
// unusable types: void and callable PHP 7.4
Expression type = fieldType;
if (fieldType instanceof NullableType) {
type = ((NullableType) fieldType).getType();
Expression type = declaredType;
if (declaredType instanceof NullableType) {
type = ((NullableType) declaredType).getType();
}
if (type == null) {
return;
Expand All @@ -262,7 +275,7 @@ private void checkFieldType(@NullAllowed Expression fieldType, boolean isInUnion
checkTrueAndFalseAndNullTypes((NamespaceName) type);
}
} else if (type instanceof UnionType) {
((UnionType) type).getTypes().forEach(unionType -> checkFieldType(unionType, true));
((UnionType) type).getTypes().forEach(unionType -> checkFieldAndConstType(unionType, true));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class ClassY {
private (ClassX&ClassZ)|(ClassY&ClassZ)|ClassX $privateYField;
protected ClassX|(ClassY&ClassZ)|ClassY $protectedYField;
public static (ClassY&ClassZ)|ClassX $publicStaticYField;
private static ( ClassY&ClassZ)|ClassY $privateStaticYField;
private static (ClassY&ClassZ)|ClassY $privateStaticYField;
protected static (ClassY&ClassZ)|ClassX|ClassY $protectedStaticYField;

public function publicYMethod((ClassY&ClassZ)|ClassX $param1, (ClassY&ClassZ)|ClassX|(ClassX&ClassZ) $param2): ClassX|(ClassY&ClassZ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

class A implements Stringable {
public function __toString() {
return static::class;
}
}

class B extends A {}
class C extends A {}

class ClassTest {
public const WITHOUT_TYPE = 1;
public const ?int NULLABLE = 1;
private const A | B UNION = D_A;
protected const A & B INTERSECTION = D_B;
public const (A &B ) | C DNF = D_C;
public const string STRING = 'a';
public const int INT = 1;
public const float FLOAT = 1.5;
public const bool BOOL = true;
public const array ARRAY = ['t', 'e', 's', 't'];
public const iterable ITERABLE = [ 'a', 'b', 'c'];
public const mixed MIXED = 1;
public const object OBJECT = D_A;
public const string | array UNION2 = 'a' , UNION3 = ['a'];
#[Attr]
public const int | null UNION4 = null;
}

interface InterfaceTest {
const string STRING = "string";
public const ? int NULLABLE = 1;
public const A|B UNION = D_A;
public const A&B INTERSECTION = D_B;
public const ( A & B ) |C DNF = D_C;
}

trait TraitTest {
const string STRING = "string";
public const ?int NULLABLE = 1;
private const A|B UNION = D_A;
protected const A&B INTERSECTION = D_B;
public const (A&B)|C DNF = D_C;
}

enum EnumTest {
const string STRING = "string";
public const ? int NULLABLE = 1;
private const A| B UNION=D_A;
protected const A&B INTERSECTION=D_B;
public const (A & B) | (A&C ) DNF=D_C;
public const static A=EnumTest :: Test;

case Test;
}

define("D_A", new A());
define("D_B", new B());
define("D_C", new C());

echo ClassTest::DNF . PHP_EOL;
var_dump(ClassTest::DNF);
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

class A implements Stringable {

public function __toString() {
return static::class;
}
}

class B extends A {

}

class C extends A {

}

class ClassTest {

public const WITHOUT_TYPE = 1;
public const ?int NULLABLE = 1;
private const A|B UNION = D_A;
protected const A&B INTERSECTION = D_B;
public const (A&B)|C DNF = D_C;
public const string STRING = 'a';
public const int INT = 1;
public const float FLOAT = 1.5;
public const bool BOOL = true;
public const array ARRAY = ['t', 'e', 's', 't'];
public const iterable ITERABLE = ['a', 'b', 'c'];
public const mixed MIXED = 1;
public const object OBJECT = D_A;
public const string|array UNION2 = 'a', UNION3 = ['a'];

#[Attr]
public const int|null UNION4 = null;
}

interface InterfaceTest {

const string STRING = "string";
public const ?int NULLABLE = 1;
public const A|B UNION = D_A;
public const A&B INTERSECTION = D_B;
public const (A&B)|C DNF = D_C;
}

trait TraitTest {

const string STRING = "string";
public const ?int NULLABLE = 1;
private const A|B UNION = D_A;
protected const A&B INTERSECTION = D_B;
public const (A&B)|C DNF = D_C;
}

enum EnumTest {

const string STRING = "string";
public const ?int NULLABLE = 1;
private const A|B UNION = D_A;
protected const A&B INTERSECTION = D_B;
public const (A&B)|(A&C) DNF = D_C;
public const static A = EnumTest::Test;

case Test;
}

define("D_A", new A());
define("D_B", new B());
define("D_C", new C());

echo ClassTest::DNF . PHP_EOL;
var_dump(ClassTest::DNF);
Loading

0 comments on commit 450cb20

Please sign in to comment.