1 /**
2 Copyright: Copyright (c) 2016-2017, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 Precise testing of the Type analyzer of the Clang AST.
7 */
8 module test.component.analyzer.type;
9 
10 import std.conv : to;
11 import std.format : format;
12 import std.typecons : scoped, Yes;
13 import std.variant : visit;
14 
15 import unit_threaded;
16 import test.clang_util;
17 import test.helpers;
18 
19 import cpptooling.data;
20 
21 import cpptooling.analyzer.clang.ast;
22 import cpptooling.analyzer.clang.analyze_helper;
23 import cpptooling.analyzer.clang.context : ClangContext;
24 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
25 import cpptooling.analyzer.clang.type;
26 import cpptooling.data.symbol : Container;
27 import cpptooling.data : TypeKindVariable, VariadicType, Location, USRType,
28     toStringDecl;
29 import cpptooling.utility.virtualfilesystem : FileName, Content;
30 
31 /* These lines are useful when debugging.
32 import unit_threaded;
33 writelnUt(visitor.container.toString);
34 */
35 
36 final class TestVisitor : Visitor {
37     import cpptooling.analyzer.clang.ast;
38 
39     alias visit = Visitor.visit;
40     mixin generateIndentIncrDecr;
41 
42     Container container;
43 
44     /// The USR to find.
45     USRType find;
46 
47     FunctionDeclResult[] funcs;
48     VarDeclResult[] vars;
49     bool found;
50 
51     override void visit(const(TranslationUnit) v) {
52         mixin(mixinNodeLog!());
53         v.accept(this);
54     }
55 
56     override void visit(const(Namespace) v) {
57         mixin(mixinNodeLog!());
58         v.accept(this);
59     }
60 
61     override void visit(const(UnexposedDecl) v) {
62         mixin(mixinNodeLog!());
63         v.accept(this);
64     }
65 
66     override void visit(const(VarDecl) v) {
67         mixin(mixinNodeLog!());
68         v.accept(this);
69 
70         auto tmp = analyzeVarDecl(v, container, indent);
71         if (this.find.length == 0 || v.cursor.usr == this.find) {
72             vars ~= tmp;
73             found = true;
74         }
75     }
76 
77     override void visit(const(FunctionDecl) v) {
78         mixin(mixinNodeLog!());
79 
80         auto tmp = analyzeFunctionDecl(v, container, indent);
81         if (this.find.length == 0 || v.cursor.usr == this.find) {
82             funcs ~= tmp;
83             found = true;
84         }
85     }
86 }
87 
88 final class TestRecordVisitor : Visitor {
89     import cpptooling.analyzer.clang.ast;
90 
91     alias visit = Visitor.visit;
92     mixin generateIndentIncrDecr;
93 
94     Container container;
95 
96     RecordResult record;
97 
98     override void visit(const(TranslationUnit) v) {
99         mixin(mixinNodeLog!());
100         v.accept(this);
101     }
102 
103     override void visit(const(Namespace) v) {
104         mixin(mixinNodeLog!());
105         v.accept(this);
106     }
107 
108     override void visit(const(ClassDecl) v) {
109         mixin(mixinNodeLog!());
110 
111         record = analyzeRecord(v, container, indent);
112         v.accept(this);
113     }
114 
115     override void visit(const(Constructor) v) {
116         mixin(mixinNodeLog!());
117 
118         analyzeConstructor(v, container, indent);
119     }
120 }
121 
122 final class TestDeclVisitor : Visitor {
123     import cpptooling.analyzer.clang.ast;
124 
125     alias visit = Visitor.visit;
126     mixin generateIndentIncrDecr;
127 
128     Container container;
129 
130     override void visit(const(TranslationUnit) v) {
131         mixin(mixinNodeLog!());
132         v.accept(this);
133     }
134 
135     override void visit(const(Declaration) v) {
136         mixin(mixinNodeLog!());
137         import cpptooling.analyzer.clang.store : put;
138 
139         auto type = () @trusted{
140             return retrieveType(v.cursor, container, indent);
141         }();
142         put(type, container, indent);
143         v.accept(this);
144     }
145 }
146 
147 final class TestFunctionBodyVisitor : Visitor {
148     import cpptooling.analyzer.clang.ast;
149 
150     alias visit = Visitor.visit;
151     mixin generateIndentIncrDecr;
152 
153     Container container;
154 
155     FunctionDeclResult[] funcs;
156 
157     override void visit(const(TranslationUnit) v) {
158         mixin(mixinNodeLog!());
159         v.accept(this);
160     }
161 
162     override void visit(const(Declaration) v) {
163         mixin(mixinNodeLog!());
164         v.accept(this);
165     }
166 
167     override void visit(const(Statement) v) {
168         mixin(mixinNodeLog!());
169         v.accept(this);
170     }
171 
172     override void visit(const(Expression) v) {
173         mixin(mixinNodeLog!());
174         v.accept(this);
175     }
176 
177     override void visit(const(DeclRefExpr) v) {
178         mixin(mixinNodeLog!());
179         import clang.Cursor : Cursor;
180 
181         Cursor ref_ = v.cursor.referenced;
182 
183         logNode(ref_, indent);
184 
185         import cpptooling.analyzer.clang.ast.tree : dispatch;
186 
187         dispatch!Visitor(ref_, this);
188     }
189 
190     override void visit(const(FunctionDecl) v) {
191         mixin(mixinNodeLog!());
192 
193         funcs ~= analyzeFunctionDecl(v, container, indent);
194         v.accept(this);
195     }
196 }
197 
198 final class TestUnionVisitor : Visitor {
199     import cpptooling.analyzer.clang.ast;
200 
201     alias visit = Visitor.visit;
202     mixin generateIndentIncrDecr;
203 
204     Container container;
205 
206     RecordResult[] records;
207 
208     override void visit(const(TranslationUnit) v) {
209         mixin(mixinNodeLog!());
210         v.accept(this);
211     }
212 
213     override void visit(const(Declaration) v) {
214         mixin(mixinNodeLog!());
215         v.accept(this);
216     }
217 
218     override void visit(const(Statement) v) {
219         mixin(mixinNodeLog!());
220         v.accept(this);
221     }
222 
223     override void visit(const(Expression) v) {
224         mixin(mixinNodeLog!());
225         v.accept(this);
226     }
227 
228     override void visit(const(UnionDecl) v) {
229         mixin(mixinNodeLog!());
230 
231         records ~= analyzeRecord(v, container, indent);
232     }
233 }
234 
235 final class ClassVisitor : Visitor {
236     import cpptooling.analyzer.clang.ast;
237 
238     alias visit = Visitor.visit;
239     mixin generateIndentIncrDecr;
240 
241     Container container;
242 
243     /// The USR to find.
244     USRType find;
245 
246     CxxMethodResult[] methods;
247     FunctionDeclResult[] funcs;
248     bool found;
249 
250     override void visit(const(TranslationUnit) v) {
251         mixin(mixinNodeLog!());
252         v.accept(this);
253     }
254 
255     override void visit(const(ClassDecl) v) {
256         mixin(mixinNodeLog!());
257         v.accept(this);
258     }
259 
260     override void visit(const(FunctionDecl) v) {
261         mixin(mixinNodeLog!());
262 
263         auto tmp = analyzeFunctionDecl(v, container, indent);
264         if (this.find.length == 0 || v.cursor.usr == this.find) {
265             funcs ~= tmp;
266             found = true;
267         }
268     }
269 
270     override void visit(const(CxxMethod) v) @trusted {
271         mixin(mixinNodeLog!());
272 
273         auto tmp = analyzeCxxMethod(v, container, indent);
274         if (this.find.length == 0 || v.cursor.usr == this.find) {
275             methods ~= tmp;
276             found = true;
277         }
278 
279         v.accept(this);
280     }
281 }
282 
283 version (linux) {
284     @("Should be a type of kind 'func'")
285     unittest {
286         enum code = `
287         #include <clocale>
288 
289         namespace dextool__gnu_cxx {
290         extern "C" __typeof(uselocale) __uselocale;
291         }
292         `;
293 
294         // arrange
295         auto visitor = new TestVisitor;
296         visitor.find = "c:@F@__uselocale";
297 
298         auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
299         ctx.virtualFileSystem.openAndWrite(cast(FileName) "issue.hpp", cast(Content) code);
300         auto tu = ctx.makeTranslationUnit("issue.hpp");
301 
302         // act
303         auto ast = ClangAST!(typeof(visitor))(tu.cursor);
304         ast.accept(visitor);
305 
306         // assert
307         checkForCompilerErrors(tu).shouldBeFalse;
308         visitor.found.shouldBeTrue;
309         visitor.funcs[0].type.kind.info.kind.shouldEqual(TypeKind.Info.Kind.func);
310         (cast(string) visitor.funcs[0].name).shouldEqual("__uselocale");
311     }
312 }
313 
314 @("Should be parameters and return type that are of primitive type")
315 // dfmt off
316 @Values("int",
317         "signed int",
318         "unsigned int",
319         "unsigned",
320         "char",
321         "signed char",
322         "unsigned char",
323         "short",
324         "signed short",
325         "unsigned short",
326         "long",
327         "signed long",
328         "unsigned long",
329         "long long",
330         "signed long long",
331         "unsigned long long",
332         "float",
333         "double",
334         "long double",
335         "wchar_t",
336         "bool",
337         )
338 @Tags("slow") // execution time is >500ms
339 // dfmt on
340 unittest {
341     enum code = "%s fun(%s);";
342 
343     // arrange
344     auto visitor = new TestVisitor;
345     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
346     ctx.virtualFileSystem.openAndWrite(cast(FileName) "issue.hpp",
347             cast(Content) format(code, getValue!string, getValue!string));
348     auto tu = ctx.makeTranslationUnit("issue.hpp");
349 
350     // act
351     auto ast = ClangAST!(typeof(visitor))(tu.cursor);
352     ast.accept(visitor);
353 
354     // assert
355     checkForCompilerErrors(tu).shouldBeFalse;
356     visitor.found.shouldBeTrue;
357     visitor.funcs[0].type.kind.info.kind.shouldEqual(TypeKind.Info.Kind.func);
358     (cast(string) visitor.funcs[0].name).shouldEqual("fun");
359 
360     foreach (param; visitor.funcs[0].params) {
361         TypeKindAttr type;
362         // dfmt off
363         param.visit!(
364                      (TypeKindVariable v) => type = v.type,
365                      (TypeKindAttr v) => type = v,
366                      (VariadicType v) => type = type);
367         // dfmt on
368 
369         type.kind.info.kind.shouldEqual(TypeKind.Info.Kind.primitive);
370     }
371 
372     // do not try and verify the string representation of the type.
373     // It may be platform and compiler specific.
374     // For example is signed char -> char.
375     visitor.funcs[0].returnType.kind.info.kind.shouldEqual(TypeKind.Info.Kind.primitive);
376 }
377 
378 @("Should be the USR of the function declaration not the typedef signature")
379 unittest {
380     import cpptooling.data.type : LocationTag;
381 
382     enum code = "
383 typedef void (gun_type)(int);
384 
385 // using a typedef signature to create a function
386 extern gun_type gun_func;
387 ";
388 
389     // arrange
390     auto visitor = new TestVisitor;
391     visitor.find = "c:@F@gun_func#I#";
392 
393     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
394     ctx.virtualFileSystem.openAndWrite(cast(FileName) "issue.hpp", cast(Content) code);
395     auto tu = ctx.makeTranslationUnit("issue.hpp");
396 
397     // act
398     auto ast = ClangAST!(typeof(visitor))(tu.cursor);
399     ast.accept(visitor);
400 
401     // assert
402     checkForCompilerErrors(tu).shouldBeFalse;
403     visitor.found.shouldBeTrue;
404 
405     auto loc_result = visitor.container.find!LocationTag(visitor.funcs[0].type.kind.usr).front.any;
406     loc_result.length.shouldEqual(1);
407 
408     auto loc = loc_result.front;
409     loc.kind.shouldEqual(LocationTag.Kind.loc);
410     // line 5 is the declaration of gun_func
411     loc.line.shouldEqual(5);
412 }
413 
414 @("Should be two pointers with the same type signature but different USRs")
415 unittest {
416     enum code = "
417 int* p0;
418 int* p1;
419 ";
420 
421     // arrange
422     auto visitor = new TestVisitor;
423     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
424     ctx.virtualFileSystem.openAndWrite(cast(FileName) "issue.hpp", cast(Content) code);
425     auto tu = ctx.makeTranslationUnit("issue.hpp");
426 
427     // act
428     auto ast = ClangAST!(typeof(visitor))(tu.cursor);
429     ast.accept(visitor);
430 
431     // assert
432     visitor.vars.length.shouldEqual(2);
433     visitor.vars[0].type.kind.usr.shouldNotEqual(visitor.vars[1].type.kind.usr);
434 }
435 
436 @("Should be a ptr-ptr at a typedef")
437 unittest {
438     enum code = `
439 typedef double MadeUp;
440 struct Struct {
441     int x;
442 };
443 
444 const void* const func(const MadeUp** const zzzz, const Struct** const yyyy);
445 `;
446 
447     import std.variant : visit;
448 
449     // arrange
450     auto visitor = new TestVisitor;
451     visitor.find = "c:@F@func#1**1d#1**1$@S@Struct#";
452 
453     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
454     ctx.virtualFileSystem.openAndWrite(cast(FileName) "issue.hpp", cast(Content) code);
455     auto tu = ctx.makeTranslationUnit("issue.hpp");
456 
457     // act
458     auto ast = ClangAST!(typeof(visitor))(tu.cursor);
459     ast.accept(visitor);
460 
461     // dfmt off
462     visitor.funcs[0].params[0]
463         .visit!((TypeKindVariable a) => writelnUt(a.type.kind.usr),
464                 (TypeKindAttr a) => writelnUt(a.kind.usr),
465                 (VariadicType a) => writelnUt("variadic"));
466     // dfmt on
467 
468     // assert
469     checkForCompilerErrors(tu).shouldBeFalse;
470     visitor.found.shouldBeTrue;
471     visitor.funcs.length.shouldNotEqual(0);
472 
473     { // assert that the found funcs is a func
474         auto res = visitor.container.find!TypeKind(visitor.funcs[0].type.kind.usr).front;
475         res.info.kind.shouldEqual(TypeKind.Info.Kind.func);
476     }
477 
478     auto param0 = visitor.container.find!TypeKind(
479             visitor.funcs[0].type.kind.info.params[0].usr).front;
480     // assert that the found funcs first parameter is a pointer
481     param0.info.kind.shouldEqual(TypeKind.Info.Kind.pointer);
482 
483     { // assert that the type pointed at is a typedef
484         auto res = visitor.container.find!TypeKind(param0.info.pointee).front;
485         res.usr.to!string().shouldNotEqual("File:issue.hpp Line:7 Column:45§1zzzz");
486         res.info.kind.shouldEqual(TypeKind.Info.Kind.typeRef);
487     }
488 }
489 
490 @("Should be the same USR for the declaration and definition of a function")
491 unittest {
492     enum code = `
493 void fun();
494 void fun() {}
495 
496 extern "C" void gun();
497 void gun() {}
498 `;
499 
500     // arrange
501     auto visitor = new TestVisitor;
502     visitor.find = "c:@F@fun";
503 
504     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
505     ctx.virtualFileSystem.openAndWrite(cast(FileName) "issue.hpp", cast(Content) code);
506     auto tu = ctx.makeTranslationUnit("issue.hpp");
507 
508     // act
509     auto ast = ClangAST!(typeof(visitor))(tu.cursor);
510     ast.accept(visitor);
511 
512     // assert
513     checkForCompilerErrors(tu).shouldBeFalse;
514     visitor.container.find!TypeKind(USRType("c:@F@fun#")).length.shouldEqual(1);
515     visitor.container.find!TypeKind(USRType("c:@F@gun")).length.shouldEqual(1);
516 }
517 
518 @("Should be a unique USR for the ptr with a ref to the typedef (my_int)")
519 unittest {
520     enum code = `
521 typedef int my_int;
522 my_int *y;
523 
524 typedef void (fun_ptr)();
525 fun_ptr *f;
526 `;
527 
528     // arrange
529     auto visitor = new TestVisitor;
530 
531     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
532     ctx.virtualFileSystem.openAndWrite(cast(FileName) "issue.hpp", cast(Content) code);
533     auto tu = ctx.makeTranslationUnit("issue.hpp");
534 
535     // act
536     auto ast = ClangAST!(typeof(visitor))(tu.cursor);
537     ast.accept(visitor);
538 
539     // assert
540     checkForCompilerErrors(tu).shouldBeFalse;
541     { // ptr to typedef
542         auto r = visitor.container.find!TypeKind(
543                 USRType("File:issue.hpp Line:3 Column:9§1y")).front;
544         r.info.kind.shouldEqual(TypeKind.Info.Kind.pointer);
545     }
546 
547     { // ptr to typedef of func prototype
548         auto r = visitor.container.find!TypeKind(
549                 USRType("File:issue.hpp Line:6 Column:10§1f")).front;
550         r.info.kind.shouldEqual(TypeKind.Info.Kind.funcPtr);
551     }
552 }
553 
554 @("Should be a forward declaration and definition separated")
555 unittest {
556     import cpptooling.data.type : LocationTag;
557 
558     enum code = "class A;
559 class A_ByCtor { A_ByCtor(A a); };";
560     enum code_def = `class A {};`;
561 
562     // arrange
563     auto visitor = new TestRecordVisitor;
564 
565     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
566     ctx.virtualFileSystem.openAndWrite(cast(FileName) "/issue.hpp", cast(Content) code);
567     ctx.virtualFileSystem.openAndWrite(cast(FileName) "/def.hpp", cast(Content) code_def);
568     auto tu0 = ctx.makeTranslationUnit("/issue.hpp");
569     auto tu1 = ctx.makeTranslationUnit("/def.hpp");
570 
571     // act
572     auto ast0 = ClangAST!(typeof(visitor))(tu0.cursor);
573     ast0.accept(visitor);
574     auto ast1 = ClangAST!(typeof(visitor))(tu1.cursor);
575     ast1.accept(visitor);
576 
577     // assert
578     checkForCompilerErrors(tu0).shouldBeFalse;
579     checkForCompilerErrors(tu1).shouldBeFalse;
580 
581     auto loc = visitor.container.find!LocationTag(visitor.record.type.kind.usr).front.get;
582 
583     loc.hasDeclaration.shouldBeTrue;
584     loc.declaration.shouldEqual(LocationTag(Location("/issue.hpp", 1, 7)));
585 
586     loc.hasDefinition.shouldBeTrue;
587     loc.definition.shouldEqual(LocationTag(Location("/def.hpp", 1, 7)));
588 }
589 
590 @("Should not crash on an anonymous type")
591 @Values("struct A { union { int x; }; };", "struct A { struct { int x; }; };")
592 unittest {
593     // arrange
594     auto visitor = new TestDeclVisitor;
595     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
596     ctx.virtualFileSystem.openAndWrite(cast(FileName) "/issue.hpp", cast(Content) getValue!string);
597     auto tu = ctx.makeTranslationUnit("/issue.hpp");
598 
599     // act
600     auto ast = ClangAST!(typeof(visitor))(tu.cursor);
601     ast.accept(visitor);
602 
603     // assert
604     checkForCompilerErrors(tu).shouldBeFalse;
605     // didn't crash
606 }
607 
608 @("Should be a builtin with a function name")
609 unittest {
610     immutable code = "
611 void f() {
612     __builtin_huge_valf();
613 }
614 
615 class A {
616     void my_builtin() {
617         __builtin_huge_valf();
618     }
619 };
620 ";
621 
622     // arrange
623     auto visitor = new TestFunctionBodyVisitor;
624     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
625     ctx.virtualFileSystem.openAndWrite(cast(FileName) "/issue.hpp", cast(Content) code);
626     auto tu = ctx.makeTranslationUnit("/issue.hpp");
627 
628     // act
629     auto ast = ClangAST!(typeof(visitor))(tu.cursor);
630     ast.accept(visitor);
631 
632     // assert
633     checkForCompilerErrors(tu).shouldBeFalse;
634     visitor.funcs.length.shouldEqual(3);
635     visitor.funcs[1].name.shouldEqual("__builtin_huge_valf");
636     visitor.funcs[2].name.shouldEqual("__builtin_huge_valf");
637 }
638 
639 @("Should be an union analysed and classified as a record")
640 unittest {
641     immutable code = "
642 struct A {
643     union {
644         char a;
645         int b;
646     };
647 };";
648 
649     // arrange
650     auto visitor = new TestUnionVisitor;
651     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
652     ctx.virtualFileSystem.openAndWrite(cast(FileName) "/issue.hpp", cast(Content) code);
653     auto tu = ctx.makeTranslationUnit("/issue.hpp");
654 
655     // act
656     auto ast = ClangAST!(typeof(visitor))(tu.cursor);
657     ast.accept(visitor);
658 
659     // assert
660     checkForCompilerErrors(tu).shouldBeFalse;
661     visitor.records.length.shouldEqual(1);
662     visitor.records[0].type.kind.info.kind.shouldEqual(TypeKind.Info.Kind.record);
663 }
664 
665 @("shall be the first level of typedef as the typeref")
666 unittest {
667     immutable code = "
668 typedef unsigned int ll;
669 typedef ll some_array[1];
670 const some_array& some_func();
671 ";
672 
673     // arrange
674     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
675     ctx.virtualFileSystem.openAndWrite(cast(FileName) "/issue.hpp", cast(Content) code);
676     auto tu = ctx.makeTranslationUnit("/issue.hpp");
677     auto visitor = new TestVisitor;
678     visitor.find = "c:@F@some_func#";
679 
680     // act
681     auto ast = ClangAST!(typeof(visitor))(tu.cursor);
682     ast.accept(visitor);
683 
684     // assert
685     checkForCompilerErrors(tu).shouldBeFalse;
686     visitor.funcs.length.shouldEqual(1);
687     visitor.funcs[0].returnType.toStringDecl("x").shouldEqual("const some_array &x");
688 }
689 
690 @("shall be a TypeRef with a canonical ref referencing the type at the end of the typedef chain")
691 unittest {
692     immutable code = "
693 #include <string>
694 typedef std::string myString1;
695 typedef myString1 myString2;
696 typedef myString2 myString3;
697 
698 void my_func(myString3 s);
699 ";
700 
701     // arrange
702     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
703     ctx.virtualFileSystem.openAndWrite(cast(FileName) "/issue.hpp", cast(Content) code);
704     auto tu = ctx.makeTranslationUnit("/issue.hpp");
705     auto visitor = new TestVisitor;
706 
707     // act
708     auto ast = ClangAST!(typeof(visitor))(tu.cursor);
709     ast.accept(visitor);
710 
711     // assert
712     checkForCompilerErrors(tu).shouldBeFalse;
713     visitor.found.shouldBeTrue;
714 
715     auto type2 = visitor.container.find!TypeKind(USRType("c:issue.hpp@T@myString3"));
716     type2.length.shouldEqual(1);
717     auto type = type2.front;
718     type.info.kind.shouldEqual(TypeKind.Info.Kind.typeRef);
719 
720     // should NOT point to myString1
721     // can't test the USR more specific because it is different on different
722     // systems.
723     (USRType(type.info.canonicalRef.dup)).shouldNotEqual(USRType("c:issue.hpp@T@myString1"));
724 }
725 
726 @("shall derive the constness of the return type")
727 @Values("int", "int*", "int&", "MyInt", "MyInt*", "MyInt&")
728 unittest {
729     immutable code = "
730 typedef int MyInt;
731 
732 class Class {
733     const %s fun();
734 };
735 ";
736 
737     // arrange
738     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
739     ctx.virtualFileSystem.openAndWrite(cast(FileName) "/issue.hpp",
740             cast(Content) format(code, getValue!string));
741     auto tu = ctx.makeTranslationUnit("/issue.hpp");
742     auto visitor = new ClassVisitor;
743 
744     // act
745     auto ast = ClangAST!(typeof(visitor))(tu.cursor);
746     ast.accept(visitor);
747 
748     // assert
749     checkForCompilerErrors(tu).shouldBeFalse;
750     visitor.found.shouldBeTrue;
751 
752     {
753         auto type2 = visitor.container.find!TypeKind(USRType("c:@S@Class@F@fun#"));
754         type2.length.shouldEqual(1);
755         type2.front.info.kind.shouldEqual(TypeKind.Info.Kind.func);
756         type2.front.info.returnAttr.isConst.shouldBeTrue;
757     }
758 }