adapt to kdevplatform changes, use DeclarationPointer when the duchain might get...
[kdevelop:php.git] / duchain / tests / expressionparser.cpp
1 /* This file is part of KDevelop
2     Copyright 2008 Niko Sams <niko.sams@gmail.com>
3
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License version 2 as published by the Free Software Foundation.
7
8    This library is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11    Library General Public License for more details.
12
13    You should have received a copy of the GNU Library General Public License
14    along with this library; see the file COPYING.LIB.  If not, write to
15    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16    Boston, MA 02110-1301, USA.
17 */
18
19 #include "expressionparser.h"
20
21 #include <QtTest/QtTest>
22
23 #include <language/duchain/parsingenvironment.h>
24 #include <language/duchain/duchain.h>
25 #include <language/duchain/duchainlock.h>
26 #include <language/duchain/topducontext.h>
27 #include <language/duchain/types/functiontype.h>
28 #include <language/duchain/types/integraltype.h>
29 #include <language/duchain/declaration.h>
30
31 #include "../types/structuretype.h"
32 #include "../expressionparser.h"
33
34 using namespace KDevelop;
35
36 QTEST_MAIN(Php::TestExpressionParser)
37
38 namespace Php
39 {
40
41 TestExpressionParser::TestExpressionParser()
42 {
43 }
44
45
46 void TestExpressionParser::newClass()
47 {
48     //                 0         1         2         3         4         5         6         7
49     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
50     QByteArray method("<? class A { function foo() {} } $i = new A();");
51
52     TopDUContext* top = parse(method, DumpNone);
53     DUChainReleaser releaseTop(top);
54     DUChainWriteLocker lock(DUChain::lock());
55
56     ExpressionParser p(true);
57     ExpressionEvaluationResult res = p.evaluateType(QByteArray("$i"), DUContextPointer(top), CursorInRevision(1, 0));
58     QVERIFY(res.type());
59     QCOMPARE(StructureType::Ptr::staticCast(res.type())->qualifiedIdentifier(), QualifiedIdentifier("a"));
60 }
61
62 void TestExpressionParser::newSelf()
63 {
64     //                 0         1         2         3         4         5         6         7
65     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
66     QByteArray method("<? class A { function self() {  } }");
67
68     TopDUContext* top = parse(method, DumpNone);
69     DUChainReleaser releaseTop(top);
70     DUChainWriteLocker lock(DUChain::lock());
71
72     ExpressionParser p(true);
73     ExpressionEvaluationResult res = p.evaluateType( QByteArray("new self()"),
74                                         DUContextPointer(top->childContexts().first()->childContexts().last()),
75                                         CursorInRevision(0, 30));
76     QVERIFY(res.type());
77     QCOMPARE(StructureType::Ptr::staticCast(res.type())->qualifiedIdentifier(), QualifiedIdentifier("a"));
78 }
79
80 void TestExpressionParser::memberVariable()
81 {
82     //                 0         1         2         3         4         5         6         7
83     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
84     QByteArray method("<? class A { /** @var A **/ public $foo; } $i = new A();");
85
86     TopDUContext* top = parse(method, DumpNone);
87     DUChainReleaser releaseTop(top);
88     DUChainWriteLocker lock(DUChain::lock());
89
90     ExpressionParser p(true);
91     ExpressionEvaluationResult res = p.evaluateType(QByteArray("$i->foo"), DUContextPointer(top), CursorInRevision(1, 0));
92     QVERIFY(res.type());
93     QCOMPARE(res.allDeclarations().count(), 1);
94     QCOMPARE(res.allDeclarations().first().data(), top->childContexts().first()->localDeclarations().first());
95     QCOMPARE(StructureType::Ptr::staticCast(res.type())->qualifiedIdentifier(), QualifiedIdentifier("a"));
96 }
97 void TestExpressionParser::memberFunction()
98 {
99     //                 0         1         2         3         4         5         6         7
100     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
101     QByteArray method("<? class A { public function foo() {} } $i = new A();");
102
103     TopDUContext* top = parse(method, DumpAll);
104     DUChainReleaser releaseTop(top);
105     DUChainWriteLocker lock(DUChain::lock());
106
107     ExpressionParser p(true);
108     ExpressionEvaluationResult res = p.evaluateType(QByteArray("$i->foo()"), DUContextPointer(top), CursorInRevision(1, 0));
109     QVERIFY(res.type());
110     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
111     QVERIFY(IntegralType::Ptr::dynamicCast(res.type())->dataType() == IntegralType::TypeVoid);
112     QCOMPARE(res.allDeclarations().size(), 1);
113     QCOMPARE(res.allDeclarations().first().data(), top->childContexts().first()->localDeclarations().first());
114 }
115 void TestExpressionParser::globalFunction()
116 {
117     //                 0         1         2         3         4         5         6         7
118     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
119     QByteArray method("<? function foo() {}");
120
121     TopDUContext* top = parse(method, DumpNone);
122     DUChainReleaser releaseTop(top);
123     DUChainWriteLocker lock(DUChain::lock());
124
125     ExpressionParser p(true);
126     ExpressionEvaluationResult res = p.evaluateType(QByteArray("foo"), DUContextPointer(top), CursorInRevision(1, 0));
127     QVERIFY(res.type());
128     QVERIFY(FunctionType::Ptr::dynamicCast(res.type()));
129     QCOMPARE(res.allDeclarations().count(), 1);
130     QCOMPARE(res.allDeclarations().first().data(), top->localDeclarations().first());
131 }
132
133 void TestExpressionParser::chainCall()
134 {
135     //                 0         1         2         3         4         5         6         7
136     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
137     QByteArray method("<? class A { function foo() { return $this; } } $a = new A();");
138
139     TopDUContext* top = parse(method, DumpAll);
140     DUChainReleaser releaseTop(top);
141     DUChainWriteLocker lock(DUChain::lock());
142
143     FunctionType::Ptr fn = top->childContexts().first()->localDeclarations().first()->type<FunctionType>();
144     QVERIFY(fn);
145     QVERIFY(fn->returnType()->equals(top->localDeclarations().first()->abstractType().unsafeData()));
146
147     ExpressionParser p(true);
148     ExpressionEvaluationResult res = p.evaluateType(QByteArray("$a->foo()"), DUContextPointer(top), CursorInRevision(1, 0));
149     QVERIFY(res.type());
150     QVERIFY(res.type()->equals(top->localDeclarations().first()->abstractType().unsafeData()));
151
152     res = p.evaluateType(QByteArray("$a->foo()->foo()->foo()"), DUContextPointer(top), CursorInRevision(1, 0));
153     QVERIFY(res.type());
154     QVERIFY(res.type()->equals(top->localDeclarations().first()->abstractType().unsafeData()));
155 }
156 void TestExpressionParser::thisObject()
157 {
158     //                 0         1         2         3         4         5         6         7
159     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
160     QByteArray method("<? class A { public function foo() {} }");
161
162     TopDUContext* top = parse(method, DumpNone);
163     DUChainReleaser releaseTop(top);
164     DUChainWriteLocker lock(DUChain::lock());
165
166     DUContext* funContext = top->childContexts().first()->localDeclarations().first()->internalContext();
167     ExpressionParser p(true);
168     ExpressionEvaluationResult res = p.evaluateType(QByteArray("$this"), DUContextPointer(funContext), CursorInRevision(1, 0));
169     QCOMPARE(res.allDeclarations().count(), 1);
170     QCOMPARE(res.allDeclarations().first().data(), top->localDeclarations().first());
171     QVERIFY(res.type());
172     QVERIFY(StructureType::Ptr::dynamicCast(res.type()));
173     QCOMPARE(StructureType::Ptr::dynamicCast(res.type())->declaration(top), top->localDeclarations().first());
174 }
175
176 void TestExpressionParser::integralTypes()
177 {
178     //                 0         1         2         3         4         5         6         7
179     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
180     QByteArray method("<? $foo=1;");
181
182     TopDUContext* top = parse(method, DumpNone);
183     DUChainReleaser releaseTop(top);
184     DUChainWriteLocker lock(DUChain::lock());
185
186     ExpressionParser p(true);
187
188     ExpressionEvaluationResult res = p.evaluateType(QByteArray("123"), DUContextPointer(top), CursorInRevision(1, 0));
189     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
190     QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast<uint>(IntegralType::TypeInt));
191
192     res = p.evaluateType(QByteArray("123.1"), DUContextPointer(top), CursorInRevision(1, 0));
193     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
194     QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast<uint>(IntegralType::TypeFloat));
195
196     res = p.evaluateType(QByteArray("\"asdf\""), DUContextPointer(top), CursorInRevision(1, 0));
197     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
198     QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast<uint>(IntegralType::TypeString));
199
200     res = p.evaluateType(QByteArray("\"as $foo df\""), DUContextPointer(top), CursorInRevision(1, 0));
201     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
202     QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast<uint>(IntegralType::TypeString));
203
204     res = p.evaluateType(QByteArray("'asdf'"), DUContextPointer(top), CursorInRevision(1, 0));
205     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
206     QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast<uint>(IntegralType::TypeString));
207
208     res = p.evaluateType(QByteArray("true"), DUContextPointer(top), CursorInRevision(1, 0));
209     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
210     QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast<uint>(IntegralType::TypeBoolean));
211
212     res = p.evaluateType(QByteArray("TRUE"), DUContextPointer(top), CursorInRevision(1, 0));
213     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
214     QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast<uint>(IntegralType::TypeBoolean));
215
216     res = p.evaluateType(QByteArray("null"), DUContextPointer(top), CursorInRevision(1, 0));
217     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
218     QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast<uint>(IntegralType::TypeNull));
219
220     res = p.evaluateType(QByteArray("NULL"), DUContextPointer(top), CursorInRevision(1, 0));
221     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
222     QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast<uint>(IntegralType::TypeNull));
223 }
224
225 void TestExpressionParser::newObject()
226 {
227     //                 0         1         2         3         4         5         6         7
228     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
229     QByteArray method("<? class A {} ");
230
231     TopDUContext* top = parse(method, DumpNone);
232     DUChainReleaser releaseTop(top);
233     DUChainWriteLocker lock(DUChain::lock());
234
235     ExpressionParser p(true);
236
237     ExpressionEvaluationResult res = p.evaluateType(QByteArray("new A();"), DUContextPointer(top), CursorInRevision(1, 0));
238     QVERIFY(StructureType::Ptr::dynamicCast(res.type()));
239     QCOMPARE(StructureType::Ptr::staticCast(res.type())->declaration(top), top->localDeclarations().first());
240 }
241
242 void TestExpressionParser::cast()
243 {
244     //                 0         1         2         3         4         5         6         7
245     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
246     QByteArray method("<? $foo = 1; ");
247
248     TopDUContext* top = parse(method, DumpNone);
249     DUChainReleaser releaseTop(top);
250     DUChainWriteLocker lock(DUChain::lock());
251
252     ExpressionParser p(true);
253
254     ExpressionEvaluationResult res = p.evaluateType(QByteArray("(string)$foo"), DUContextPointer(top), CursorInRevision(1, 0));
255     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
256     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeString);
257
258     res = p.evaluateType(QByteArray("(int)$foo"), DUContextPointer(top), CursorInRevision(1, 0));
259     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
260     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt);
261
262     res = p.evaluateType(QByteArray("(double)$foo"), DUContextPointer(top), CursorInRevision(1, 0));
263     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
264     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeFloat);
265
266     res = p.evaluateType(QByteArray("(bool)$foo"), DUContextPointer(top), CursorInRevision(1, 0));
267     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
268     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeBoolean);
269
270     res = p.evaluateType(QByteArray("(array)$foo"), DUContextPointer(top), CursorInRevision(1, 0));
271     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
272     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeArray);
273
274     res = p.evaluateType(QByteArray("(object)$foo"), DUContextPointer(top), CursorInRevision(1, 0));
275     kDebug() << res.type();
276     kDebug() << res.type()->toString();
277     QVERIFY(StructureType::Ptr::dynamicCast(res.type()));
278     QVERIFY(StructureType::Ptr::staticCast(res.type())->qualifiedIdentifier() == QualifiedIdentifier("stdclass"));
279 }
280
281 void TestExpressionParser::operations()
282 {
283     //                 0         1         2         3         4         5         6         7
284     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
285     QByteArray method("<? $foo = 1; ");
286
287     TopDUContext* top = parse(method, DumpNone);
288     DUChainReleaser releaseTop(top);
289     DUChainWriteLocker lock(DUChain::lock());
290
291     ExpressionParser p(true);
292
293     ExpressionEvaluationResult res = p.evaluateType(QByteArray("'1' . '1'"), DUContextPointer(top), CursorInRevision(1, 0));
294     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
295     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeString);
296
297     res = p.evaluateType(QByteArray("1 . 1"), DUContextPointer(top), CursorInRevision(1, 0));
298     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
299     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeString);
300
301     res = p.evaluateType(QByteArray("1 + 1"), DUContextPointer(top), CursorInRevision(1, 0));
302     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
303     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt);
304
305     res = p.evaluateType(QByteArray("'1' + '1'"), DUContextPointer(top), CursorInRevision(1, 0));
306     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
307     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt);
308
309     res = p.evaluateType(QByteArray("$foo .= '1'"), DUContextPointer(top), CursorInRevision(1, 0));
310     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
311     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeString);
312
313     res = p.evaluateType(QByteArray("$foo .= 1"), DUContextPointer(top), CursorInRevision(1, 0));
314     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
315     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeString);
316
317     res = p.evaluateType(QByteArray("$foo += 1"), DUContextPointer(top), CursorInRevision(1, 0));
318     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
319     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt);
320
321     res = p.evaluateType(QByteArray("$foo += '1'"), DUContextPointer(top), CursorInRevision(1, 0));
322     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
323     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt);
324
325     res = p.evaluateType(QByteArray("$foo *= 1"), DUContextPointer(top), CursorInRevision(1, 0));
326     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
327     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt);
328
329     res = p.evaluateType(QByteArray("$foo *= '1'"), DUContextPointer(top), CursorInRevision(1, 0));
330     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
331     QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt);
332 }
333
334 void TestExpressionParser::findArg()
335 {
336     //                 0         1         2         3         4         5         6         7
337     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
338     QByteArray method("<? class A{} function foo($arg, &$bar, A &$a) {  } ");
339
340     TopDUContext* top = parse(method, DumpNone);
341     DUChainReleaser releaseTop(top);
342     DUChainWriteLocker lock(DUChain::lock());
343
344     ExpressionParser p(true);
345
346     QCOMPARE(top->childContexts().size(), 3);
347     QVERIFY(top->childContexts().at(0)->type() == DUContext::Class);
348     QVERIFY(top->childContexts().at(1)->type() == DUContext::Function);
349     QVERIFY(top->childContexts().at(2)->type() != DUContext::Function);
350
351     ExpressionEvaluationResult res = p.evaluateType(QByteArray("$arg"), DUContextPointer(top->childContexts().last()),
352                                                     CursorInRevision(0, 47));
353     QVERIFY(IntegralType::Ptr::dynamicCast(res.type()));
354     QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast<uint>(IntegralType::TypeMixed));
355
356     res = p.evaluateType(QByteArray("$bar"), DUContextPointer(top->childContexts().last()),
357                          CursorInRevision(0, 47));
358     ReferenceType::Ptr type = ReferenceType::Ptr::dynamicCast(res.type());
359     QVERIFY(type);
360     QVERIFY(IntegralType::Ptr::dynamicCast(type->baseType()));
361     QCOMPARE(IntegralType::Ptr::staticCast(type->baseType())->dataType(), static_cast<uint>(IntegralType::TypeMixed));
362
363     res = p.evaluateType(QByteArray("$a"), DUContextPointer(top->childContexts().last()),
364                          CursorInRevision(0, 47));
365     type = ReferenceType::Ptr::dynamicCast(res.type());
366     QVERIFY(type);
367     QVERIFY(StructureType::Ptr::dynamicCast(type->baseType()));
368     QCOMPARE(StructureType::Ptr::staticCast(type->baseType())->declaration(top), top->localDeclarations().first());
369 }
370
371 void TestExpressionParser::array()
372 {
373     // see bug https://bugs.kde.org/show_bug.cgi?id=237110
374
375     //                 0         1         2         3         4         5         6         7
376     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
377     QByteArray method("<? $a = array(\"foo\");");
378
379     TopDUContext* top = parse(method, DumpNone);
380     DUChainReleaser releaseTop(top);
381     DUChainWriteLocker lock;
382
383     ExpressionParser p(true);
384     QCOMPARE(top->localDeclarations().first()->abstractType().cast<IntegralType>()->dataType(), static_cast<uint>(IntegralType::TypeArray));
385
386     ExpressionEvaluationResult res = p.evaluateType("$b = $a[0]", DUContextPointer(top), CursorInRevision(0, 22));
387     QVERIFY(res.type().cast<IntegralType>());
388     QEXPECT_FAIL("", "we'd need advanced array support to know that [0] returns a string...", Continue);
389     QCOMPARE(res.type().cast<IntegralType>()->dataType(), static_cast<uint>(IntegralType::TypeString));
390     // fallback
391     QCOMPARE(res.type().cast<IntegralType>()->dataType(), static_cast<uint>(IntegralType::TypeMixed));
392 }
393
394 }
395
396 #include "expressionparser.moc"