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 }