Skip to content

Commit a5eb5b5

Browse files
committed
literal-string in the type system
1 parent 65b91aa commit a5eb5b5

39 files changed

+562
-55
lines changed

src/Analyser/MutatingScope.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
use PHPStan\Reflection\TrivialParametersAcceptor;
4848
use PHPStan\Rules\Properties\PropertyReflectionFinder;
4949
use PHPStan\TrinaryLogic;
50+
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
5051
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
5152
use PHPStan\Type\ArrayType;
5253
use PHPStan\Type\BenevolentUnionType;
@@ -1051,11 +1052,17 @@ private function resolveType(Expr $node): Type
10511052
return $leftStringType->append($rightStringType);
10521053
}
10531054

1055+
$accessoryTypes = [];
10541056
if ($leftStringType->isNonEmptyString()->or($rightStringType->isNonEmptyString())->yes()) {
1055-
return new IntersectionType([
1056-
new StringType(),
1057-
new AccessoryNonEmptyStringType(),
1058-
]);
1057+
$accessoryTypes[] = new AccessoryNonEmptyStringType();
1058+
}
1059+
1060+
if ($leftStringType->isLiteralString()->and($rightStringType->isLiteralString())->yes()) {
1061+
$accessoryTypes[] = new AccessoryLiteralStringType();
1062+
}
1063+
1064+
if (count($accessoryTypes) > 0) {
1065+
return new IntersectionType([new StringType(), ...$accessoryTypes]);
10591066
}
10601067

10611068
return new StringType();

src/PhpDoc/TypeNodeResolver.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use PHPStan\Reflection\Native\NativeParameterReflection;
2929
use PHPStan\Reflection\PassedByReference;
3030
use PHPStan\Reflection\ReflectionProvider;
31+
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
3132
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
3233
use PHPStan\Type\Accessory\AccessoryNumericStringType;
3334
use PHPStan\Type\Accessory\NonEmptyArrayType;
@@ -147,7 +148,7 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
147148
return new StringType();
148149

149150
case 'literal-string':
150-
return new StringType();
151+
return new IntersectionType([new StringType(), new AccessoryLiteralStringType()]);
151152

152153
case 'class-string':
153154
return new ClassStringType();
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Accessory;
4+
5+
use PHPStan\TrinaryLogic;
6+
use PHPStan\Type\CompoundType;
7+
use PHPStan\Type\CompoundTypeHelper;
8+
use PHPStan\Type\Constant\ConstantArrayType;
9+
use PHPStan\Type\Constant\ConstantIntegerType;
10+
use PHPStan\Type\ErrorType;
11+
use PHPStan\Type\FloatType;
12+
use PHPStan\Type\IntegerType;
13+
use PHPStan\Type\IntersectionType;
14+
use PHPStan\Type\MixedType;
15+
use PHPStan\Type\StringType;
16+
use PHPStan\Type\Traits\MaybeCallableTypeTrait;
17+
use PHPStan\Type\Traits\NonGenericTypeTrait;
18+
use PHPStan\Type\Traits\NonIterableTypeTrait;
19+
use PHPStan\Type\Traits\NonObjectTypeTrait;
20+
use PHPStan\Type\Traits\TruthyBooleanTypeTrait;
21+
use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
22+
use PHPStan\Type\Type;
23+
use PHPStan\Type\UnionType;
24+
25+
class AccessoryLiteralStringType implements CompoundType, AccessoryType
26+
{
27+
28+
use MaybeCallableTypeTrait;
29+
use NonObjectTypeTrait;
30+
use NonIterableTypeTrait;
31+
use TruthyBooleanTypeTrait;
32+
use UndecidedComparisonCompoundTypeTrait;
33+
use NonGenericTypeTrait;
34+
35+
/** @api */
36+
public function __construct()
37+
{
38+
}
39+
40+
public function getReferencedClasses(): array
41+
{
42+
return [];
43+
}
44+
45+
public function accepts(Type $type, bool $strictTypes): TrinaryLogic
46+
{
47+
if ($type instanceof MixedType) {
48+
return TrinaryLogic::createNo();
49+
}
50+
if ($type instanceof CompoundType) {
51+
return CompoundTypeHelper::accepts($type, $this, $strictTypes);
52+
}
53+
54+
return $type->isLiteralString();
55+
}
56+
57+
public function isSuperTypeOf(Type $type): TrinaryLogic
58+
{
59+
if ($type instanceof CompoundType) {
60+
return $type->isSubTypeOf($this);
61+
}
62+
63+
if ($this->equals($type)) {
64+
return TrinaryLogic::createYes();
65+
}
66+
67+
return $type->isLiteralString();
68+
}
69+
70+
public function isSubTypeOf(Type $otherType): TrinaryLogic
71+
{
72+
if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) {
73+
return $otherType->isSuperTypeOf($this);
74+
}
75+
76+
return $otherType->isLiteralString()
77+
->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe());
78+
}
79+
80+
public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic
81+
{
82+
return $this->isSubTypeOf($acceptingType);
83+
}
84+
85+
public function equals(Type $type): bool
86+
{
87+
return $type instanceof self;
88+
}
89+
90+
public function describe(\PHPStan\Type\VerbosityLevel $level): string
91+
{
92+
return 'literal-string';
93+
}
94+
95+
public function isOffsetAccessible(): TrinaryLogic
96+
{
97+
return TrinaryLogic::createYes();
98+
}
99+
100+
public function hasOffsetValueType(Type $offsetType): TrinaryLogic
101+
{
102+
return (new IntegerType())->isSuperTypeOf($offsetType)->and(TrinaryLogic::createMaybe());
103+
}
104+
105+
public function getOffsetValueType(Type $offsetType): Type
106+
{
107+
if ($this->hasOffsetValueType($offsetType)->no()) {
108+
return new ErrorType();
109+
}
110+
111+
return new StringType();
112+
}
113+
114+
public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
115+
{
116+
return $this;
117+
}
118+
119+
public function isArray(): TrinaryLogic
120+
{
121+
return TrinaryLogic::createNo();
122+
}
123+
124+
public function toNumber(): Type
125+
{
126+
return new UnionType([
127+
$this->toInteger(),
128+
$this->toFloat(),
129+
]);
130+
}
131+
132+
public function toInteger(): Type
133+
{
134+
return new IntegerType();
135+
}
136+
137+
public function toFloat(): Type
138+
{
139+
return new FloatType();
140+
}
141+
142+
public function toString(): Type
143+
{
144+
return $this;
145+
}
146+
147+
public function toArray(): Type
148+
{
149+
return new ConstantArrayType(
150+
[new ConstantIntegerType(0)],
151+
[$this],
152+
1
153+
);
154+
}
155+
156+
public function isNumericString(): TrinaryLogic
157+
{
158+
return TrinaryLogic::createMaybe();
159+
}
160+
161+
public function isNonEmptyString(): TrinaryLogic
162+
{
163+
return TrinaryLogic::createMaybe();
164+
}
165+
166+
public function isLiteralString(): TrinaryLogic
167+
{
168+
return TrinaryLogic::createYes();
169+
}
170+
171+
public function traverse(callable $cb): Type
172+
{
173+
return $this;
174+
}
175+
176+
public static function __set_state(array $properties): Type
177+
{
178+
return new self();
179+
}
180+
181+
}

src/Type/Accessory/AccessoryNonEmptyStringType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ public function isNonEmptyString(): TrinaryLogic
163163
return TrinaryLogic::createYes();
164164
}
165165

166+
public function isLiteralString(): TrinaryLogic
167+
{
168+
return TrinaryLogic::createMaybe();
169+
}
170+
166171
public function traverse(callable $cb): Type
167172
{
168173
return $this;

src/Type/Accessory/AccessoryNumericStringType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ public function isNonEmptyString(): TrinaryLogic
159159
return TrinaryLogic::createYes();
160160
}
161161

162+
public function isLiteralString(): TrinaryLogic
163+
{
164+
return TrinaryLogic::createMaybe();
165+
}
166+
162167
public function traverse(callable $cb): Type
163168
{
164169
return $this;

src/Type/Accessory/HasOffsetType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ public function isNonEmptyString(): TrinaryLogic
136136
return TrinaryLogic::createMaybe();
137137
}
138138

139+
public function isLiteralString(): TrinaryLogic
140+
{
141+
return TrinaryLogic::createMaybe();
142+
}
143+
139144
public function toNumber(): Type
140145
{
141146
return new ErrorType();

src/Type/Accessory/NonEmptyArrayType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ public function isNonEmptyString(): TrinaryLogic
142142
return TrinaryLogic::createNo();
143143
}
144144

145+
public function isLiteralString(): TrinaryLogic
146+
{
147+
return TrinaryLogic::createNo();
148+
}
149+
145150
public function toNumber(): Type
146151
{
147152
return new ErrorType();

src/Type/ArrayType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ public function isNonEmptyString(): TrinaryLogic
200200
return TrinaryLogic::createNo();
201201
}
202202

203+
public function isLiteralString(): TrinaryLogic
204+
{
205+
return TrinaryLogic::createNo();
206+
}
207+
203208
public function isOffsetAccessible(): TrinaryLogic
204209
{
205210
return TrinaryLogic::createYes();

src/Type/ClassStringType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ public function isNonEmptyString(): TrinaryLogic
7575
return TrinaryLogic::createYes();
7676
}
7777

78+
public function isLiteralString(): TrinaryLogic
79+
{
80+
return TrinaryLogic::createMaybe();
81+
}
82+
7883
/**
7984
* @param mixed[] $properties
8085
* @return Type

src/Type/ClosureType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,11 @@ public function isNonEmptyString(): TrinaryLogic
411411
return TrinaryLogic::createNo();
412412
}
413413

414+
public function isLiteralString(): TrinaryLogic
415+
{
416+
return TrinaryLogic::createNo();
417+
}
418+
414419
/**
415420
* @param mixed[] $properties
416421
* @return Type

src/Type/Constant/ConstantStringType.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PHPStan\Reflection\InaccessibleMethod;
99
use PHPStan\Reflection\TrivialParametersAcceptor;
1010
use PHPStan\TrinaryLogic;
11+
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
1112
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
1213
use PHPStan\Type\ClassStringType;
1314
use PHPStan\Type\CompoundType;
@@ -247,6 +248,11 @@ public function isNonEmptyString(): TrinaryLogic
247248
return TrinaryLogic::createFromBoolean($this->getValue() !== '');
248249
}
249250

251+
public function isLiteralString(): TrinaryLogic
252+
{
253+
return TrinaryLogic::createYes();
254+
}
255+
250256
public function hasOffsetValueType(Type $offsetType): TrinaryLogic
251257
{
252258
if ($offsetType instanceof ConstantIntegerType) {
@@ -309,6 +315,14 @@ public function generalize(?GeneralizePrecision $precision = null): Type
309315
return new IntersectionType([
310316
new StringType(),
311317
new AccessoryNonEmptyStringType(),
318+
new AccessoryLiteralStringType(),
319+
]);
320+
}
321+
322+
if ($precision !== null && $precision->isMoreSpecific()) {
323+
return new IntersectionType([
324+
new StringType(),
325+
new AccessoryLiteralStringType(),
312326
]);
313327
}
314328

src/Type/FloatType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ public function isNonEmptyString(): TrinaryLogic
143143
return TrinaryLogic::createNo();
144144
}
145145

146+
public function isLiteralString(): TrinaryLogic
147+
{
148+
return TrinaryLogic::createNo();
149+
}
150+
146151
public function traverse(callable $cb): Type
147152
{
148153
return $this;

src/Type/Generic/TemplateTypeHelper.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ public static function generalizeType(Type $type): Type
7575
return new StringType();
7676
}
7777

78+
if ($type->isLiteralString()->yes()) {
79+
return new StringType();
80+
}
81+
7882
return $traverse($type);
7983
});
8084
}

0 commit comments

Comments
 (0)