LegalizeAdulthood added inline comments. ================ Comment at: unittests/ASTMatchers/ASTMatchersTest.cpp:4992 @@ +4991,3 @@ + EXPECT_TRUE(matches("typedef int hasUnderlyingTypeTest;", + typedefDecl(hasUnderlyingType(asString("int"))))); + EXPECT_TRUE(matches("typedef const int T;", ---------------- LegalizeAdulthood wrote: > LegalizeAdulthood wrote: > > LegalizeAdulthood wrote: > > > aaron.ballman wrote: > > > > LegalizeAdulthood wrote: > > > > > aaron.ballman wrote: > > > > > > > I'm happy to make the improvement, but I don't know how. I simply > > > > > > > call the Node.underlyingType(), just like hasType calls > > > > > > > Node.getType(). I don't know why they are different. > > > > > > > > > > > > hasType() and hasUnderlyingType() have different operational > > > > > > semantic implications, at least to me. hasType() asks, "ultimately, > > > > > > what is the type of this thing?", and Node.underlyingType() answers > > > > > > that. To me, hasUnderlyingType() asks, "in the chain of types that > > > > > > this typedef refers to, does this thing match any of them?", and > > > > > > Node.underlyingType() does not answer that -- it only looks at the > > > > > > final desugared type. The difference we want is that hasType() > > > > > > continues to look at the final type, but hasUnderlyingType() needs > > > > > > to do more work to look at intermediary types and terminate that > > > > > > loop when the answer is "yes, it has this type" or "no, we can't > > > > > > desugar any further." > > > > > > > > > > > > If we don't care about the intermediate types for your needs, then > > > > > > I don't see the point to hasUnderlyingType() being an AST matcher > > > > > > at all. hasType() does exactly what is needed, and you can instead > > > > > > modify that to accept a TypedefDecl in addition to Expr and > > > > > > ValueDecl. However, I still see value in hasUnderlyingType() > > > > > > because typedef chains can be long and complex (like in the Win32 > > > > > > APIs), and being able to query for intermediary types would be > > > > > > useful. e.g., I want to know about all types that have an > > > > > > intermediary type of DWORD (which itself is a typedef for an > > > > > > unsigned integer type). hasType() will always go to the final type, > > > > > > making such a matcher impossible without something like > > > > > > hasUnderlyingType(). > > > > > I just named the matcher after the > > > > > [accessor](http://clang.llvm.org/doxygen/classclang_1_1TypedefNameDecl.html#a5fccedff6d3854db365a540145029158) > > > > > on a typedefDecl node. To me it's just a narrowing matcher on that > > > > > node that gives you access to whatever the accessor returns. > > > > > > > > > > Things may have changed in the meantime, but when I originally > > > > > created this patch you couldn't do > > > > > `typedefDecl(hasType(asString("int")))`. It doesn't compile. Since > > > > > `hasType` just matched against the value of whatever `Node.getType()` > > > > > returned it seemed natural to add a matcher called > > > > > `hasUnderlyingType` that just matched against the value of whatever > > > > > `Node.getUnderlyingType()` returned. > > > > > > > > > > As I said, I'm happy to make the suggested improvement, but beyond > > > > > simple immitation of existing matchers, I have no idea how to > > > > > traverse the encodings of types within the guts of clang. This is > > > > > what I mean when I say "I don't know how". I understand conceptually > > > > > what you are saying but I have no idea how to express that in clang's > > > > > code base. > > > > That's why I am trying to understand what your goal is. As best I can > > > > tell, there are two distinct things someone may want to do with a > > > > typedef's type: figure out what it ultimately boils down to, or > > > > traverse the typedef chain. I was uncertain which one fit your needs > > > > better, but it sounds like "get the ultimate type" is what you want. > > > > From there we can discuss what the proper design of the matcher is. I > > > > think I was thrown off by you naming it *has*UnderlyingType instead of > > > > *get*UnderlyingType because *has* implies a different relationship than > > > > *get* and I couldn't see why you wouldn't just update hasType() instead. > > > > > > > > Another thing to think about: the typedef matcher is a bit > > > > controversial in terms of the semantics, but the other matchers are > > > > not. Perhaps it makes sense to split the patch into two parts? > > > > > > > > As for how to actually implement the traversal, I think it looks > > > > something like (totally untested): > > > > ``` > > > > QualType QT = Context.getTypedefType(&Node); > > > > for (QualType NextQT; QT != NextQT; NextQT = > > > > QT.getSingleStepDesugaredType(Context)) { > > > > // If matches, return Type > > > > } > > > > return QT; > > > > ``` > > > This all came up because of the work I was doing on clang-tidy to > > > implement the modernize-redundant-void-arg check. There wasn't a way to > > > narrow typedefDecl nodes based on the actual type for which a synonym was > > > being introduced. In that situation, suppose we have something like this: > > > > > > ``` > > > typedef void (fnReturningVoid)(void); > > > typedef foo fnReturningVoid; > > > ``` > > > > > > I would want to match the first typedef because that is the one my check > > > needs to modify. I don't want to match the second typedef because even > > > though it has the same ultimate type as the first, it doesn't contain the > > > redundant void argument tokens that need to be fixed up. > > > > > > So for my needs in this scenario, the existing matcher does **exactly** > > > what I need, namely match the first typedef, but not the second. It > > > turns out that on a typedefDecl node you call the member function > > > `getUnderlyingType()` in order to match the first typedef, so that is > > > what I have called the matcher. > > > > > > If the phrase "underlying type" in common clang parlance means the > > > ultimate desugaring of a type down to it's most basic form with all > > > intervening typedefs removed, then it seems that not only is my matcher > > > misnamed, but also this member function is misnamed. Because that's not > > > what this member function is returning for the typedef-of-a-typedef > > > situation (as the earlier failed test fragment discussed). > > > > > > Am I making sense? > > Oops, that code snippet should have been: > > > > ``` > > typedef void (fnReturningVoid)(void); > > typedef fnReturningVoid foo; > > ``` > > > > In other words, `foo` was just anohter synonym for `fnReturningVoid`. > OK, so I experimented with clang-query a little bit and here's where this > changeset gets us: > > ``` > 1: typedef void (fn)(void); > 2: typedef fn foo; > 3: typedef int bar; > 4: typedef int (f); > 5: typedef int (fn2)(int); > ``` > > ``` > clang-query> match typedefDecl(hasUnderlyingType(asString("int"))) > > Match #1: > > /tmp/a.cpp:3:1: note: "root" binds here > typedef int bar; > ^~~~~~~~~~~~~~~ > > Match #2: > > /tmp/a.cpp:4:1: note: "root" binds here > typedef int (f); > ^~~~~~~~~~~~~~~ > 2 matches. > ``` > > ``` > clang-query> match typedefDecl(hasUnderlyingType(typedefType())) > > Match #1: > > /tmp/a.cpp:2:1: note: "root" binds here > typedef fn foo; > ^~~~~~~~~~~~~~ > 1 match. > ``` > > ``` > clang-query> match typedefDecl(hasUnderlyingType(parenType())) > > Match #1: > > /tmp/a.cpp:1:1: note: "root" binds here > typedef void (fn)(void); > ^~~~~~~~~~~~~~~~~~~~~~~ > > Match #2: > > /tmp/a.cpp:4:1: note: "root" binds here > typedef int (f); > ^~~~~~~~~~~~~~~ > > Match #3: > > /tmp/a.cpp:5:1: note: "root" binds here > typedef int (fn2)(int); > ^~~~~~~~~~~~~~~~~~~~~~ > 3 matches. > ``` > > ``` > clang-query> match > typedefDecl(hasUnderlyingType(parenType(innerType(functionType())))) > > Match #1: > > /tmp/a.cpp:1:1: note: "root" binds here > typedef void (fn)(void); > ^~~~~~~~~~~~~~~~~~~~~~~ > > Match #2: > > /tmp/a.cpp:5:1: note: "root" binds here > typedef int (fn2)(int); > ^~~~~~~~~~~~~~~~~~~~~~ > 2 matches. > ``` > > To me this means that the matcher is working as desired. If > `hasUnderlyingType` squished out all the stuff in the middle, then you > wouldn't be able to tell the difference between line 1 or line 2. To me it > seems that the matcher should do just **one** level of narrowing on a > typedefDecl. If you want to throw all the intervening sugaring away so that > line 2 and line 1 match `functionType()`, then I think that is a distinct > matcher, along the lines of existing matchers like `hasDescendent` that > traverse multiple relationships to find a match. There may even be a way to > use existing matchers like `has` in order to do this arbitrarily deep > traversal in order to find a matching type no matter how many layers of > typedef aliases exist between an identifier and the real type. > > Going back to my original motivation, it allows me to match typedef aliases > for functions taking zero arguments: > > ``` > clang-query> match typedefDecl(hasUnderlyingType(parenType( > innerType(functionType( functionProtoType(parameterCountIs(0)) )) ))) > > Match #1: > > /tmp/a.cpp:1:1: note: "root" binds here > typedef void (fn)(void); > ^~~~~~~~~~~~~~~~~~~~~~~ > 1 match. > ``` Looks like that last one could be simplified slightly:
``` clang-query> match typedefDecl(hasUnderlyingType( parenType(innerType( functionProtoType(parameterCountIs(0)) )) )) Match #1: /tmp/a.cpp:1:1: note: "root" binds here typedef void (fn)(void); ^~~~~~~~~~~~~~~~~~~~~~~ 1 match. ``` http://reviews.llvm.org/D8149 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits