Skip to content

Commit 1ab24d2

Browse files
committed
Add support for PHP 7.1 types to builders
This adds support for void, iterable and nullable types.
1 parent a712011 commit 1ab24d2

File tree

6 files changed

+117
-36
lines changed

6 files changed

+117
-36
lines changed

lib/PhpParser/Builder/FunctionLike.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,14 @@ public function addParams(array $params) {
6060
/**
6161
* Sets the return type for PHP 7.
6262
*
63-
* @param string|Node\Name $type One of array, callable, string, int, float, bool,
63+
* @param string|Node\Name $type One of array, callable, string, int, float, bool, iterable,
6464
* or a class/interface name.
6565
*
6666
* @return $this The builder instance (for fluid interface)
6767
*/
6868
public function setReturnType($type)
6969
{
70-
if (in_array($type, array('array', 'callable', 'string', 'int', 'float', 'bool'))) {
71-
$this->returnType = $type;
72-
} else {
73-
$this->returnType = $this->normalizeName($type);
74-
}
70+
$this->returnType = $this->normalizeType($type);
7571

7672
return $this;
7773
}

lib/PhpParser/Builder/Param.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,9 @@ public function setDefault($value) {
4343
* @return $this The builder instance (for fluid interface)
4444
*/
4545
public function setTypeHint($type) {
46-
if (in_array($type, array('array', 'callable', 'string', 'int', 'float', 'bool'))) {
47-
$this->type = $type;
48-
} else {
49-
$this->type = $this->normalizeName($type);
46+
$this->type = $this->normalizeType($type);
47+
if ($this->type === 'void') {
48+
throw new \LogicException('Parameter type cannot be void');
5049
}
5150

5251
return $this;

lib/PhpParser/BuilderAbstract.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpParser\Node\Name;
66
use PhpParser\Node\Expr;
7+
use PhpParser\Node\NullableType;
78
use PhpParser\Node\Stmt;
89
use PhpParser\Node\Scalar;
910
use PhpParser\Comment;
@@ -53,6 +54,49 @@ protected function normalizeName($name) {
5354
throw new \LogicException('Name must be a string or an instance of PhpParser\Node\Name');
5455
}
5556

57+
/**
58+
* Normalizes a type: Converts plain-text type names into proper AST representation.
59+
*
60+
* In particular, builtin types are left as strings, custom types become Names and nullables
61+
* are wrapped in NullableType nodes.
62+
*
63+
* @param Name|string $type The type to normalize
64+
*
65+
* @return Name|string The normalized type
66+
*/
67+
protected function normalizeType($type) {
68+
if (!is_string($type)) {
69+
if (!$type instanceof Name && !$type instanceof NullableType) {
70+
throw new \LogicException(
71+
'Type must be a string, or an instance of Name or NullableType');
72+
}
73+
return $type;
74+
}
75+
76+
$nullable = false;
77+
if (strlen($type) > 0 && $type[0] === '?') {
78+
$nullable = true;
79+
$type = substr($type, 1);
80+
}
81+
82+
$builtinTypes = array(
83+
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void'
84+
);
85+
86+
$lowerType = strtolower($type);
87+
if (in_array($lowerType, $builtinTypes)) {
88+
$type = $lowerType;
89+
} else {
90+
$type = $this->normalizeName($type);
91+
}
92+
93+
if ($nullable && $type === 'void') {
94+
throw new \LogicException('void type cannot be nullable');
95+
}
96+
97+
return $nullable ? new Node\NullableType($type) : $type;
98+
}
99+
56100
/**
57101
* Normalizes a value: Converts nulls, booleans, integers,
58102
* floats, strings and arrays into their respective nodes

lib/PhpParser/Node/NullableType.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ class NullableType extends NodeAbstract
1212
/**
1313
* Constructs a nullable type (wrapping another type).
1414
*
15-
* @param string|Name $flags Type
15+
* @param string|Name $type Type
1616
* @param array $attributes Additional attributes
1717
*/
18-
public function __construct($flags, array $attributes = array()) {
18+
public function __construct($type, array $attributes = array()) {
1919
parent::__construct($attributes);
20-
$this->type = $flags;
20+
$this->type = $type;
2121
}
2222

2323
public function getSubNodeNames() {

test/PhpParser/Builder/FunctionTest.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,22 @@ public function testDocComment() {
7878

7979
public function testReturnType() {
8080
$node = $this->createFunctionBuilder('test')
81-
->setReturnType('bool')
81+
->setReturnType('void')
8282
->getNode();
8383

8484
$this->assertEquals(new Stmt\Function_('test', array(
85-
'returnType' => 'bool'
85+
'returnType' => 'void'
8686
), array()), $node);
8787
}
8888

89+
/**
90+
* @expectedException \LogicException
91+
* @expectedExceptionMessage void type cannot be nullable
92+
*/
93+
public function testInvalidNullableVoidType() {
94+
$this->createFunctionBuilder('test')->setReturnType('?void');
95+
}
96+
8997
/**
9098
* @expectedException \LogicException
9199
* @expectedExceptionMessage Expected parameter node, got "Name"

test/PhpParser/Builder/ParamTest.php

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -78,36 +78,70 @@ public function provideTestDefaultValues() {
7878
);
7979
}
8080

81-
public function testTypeHints() {
81+
/**
82+
* @dataProvider provideTestTypeHints
83+
*/
84+
public function testTypeHints($typeHint, $expectedType) {
8285
$node = $this->createParamBuilder('test')
83-
->setTypeHint('array')
86+
->setTypeHint($typeHint)
8487
->getNode()
8588
;
89+
$type = $node->type;
8690

87-
$this->assertEquals(
88-
new Node\Param('test', null, 'array'),
89-
$node
90-
);
91+
/* Manually implement comparison to avoid __toString stupidity */
92+
if ($expectedType instanceof Node\NullableType) {
93+
$this->assertInstanceOf(get_class($expectedType), $type);
94+
$expectedType = $expectedType->type;
95+
$type = $type->type;
96+
}
9197

92-
$node = $this->createParamBuilder('test')
93-
->setTypeHint('callable')
94-
->getNode()
95-
;
98+
if ($expectedType instanceof Node\Name) {
99+
$this->assertInstanceOf(get_class($expectedType), $type);
100+
$this->assertEquals($expectedType, $type);
101+
} else {
102+
$this->assertSame($expectedType, $type);
103+
}
104+
}
96105

97-
$this->assertEquals(
98-
new Node\Param('test', null, 'callable'),
99-
$node
106+
public function provideTestTypeHints() {
107+
return array(
108+
array('array', 'array'),
109+
array('callable', 'callable'),
110+
array('bool', 'bool'),
111+
array('int', 'int'),
112+
array('float', 'float'),
113+
array('string', 'string'),
114+
array('iterable', 'iterable'),
115+
array('Array', 'array'),
116+
array('CALLABLE', 'callable'),
117+
array('Some\Class', new Node\Name('Some\Class')),
118+
array('\Foo', new Node\Name\FullyQualified('Foo')),
119+
array('self', new Node\Name('self')),
120+
array('?array', new Node\NullableType('array')),
121+
array('?Some\Class', new Node\NullableType(new Node\Name('Some\Class'))),
122+
array(new Node\Name('Some\Class'), new Node\Name('Some\Class')),
123+
array(new Node\NullableType('int'), new Node\NullableType('int')),
124+
array(
125+
new Node\NullableType(new Node\Name('Some\Class')),
126+
new Node\NullableType(new Node\Name('Some\Class'))
127+
),
100128
);
129+
}
101130

102-
$node = $this->createParamBuilder('test')
103-
->setTypeHint('Some\Class')
104-
->getNode()
105-
;
131+
/**
132+
* @expectedException \LogicException
133+
* @expectedExceptionMessage Parameter type cannot be void
134+
*/
135+
public function testVoidTypeError() {
136+
$this->createParamBuilder('test')->setTypeHint('void');
137+
}
106138

107-
$this->assertEquals(
108-
new Node\Param('test', null, new Node\Name('Some\Class')),
109-
$node
110-
);
139+
/**
140+
* @expectedException \LogicException
141+
* @expectedExceptionMessage Type must be a string, or an instance of Name or NullableType
142+
*/
143+
public function testInvalidTypeError() {
144+
$this->createParamBuilder('test')->setTypeHint(new \stdClass);
111145
}
112146

113147
public function testByRef() {

0 commit comments

Comments
 (0)