YieldAnalyzer   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 202
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 25

Importance

Changes 0
Metric Value
dl 0
loc 202
rs 9.28
c 0
b 0
f 0
wmc 39
lcom 1
cbo 25

1 Method

Rating   Name   Duplication   Size   Complexity  
F analyze() 0 194 39
1
<?php
2
namespace Psalm\Internal\Analyzer\Statements\Expression;
3
4
use PhpParser;
5
use Psalm\Internal\Analyzer\CommentAnalyzer;
6
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
7
use Psalm\Internal\Analyzer\TraitAnalyzer;
8
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
9
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\InstancePropertyFetchAnalyzer;
10
use Psalm\Internal\Analyzer\StatementsAnalyzer;
11
use Psalm\CodeLocation;
12
use Psalm\Context;
13
use Psalm\Exception\DocblockParseException;
14
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
15
use Psalm\Issue\InvalidDocblock;
16
use Psalm\Issue\UnnecessaryVarAnnotation;
17
use Psalm\IssueBuffer;
18
use Psalm\Type;
19
20
class YieldAnalyzer
21
{
22
    /**
23
     * @param   StatementsAnalyzer           $statements_analyzer
24
     * @param   PhpParser\Node\Expr\Yield_  $stmt
25
     * @param   Context                     $context
26
     */
27
    public static function analyze(
28
        StatementsAnalyzer $statements_analyzer,
29
        PhpParser\Node\Expr\Yield_ $stmt,
30
        Context $context
31
    ) : bool {
32
        $doc_comment = $stmt->getDocComment();
33
34
        $var_comments = [];
35
        $var_comment_type = null;
36
37
        $codebase = $statements_analyzer->getCodebase();
38
39
        if ($doc_comment) {
40
            try {
41
                $var_comments = CommentAnalyzer::getTypeFromComment(
42
                    $doc_comment,
43
                    $statements_analyzer,
44
                    $statements_analyzer->getAliases()
45
                );
46
            } catch (DocblockParseException $e) {
47
                if (IssueBuffer::accepts(
48
                    new InvalidDocblock(
49
                        (string)$e->getMessage(),
50
                        new CodeLocation($statements_analyzer->getSource(), $stmt)
51
                    )
52
                )) {
53
                    // fall through
54
                }
55
            }
56
57
            foreach ($var_comments as $var_comment) {
58
                if (!$var_comment->type) {
59
                    continue;
60
                }
61
62
                $comment_type = \Psalm\Internal\Type\TypeExpander::expandUnion(
63
                    $codebase,
64
                    $var_comment->type,
65
                    $context->self,
66
                    $context->self ? new Type\Atomic\TNamedObject($context->self) : null,
67
                    $statements_analyzer->getParentFQCLN()
68
                );
69
70
                $type_location = null;
71
72
                if ($var_comment->type_start
73
                    && $var_comment->type_end
74
                    && $var_comment->line_number
75
                ) {
76
                    $type_location = new CodeLocation\DocblockTypeLocation(
77
                        $statements_analyzer,
78
                        $var_comment->type_start,
79
                        $var_comment->type_end,
80
                        $var_comment->line_number
81
                    );
82
                }
83
84
                if (!$var_comment->var_id) {
85
                    $var_comment_type = $comment_type;
86
                    continue;
87
                }
88
89
                if ($codebase->find_unused_variables
90
                    && $type_location
91
                    && isset($context->vars_in_scope[$var_comment->var_id])
92
                    && $context->vars_in_scope[$var_comment->var_id]->getId() === $comment_type->getId()
93
                ) {
94
                    $project_analyzer = $statements_analyzer->getProjectAnalyzer();
95
96
                    if ($codebase->alter_code
97
                        && isset($project_analyzer->getIssuesToFix()['UnnecessaryVarAnnotation'])
98
                    ) {
99
                        FileManipulationBuffer::addVarAnnotationToRemove($type_location);
100
                    } elseif (IssueBuffer::accepts(
101
                        new UnnecessaryVarAnnotation(
102
                            'The @var annotation for ' . $var_comment->var_id . ' is unnecessary',
103
                            $type_location
104
                        ),
105
                        [],
106
                        true
107
                    )) {
108
                        // fall through
109
                    }
110
                }
111
112
                $context->vars_in_scope[$var_comment->var_id] = $comment_type;
113
            }
114
        }
115
116
        if ($stmt->key) {
117
            $context->inside_call = true;
118
            if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->key, $context) === false) {
119
                return false;
120
            }
121
            $context->inside_call = false;
122
        }
123
124
        if ($stmt->value) {
125
            $context->inside_call = true;
126
            if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->value, $context) === false) {
127
                return false;
128
            }
129
            $context->inside_call = false;
130
131
            if ($var_comment_type) {
132
                $expression_type = clone $var_comment_type;
133
            } elseif ($stmt_var_type = $statements_analyzer->node_data->getType($stmt->value)) {
134
                $expression_type = clone $stmt_var_type;
135
            } else {
136
                $expression_type = Type::getMixed();
137
            }
138
        } else {
139
            $expression_type = Type::getEmpty();
140
        }
141
142
        $yield_type = null;
143
144
        foreach ($expression_type->getAtomicTypes() as $expression_atomic_type) {
145
            if ($expression_atomic_type instanceof Type\Atomic\TNamedObject) {
146
                $classlike_storage = $codebase->classlike_storage_provider->get($expression_atomic_type->value);
147
148
                if ($classlike_storage->yield) {
149
                    if ($expression_atomic_type instanceof Type\Atomic\TGenericObject) {
150
                        $yield_candidate_type = InstancePropertyFetchAnalyzer::localizePropertyType(
151
                            $codebase,
152
                            clone $classlike_storage->yield,
153
                            $expression_atomic_type,
154
                            $classlike_storage,
155
                            $classlike_storage
156
                        );
157
158
                        if ($yield_type) {
159
                            $yield_type = Type::combineUnionTypes(
160
                                $yield_type,
161
                                $yield_candidate_type,
162
                                $codebase
163
                            );
164
                        } else {
165
                            $yield_type = $yield_candidate_type;
166
                        }
167
                    } else {
168
                        $yield_type = Type::getMixed();
169
                    }
170
                }
171
            }
172
        }
173
174
        if ($yield_type) {
175
            $expression_type->substitute($expression_type, $yield_type);
176
        }
177
178
        $statements_analyzer->node_data->setType($stmt, $expression_type);
179
180
        $source = $statements_analyzer->getSource();
181
182
        if ($source instanceof FunctionLikeAnalyzer
183
            && !($source->getSource() instanceof TraitAnalyzer)
184
        ) {
185
            $source->examineParamTypes($statements_analyzer, $context, $codebase, $stmt);
186
187
            $storage = $source->getFunctionLikeStorage($statements_analyzer);
188
189
            if ($storage->return_type && !$yield_type) {
190
                foreach ($storage->return_type->getAtomicTypes() as $atomic_return_type) {
191
                    if ($atomic_return_type instanceof Type\Atomic\TNamedObject
192
                        && $atomic_return_type->value === 'Generator'
193
                    ) {
194
                        if ($atomic_return_type instanceof Type\Atomic\TGenericObject) {
195
                            if (!$atomic_return_type->type_params[2]->isVoid()) {
196
                                $statements_analyzer->node_data->setType(
197
                                    $stmt,
198
                                    Type::combineUnionTypes(
199
                                        clone $atomic_return_type->type_params[2],
200
                                        $expression_type,
201
                                        $codebase
202
                                    )
203
                                );
204
                            }
205
                        } else {
206
                            $statements_analyzer->node_data->setType(
207
                                $stmt,
208
                                Type::combineUnionTypes(
209
                                    Type::getMixed(),
210
                                    $expression_type
211
                                )
212
                            );
213
                        }
214
                    }
215
                }
216
            }
217
        }
218
219
        return true;
220
    }
221
}
222