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