1 /*
2     This file is part of KDevelop
3     SPDX-FileCopyrightText: 2008 Niko Sams <niko.sams@gmail.com>
4 
5     SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "typebuilder.h"
9 
10 #include <language/duchain/identifier.h>
11 #include <language/duchain/duchain.h>
12 #include <language/duchain/duchainlock.h>
13 #include <language/duchain/ducontext.h>
14 #include <language/duchain/declaration.h>
15 #include <language/duchain/types/integraltype.h>
16 #include "../declarations/classdeclaration.h"
17 #include "../types/integraltypeextended.h"
18 #include "../types/structuretype.h"
19 #include "../duchaindebug.h"
20 
21 #include "editorintegrator.h"
22 #include "parsesession.h"
23 #include "phpdebugvisitor.h"
24 #include "expressionparser.h"
25 #include "expressionvisitor.h"
26 #include "../declarations/classmethoddeclaration.h"
27 #include <language/duchain/types/unsuretype.h>
28 
29 using namespace KDevelop;
30 namespace Php
31 {
32 
TypeBuilder()33 TypeBuilder::TypeBuilder()
34     : TypeBuilderBase()
35     , m_gotTypeFromDocComment(false)
36     , m_gotReturnTypeFromDocComment(false)
37 {
38 }
39 
~TypeBuilder()40 TypeBuilder::~TypeBuilder()
41 {
42 }
43 
parseType(QString type,AstNode * node)44 AbstractType::Ptr TypeBuilder::parseType(QString type, AstNode* node)
45 {
46     uint iType = 0;
47     type = type.trimmed();
48     if (!type.compare(QLatin1String("int"), Qt::CaseInsensitive) || !type.compare(QLatin1String("integer"), Qt::CaseInsensitive)) {
49         iType = IntegralType::TypeInt;
50     } else if (!type.compare(QLatin1String("float"), Qt::CaseInsensitive) || !type.compare(QLatin1String("double"), Qt::CaseInsensitive)) {
51         iType = IntegralType::TypeFloat;
52     } else if (!type.compare(QLatin1String("bool"), Qt::CaseInsensitive) || !type.compare(QLatin1String("boolean"), Qt::CaseInsensitive)
53             || !type.compare(QLatin1String("false"), Qt::CaseInsensitive) || !type.compare(QLatin1String("true"), Qt::CaseInsensitive)) {
54         iType = IntegralType::TypeBoolean;
55     } else if (!type.compare(QLatin1String("string"), Qt::CaseInsensitive)) {
56         iType = IntegralType::TypeString;
57     } else if (!type.compare(QLatin1String("mixed"), Qt::CaseInsensitive)) {
58         iType = IntegralType::TypeMixed;
59     } else if (!type.compare(QLatin1String("array"), Qt::CaseInsensitive)) {
60         iType = IntegralType::TypeArray;
61     } else if (!type.compare(QLatin1String("resource"), Qt::CaseInsensitive)) {
62         return AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeResource));
63     } else if (!type.compare(QLatin1String("null"), Qt::CaseInsensitive)) {
64         iType = IntegralType::TypeNull;
65     } else if (!type.compare(QLatin1String("void"), Qt::CaseInsensitive)) {
66         iType = IntegralType::TypeVoid;
67     } else if (!type.compare(QLatin1String("self"), Qt::CaseInsensitive)
68             || !type.compare(QLatin1String("this"), Qt::CaseInsensitive) || !type.compare(QLatin1String("static"), Qt::CaseInsensitive)) {
69         DUChainReadLocker lock(DUChain::lock());
70         if ( currentContext()->type() == DUContext::Class && currentContext()->owner() ) {
71             return currentContext()->owner()->abstractType();
72         }
73     } else {
74         if (!type.compare(QLatin1String("object"), Qt::CaseInsensitive)) {
75             type = QStringLiteral("stdclass");
76         }
77         //don't use openTypeFromName as it uses cursor for findDeclarations
78         DeclarationPointer decl = findDeclarationImport(ClassDeclarationType,
79                                                         QualifiedIdentifier(type.toLower()));
80         if (decl && decl->abstractType()) {
81             return decl->abstractType();
82         }
83         if (type.contains('|')) {
84             QList<AbstractType::Ptr> types;
85             foreach (const QString& t, type.split('|')) {
86                 AbstractType::Ptr subType = parseType(t, node);
87                 if (!(IntegralType::Ptr::dynamicCast(subType) && IntegralType::Ptr::staticCast(subType)->dataType() == IntegralType::TypeMixed)) {
88                     types << parseType(t, node);
89                 }
90             }
91             if (!type.isEmpty()) {
92                 UnsureType::Ptr ret(new UnsureType());
93                 foreach (const AbstractType::Ptr& t, types) {
94                     ret->addType(t->indexed());
95                 }
96                 //qCDebug(DUCHAIN) << type << ret->toString();
97                 return AbstractType::Ptr::staticCast(ret);
98             }
99         }
100         iType = IntegralType::TypeMixed;
101     }
102     AbstractType::Ptr ret(new IntegralType(iType));
103     //qCDebug(DUCHAIN) << type << ret->toString();
104     return ret;
105 }
106 
injectParseType(QString type,AstNode * node)107 AbstractType::Ptr TypeBuilder::injectParseType(QString type, AstNode* node)
108 {
109     AbstractType::Ptr ret = parseType(type, node);
110     injectType(ret);
111     //qCDebug(DUCHAIN) << type << ret->toString();
112     return ret;
113 }
114 
115 /**
116  * Find all (or only one - see @p docCommentName) values for a given needle
117  * in a doc-comment. Needle has to start a line in the doccomment,
118  * i.e.:
119  *
120  *  * @docCommentName value
121  *
122  * or
123  *
124  *  /// @docCommentName value
125  */
findInDocComment(const QString & docComment,const QString & docCommentName,const bool onlyOne)126 QStringList findInDocComment(const QString &docComment, const QString &docCommentName, const bool onlyOne)
127 {
128     QStringList matches;
129     // optimization that does not require potentially slow regexps
130     // old code was something like this:
131     /*
132     if (!docComment.isEmpty()) {
133         QRegExp rx("\\*\\s+@param\\s([^\\s]*)");
134         int pos = 0;
135         while ((pos = rx.indexIn(docComment, pos)) != -1) {
136             ret << parseType(rx.cap(1), node);
137             pos += rx.matchedLength();
138         }
139     }
140     */
141 
142     for ( int i = 0, size = docComment.size(); i < size; ++i ) {
143         if ( docComment[i].isSpace() || docComment[i] == '*' || docComment[i] == '/' ) {
144             // skip whitespace and comment-marker at beginning of line
145             continue;
146         } else if ( docComment[i] == '@' && docComment.midRef(i + 1, docCommentName.size()) == docCommentName ) {
147             // find @return or similar
148             i += docCommentName.size() + 1;
149             // skip whitespace (at least one is required)
150             if ( i >= size || !docComment[i].isSpace() ) {
151                 // skip to next line
152                 i = docComment.indexOf('\n', i);
153                 if ( i == -1 ) {
154                     break;
155                 }
156                 continue;
157             } else if ( docComment[i] == '\n' ) {
158                 continue;
159             }
160             ++i; // at least one whitespace
161             while ( i < size && docComment[i].isSpace() ) {
162                 ++i;
163             }
164             // finally get the typename
165             int pos = i;
166             while ( pos < size && !docComment[pos].isSpace() ) {
167                 ++pos;
168             }
169             if ( pos > i ) {
170                 matches << docComment.mid(i, pos - i);
171                 if ( onlyOne ) {
172                     break;
173                 } else {
174                     i = pos;
175                 }
176             }
177         }
178         // skip to next line
179         i = docComment.indexOf('\n', i);
180         if ( i == -1 ) {
181             break;
182         }
183     }
184 
185     return matches;
186 }
187 
parseDocComment(AstNode * node,const QString & docCommentName)188 AbstractType::Ptr TypeBuilder::parseDocComment(AstNode* node, const QString& docCommentName)
189 {
190     m_gotTypeFromDocComment = false;
191     const QString& docComment = editor()->parseSession()->docComment(node->startToken);
192 
193     if ( !docComment.isEmpty() ) {
194         const QStringList& matches = findInDocComment(docComment, docCommentName, true);
195         if ( !matches.isEmpty() ) {
196             AbstractType::Ptr type;
197             if (matches.first() == QLatin1String("$this")) {
198                 DUChainReadLocker lock(DUChain::lock());
199                 if (currentContext()->owner()) {
200                     type = currentContext()->owner()->abstractType();
201                 }
202             } else {
203                 type = injectParseType(matches.first(), node);
204             }
205             if (type) {
206                 m_gotTypeFromDocComment = true;
207             }
208             return type;
209         }
210     }
211     return AbstractType::Ptr();
212 }
213 
parseDocCommentParams(AstNode * node)214 QList<AbstractType::Ptr> TypeBuilder::parseDocCommentParams(AstNode* node)
215 {
216     QList<AbstractType::Ptr> ret;
217     QString docComment = editor()->parseSession()->docComment(node->startToken);
218     if ( !docComment.isEmpty() ) {
219         const QStringList& matches = findInDocComment(docComment, QStringLiteral("param"), false);
220         if ( !matches.isEmpty() ) {
221             ret.reserve(matches.size());
222             foreach ( const QString& type, matches ) {
223                 ret << parseType(type, node);
224             }
225         }
226     }
227     return ret;
228 }
229 
getTypeForNode(AstNode * node)230 AbstractType::Ptr TypeBuilder::getTypeForNode(AstNode* node)
231 {
232 
233     AbstractType::Ptr type;
234     if (node) {
235         type = parseDocComment(node, QStringLiteral("var")); //we fully trust in @var typehint and don't try to evaluate ourself
236         if (!type) {
237             node->ducontext = currentContext();
238             ExpressionParser ep;
239             ep.setCreateProblems(true);
240             ExpressionEvaluationResult res = ep.evaluateType(node, editor());
241             if (res.hadUnresolvedIdentifiers()) {
242                 m_hadUnresolvedIdentifiers = true;
243             }
244             type = res.type();
245         }
246     }
247     if (!type) {
248         type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed));
249     }
250     return type;
251 }
252 
openFunctionType(AstNode * node)253 FunctionType::Ptr TypeBuilder::openFunctionType(AstNode* node)
254 {
255     FunctionType::Ptr functionType = FunctionType::Ptr(new FunctionType());
256 
257     openType(functionType);
258 
259     functionType->setReturnType(parseDocComment(node, QStringLiteral("return")));
260     m_gotReturnTypeFromDocComment = functionType->returnType();
261     updateCurrentType();
262 
263     return functionType;
264 }
265 
visitClassDeclarationStatement(ClassDeclarationStatementAst * node)266 void TypeBuilder::visitClassDeclarationStatement(ClassDeclarationStatementAst* node)
267 {
268     // the predeclaration builder should have set up a type already
269     // and the declarationbuilder should have set that as current type
270     Q_ASSERT(hasCurrentType() && currentType<StructureType>());
271 
272     TypeBuilderBase::visitClassDeclarationStatement(node);
273 }
274 
visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst * node)275 void TypeBuilder::visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst* node)
276 {
277     // the predeclaration builder should have set up a type already
278     // and the declarationbuilder should have set that as current type
279     Q_ASSERT(hasCurrentType() && currentType<StructureType>());
280 
281     TypeBuilderBase::visitInterfaceDeclarationStatement(node);
282 }
283 
visitTraitDeclarationStatement(TraitDeclarationStatementAst * node)284 void TypeBuilder::visitTraitDeclarationStatement(TraitDeclarationStatementAst* node)
285 {
286     // the predeclaration builder should have set up a type already
287     // and the declarationbuilder should have set that as current type
288     Q_ASSERT(hasCurrentType() && currentType<StructureType>());
289 
290     TypeBuilderBase::visitTraitDeclarationStatement(node);
291 }
292 
visitClassStatement(ClassStatementAst * node)293 void TypeBuilder::visitClassStatement(ClassStatementAst *node)
294 {
295     if (node->methodName) {
296         //method declaration
297         m_currentFunctionParams = parseDocCommentParams(node);
298         openFunctionType(node);
299         TypeBuilderBase::visitClassStatement(node);
300         if (currentType<FunctionType>() && !currentType<FunctionType>()->returnType()) {
301             currentType<FunctionType>()->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid)));
302         }
303         closeType();
304     } else {
305         //member-variable
306         parseDocComment(node, QStringLiteral("var"));
307         TypeBuilderBase::visitClassStatement(node);
308         if (m_gotTypeFromDocComment) {
309             clearLastType();
310             m_gotTypeFromDocComment = false;
311         }
312     }
313 }
314 
visitClassVariable(ClassVariableAst * node)315 void TypeBuilder::visitClassVariable(ClassVariableAst *node)
316 {
317     if (!m_gotTypeFromDocComment) {
318         openAbstractType(getTypeForNode(node->value));
319 
320         TypeBuilderBase::visitClassVariable(node);
321 
322         closeType();
323     } else {
324         TypeBuilderBase::visitClassVariable(node);
325     }
326 }
327 
visitConstantDeclaration(ConstantDeclarationAst * node)328 void TypeBuilder::visitConstantDeclaration(ConstantDeclarationAst* node)
329 {
330     if (!m_gotTypeFromDocComment || !currentAbstractType()) {
331         AbstractType::Ptr type = getTypeForNode(node->scalar);
332         type->setModifiers(type->modifiers() | AbstractType::ConstModifier);
333         openAbstractType(type);
334 
335         TypeBuilderBase::visitConstantDeclaration(node);
336 
337         closeType();
338     } else {
339         currentAbstractType()->setModifiers(currentAbstractType()->modifiers() & AbstractType::ConstModifier);
340         TypeBuilderBase::visitConstantDeclaration(node);
341     }
342 }
343 
visitParameter(ParameterAst * node)344 void TypeBuilder::visitParameter(ParameterAst *node)
345 {
346     AbstractType::Ptr type;
347     if (node->parameterType) {
348         //don't use openTypeFromName as it uses cursor for findDeclarations
349         DeclarationPointer decl = findDeclarationImport(ClassDeclarationType,
350                                                   identifierForNamespace(node->parameterType, editor()));
351         if (decl) {
352             type = decl->abstractType();
353         }
354     } else if (node->arrayType != -1) {
355         type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray));
356     } else if (node->defaultValue) {
357         ExpressionVisitor v(editor());
358         node->defaultValue->ducontext = currentContext();
359         v.visitNode(node->defaultValue);
360         type = v.result().type();
361     }
362     if (!type) {
363         if (m_currentFunctionParams.count() > currentType<FunctionType>()->arguments().count()) {
364             type = m_currentFunctionParams.at(currentType<FunctionType>()->arguments().count());
365         } else {
366             type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed));
367         }
368     }
369 
370     if ( node->isRef != -1 ) {
371       ReferenceType::Ptr p( new ReferenceType() );
372       p->setBaseType( type );
373 
374       type = p.cast<AbstractType>();
375     }
376 
377     openAbstractType(type);
378     TypeBuilderBase::visitParameter(node);
379     closeType();
380     DUChainWriteLocker lock(DUChain::lock());
381     currentType<FunctionType>()->addArgument(type);
382 }
383 
visitFunctionDeclarationStatement(FunctionDeclarationStatementAst * node)384 void TypeBuilder::visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node)
385 {
386     m_currentFunctionParams = parseDocCommentParams(node);
387     // the predeclarationbuilder should have already built the type
388     // and the declarationbuilder should have set it to open
389     Q_ASSERT(hasCurrentType());
390     FunctionType::Ptr type = currentType<FunctionType>();
391     Q_ASSERT(type);
392 
393     type->setReturnType(parseDocComment(node, QStringLiteral("return")));
394     m_gotReturnTypeFromDocComment = type->returnType();
395 
396     updateCurrentType();
397 
398     TypeBuilderBase::visitFunctionDeclarationStatement(node);
399 
400     if (!type->returnType()) {
401         type->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid)));
402     }
403 }
404 
visitClosure(ClosureAst * node)405 void TypeBuilder::visitClosure(ClosureAst* node)
406 {
407     m_currentFunctionParams = parseDocCommentParams(node);
408     FunctionType::Ptr type = FunctionType::Ptr(new FunctionType());
409     openType(type);
410 
411     type->setReturnType(parseDocComment(node, QStringLiteral("return")));
412     m_gotReturnTypeFromDocComment = type->returnType();
413 
414     updateCurrentType();
415 
416     TypeBuilderBase::visitClosure(node);
417 
418     if (!type->returnType()) {
419         type->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid)));
420     }
421     closeType();
422 }
423 
visitAssignmentExpression(AssignmentExpressionAst * node)424 void TypeBuilder::visitAssignmentExpression(AssignmentExpressionAst* node)
425 {
426     // performance: only try to find type when we are actually in an assignment expr
427     if (node->assignmentExpression || node->assignmentExpressionEqual) {
428         openAbstractType(getTypeForNode(node));
429     }
430 
431     TypeBuilderBase::visitAssignmentExpression(node);
432 
433     if (node->assignmentExpression || node->assignmentExpressionEqual) {
434         closeType();
435     }
436 }
437 
visitStaticVar(StaticVarAst * node)438 void TypeBuilder::visitStaticVar(StaticVarAst *node)
439 {
440     openAbstractType(getTypeForNode(node->value));
441 
442     TypeBuilderBase::visitStaticVar(node);
443 
444     closeType();
445 }
446 
visitStatement(StatementAst * node)447 void TypeBuilder::visitStatement(StatementAst* node)
448 {
449     TypeBuilderBase::visitStatement(node);
450     if ( !m_gotReturnTypeFromDocComment && node->returnExpr && hasCurrentType() && currentType<FunctionType>())
451     {
452         FunctionType::Ptr ft = currentType<FunctionType>();
453         // qCDebug(DUCHAIN) << "return" << (ft->returnType() ? ft->returnType()->toString() : "none") << lastType()->toString();
454         AbstractType::Ptr type = getTypeForNode(node->returnExpr);
455         if (type) {
456             // ignore references for return values, PHP does so as well
457             if ( ReferenceType::Ptr rType = ReferenceType::Ptr::dynamicCast(type) ) {
458                 type = rType->baseType();
459             }
460             if (ft->returnType() && !ft->returnType()->equals(type.data())) {
461                 if (ft->returnType().cast<IntegralType>()
462                     && ft->returnType().cast<IntegralType>()->dataType() == IntegralType::TypeMixed)
463                 {
464                     //don't add TypeMixed to the list, just ignore
465                     ft->setReturnType(type);
466                 } else {
467                     UnsureType::Ptr retT;
468                     if (ft->returnType().cast<UnsureType>()) {
469                         //qCDebug(DUCHAIN) << "we already have an unsure type";
470                         retT = ft->returnType().cast<UnsureType>();
471                         if (type.cast<UnsureType>()) {
472                             //qCDebug(DUCHAIN) << "add multiple to returnType";
473                             FOREACH_FUNCTION(const IndexedType& t, type.cast<UnsureType>()->types) {
474                                 retT->addType(t);
475                             }
476                         } else {
477                             //qCDebug(DUCHAIN) << "add to returnType";
478                             retT->addType(type->indexed());
479                         }
480                     } else {
481                         if (type.cast<UnsureType>()) {
482                             retT = type.cast<UnsureType>();
483                         } else {
484                             retT = new UnsureType();
485                             retT->addType(type->indexed());
486                         }
487                         retT->addType(ft->returnType()->indexed());
488                     }
489                     ft->setReturnType(AbstractType::Ptr::staticCast(retT));
490                 }
491             } else {
492                 ft->setReturnType(type);
493             }
494             updateCurrentType();
495         }
496     }
497 
498     AstNode *foreachNode = 0;
499     if (node->foreachVar) {
500         foreachNode = node->foreachVar;
501     } else if (node->foreachExpr) {
502         foreachNode = node->foreachExpr;
503     } else if (node->foreachExprAsVar) {
504         foreachNode = node->foreachExprAsVar;
505     }
506     if (foreachNode) {
507         ExpressionVisitor v(editor());
508         foreachNode->ducontext = currentContext();
509         v.visitNode(foreachNode);
510         DUChainReadLocker lock(DUChain::lock());
511         bool foundType = false;
512         if (StructureType::Ptr type = StructureType::Ptr::dynamicCast(v.result().type())) {
513             ClassDeclaration *classDec = dynamic_cast<ClassDeclaration*>(type->declaration(currentContext()->topContext()));
514             if (!classDec) {
515                 ///FIXME: this is just a hack for https://bugs.kde.org/show_bug.cgi?id=269369
516                 ///       a proper fix needs full fledged two-pass, i.e. get rid of PreDeclarationBuilder
517                 // 0 == global lookup and the declaration is found again...
518                 classDec = dynamic_cast<ClassDeclaration*>(type->declaration(0));
519             }
520             if (classDec) {
521                 /// Qualified identifier for 'iterator'
522                 static const QualifiedIdentifier iteratorQId(QStringLiteral("iterator"));
523                 ClassDeclaration* iteratorDecl = dynamic_cast<ClassDeclaration*>(
524                     findDeclarationImport(ClassDeclarationType, iteratorQId).data()
525                 );
526                 Q_ASSERT(iteratorDecl);
527                 if (classDec->isPublicBaseClass(iteratorDecl, currentContext()->topContext())) {
528                     /// Qualified identifier for 'current'
529                     static const QualifiedIdentifier currentQId(QStringLiteral("current"));
530                     auto classContext = classDec->internalContext();
531                     if (classContext) {
532                         foreach (Declaration *d, classContext->findDeclarations(currentQId)) {
533                             if (!dynamic_cast<ClassMethodDeclaration*>(d)) continue;
534                             Q_ASSERT(d->type<FunctionType>());
535                             injectType(d->type<FunctionType>()->returnType());
536                             foundType = true;
537                             // qCDebug(DUCHAIN) << "that's it: " << d->type<FunctionType>()->returnType()->toString();
538                         }
539                     }
540                 }
541             }
542         }
543         if (!foundType) {
544             injectType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)));
545         }
546     }
547 }
548 
visitCatchItem(Php::CatchItemAst * node)549 void TypeBuilder::visitCatchItem(Php::CatchItemAst *node)
550 {
551     TypeBuilderBase::visitCatchItem(node);
552     DeclarationPointer dec = findDeclarationImport(ClassDeclarationType,
553                                                    identifierForNamespace(node->catchClass, m_editor));
554     if (dec && dec->abstractType()) {
555         openAbstractType(dec->abstractType());
556         closeType();
557     }
558 
559 }
560 
updateCurrentType()561 void TypeBuilder::updateCurrentType()
562 {
563     // do nothing
564 }
565 
566 }
567 
568