@@ -1072,50 +1072,7 @@ private function resolveType(Expr $node): Type
10721072 }
10731073
10741074 if ($ node instanceof Expr \BinaryOp \Concat || $ node instanceof Expr \AssignOp \Concat) {
1075- if ($ node instanceof Node \Expr \AssignOp) {
1076- $ left = $ node ->var ;
1077- $ right = $ node ->expr ;
1078- } else {
1079- $ left = $ node ->left ;
1080- $ right = $ node ->right ;
1081- }
1082-
1083- $ leftStringType = $ this ->getType ($ left )->toString ();
1084- $ rightStringType = $ this ->getType ($ right )->toString ();
1085- if (TypeCombinator::union (
1086- $ leftStringType ,
1087- $ rightStringType ,
1088- ) instanceof ErrorType) {
1089- return new ErrorType ();
1090- }
1091-
1092- if ($ leftStringType instanceof ConstantStringType && $ leftStringType ->getValue () === '' ) {
1093- return $ rightStringType ;
1094- }
1095-
1096- if ($ rightStringType instanceof ConstantStringType && $ rightStringType ->getValue () === '' ) {
1097- return $ leftStringType ;
1098- }
1099-
1100- if ($ leftStringType instanceof ConstantStringType && $ rightStringType instanceof ConstantStringType) {
1101- return $ leftStringType ->append ($ rightStringType );
1102- }
1103-
1104- $ accessoryTypes = [];
1105- if ($ leftStringType ->isNonEmptyString ()->or ($ rightStringType ->isNonEmptyString ())->yes ()) {
1106- $ accessoryTypes [] = new AccessoryNonEmptyStringType ();
1107- }
1108-
1109- if ($ leftStringType ->isLiteralString ()->and ($ rightStringType ->isLiteralString ())->yes ()) {
1110- $ accessoryTypes [] = new AccessoryLiteralStringType ();
1111- }
1112-
1113- if (count ($ accessoryTypes ) > 0 ) {
1114- $ accessoryTypes [] = new StringType ();
1115- return new IntersectionType ($ accessoryTypes );
1116- }
1117-
1118- return new StringType ();
1075+ return $ this ->resolveConcatType ($ node );
11191076 }
11201077
11211078 if (
@@ -2680,6 +2637,86 @@ private function resolveType(Expr $node): Type
26802637 return new MixedType ();
26812638 }
26822639
2640+ private function resolveConcatType (Expr \BinaryOp \Concat |Expr \AssignOp \Concat $ node ): Type
2641+ {
2642+ if ($ node instanceof Node \Expr \AssignOp) {
2643+ $ left = $ node ->var ;
2644+ $ right = $ node ->expr ;
2645+ } else {
2646+ $ left = $ node ->left ;
2647+ $ right = $ node ->right ;
2648+ }
2649+
2650+ $ leftStringType = $ this ->getType ($ left )->toString ();
2651+ $ rightStringType = $ this ->getType ($ right )->toString ();
2652+ if (TypeCombinator::union (
2653+ $ leftStringType ,
2654+ $ rightStringType ,
2655+ ) instanceof ErrorType) {
2656+ return new ErrorType ();
2657+ }
2658+
2659+ if ($ leftStringType instanceof ConstantStringType && $ leftStringType ->getValue () === '' ) {
2660+ return $ rightStringType ;
2661+ }
2662+
2663+ if ($ rightStringType instanceof ConstantStringType && $ rightStringType ->getValue () === '' ) {
2664+ return $ leftStringType ;
2665+ }
2666+
2667+ if ($ leftStringType instanceof ConstantStringType && $ rightStringType instanceof ConstantStringType) {
2668+ return $ leftStringType ->append ($ rightStringType );
2669+ }
2670+
2671+ // we limit the number of union-types for performance reasons
2672+ if ($ leftStringType instanceof UnionType && count ($ leftStringType ->getTypes ()) <= 16 && $ rightStringType instanceof ConstantStringType) {
2673+ $ constantStrings = TypeUtils::getConstantStrings ($ leftStringType );
2674+ if (count ($ constantStrings ) > 0 ) {
2675+ $ strings = [];
2676+ foreach ($ constantStrings as $ constantString ) {
2677+ if ($ constantString ->getValue () === '' ) {
2678+ $ strings [] = $ rightStringType ;
2679+
2680+ continue ;
2681+ }
2682+ $ strings [] = $ constantString ->append ($ rightStringType );
2683+ }
2684+ return TypeCombinator::union (...$ strings );
2685+ }
2686+ }
2687+ if ($ rightStringType instanceof UnionType && count ($ rightStringType ->getTypes ()) <= 16 && $ leftStringType instanceof ConstantStringType) {
2688+ $ constantStrings = TypeUtils::getConstantStrings ($ rightStringType );
2689+ if (count ($ constantStrings ) > 0 ) {
2690+ $ strings = [];
2691+ foreach ($ constantStrings as $ constantString ) {
2692+ if ($ constantString ->getValue () === '' ) {
2693+ $ strings [] = $ leftStringType ;
2694+
2695+ continue ;
2696+ }
2697+ $ strings [] = $ leftStringType ->append ($ constantString );
2698+ }
2699+ return TypeCombinator::union (...$ strings );
2700+ }
2701+ }
2702+
2703+ $ accessoryTypes = [];
2704+ if ($ leftStringType ->isNonEmptyString ()->or ($ rightStringType ->isNonEmptyString ())->yes ()) {
2705+ $ accessoryTypes [] = new AccessoryNonEmptyStringType ();
2706+ }
2707+
2708+ if ($ leftStringType ->isLiteralString ()->and ($ rightStringType ->isLiteralString ())->yes ()) {
2709+ $ accessoryTypes [] = new AccessoryLiteralStringType ();
2710+ }
2711+
2712+ if (count ($ accessoryTypes ) > 0 ) {
2713+ $ accessoryTypes [] = new StringType ();
2714+ return new IntersectionType ($ accessoryTypes );
2715+ }
2716+
2717+ return new StringType ();
2718+ }
2719+
26832720 private function getNullsafeShortCircuitingType (Expr $ expr , Type $ type ): Type
26842721 {
26852722 if ($ expr instanceof Expr \NullsafePropertyFetch || $ expr instanceof Expr \NullsafeMethodCall) {
0 commit comments