Using Rewriter to remove a function call argument along with preceding comma?

Am new here, hope this isn't too dumb a question. (And from looking at the archives, fits better here than on cfe-users?)

Anyway, what I want to do is a rewriting Clang plugin that removes one argument from specific function calls. But where I'm lost is how to determine the SourceLocation of the comma preceding the given argument, to do something like

   Rewriter.RemoveText(
     SourceRange(
       /*TODO*/,
       callExpr->getArg(1)->getLocEnd()));

Thanks,
Stephan

Am new here, hope this isn’t too dumb a question. (And from looking at the archives, fits better here than on cfe-users?)

Anyway, what I want to do is a rewriting Clang plugin that removes one argument from specific function calls. But where I’m lost is how to determine the SourceLocation of the comma preceding the given argument, to do something like

You could remove this range:

[loc-for-end-of-token of end location of preceding argument, end location of removed argument]

like in these examples:

foo(4, 5, 6) → foo(4, 6)
^-^

foo(4 , 5 , 6) → foo(4 , 6)
^–^

foo(4, 5, 6) → foo(4, 5)
^-^

foo( 4 , 5 , 6 ) → foo( 4 , 5 )
^–^

Anyway, what I want to do is a rewriting Clang plugin that removes one
argument from specific function calls. But where I'm lost is how to
determine the SourceLocation of the comma preceding the given
argument, to do something like

You could remove this range:

[loc-for-end-of-token of end location of preceding argument, end
location of removed argument]

Yeah, but how do I get that loc-for-end-of-token? I must confess that studying the header files left me clueless. Continuing with my below example, what would replace the /*TODO*/ if I wanted to remove starting past the tokens for callExpr->getArg(0)?

Thanks again,
Stephan

Anyway, what I want to do is a rewriting Clang plugin that removes one
argument from specific function calls. But where I'm lost is how to
determine the SourceLocation of the comma preceding the given
argument, to do something like

You could remove this range:

[loc-for-end-of-token of end location of preceding argument, end
location of removed argument]

Yeah, but how do I get that loc-for-end-of-token?

See Lexer::getLocForEndOfToken() or Preprocessor::getLocForEndOfToken() (the latter delegates to the former).

I must confess that studying the header files left me clueless. Continuing with my below example, what would replace the /*TODO*/ if I wanted to remove starting past the tokens for callExpr->getArg(0)?

You pass the end location to one of the above functions.

Ah, that was the missing link.

Thanks a lot,
Stephan

...but that is still not working when macros are involved (getLocForEndOfToken(l) returns an invalid loc if l points into a macro expansion). What I want to do is remove the second (zero-valued) argument from all calls to f in

   #define FOO "foo"
   #define ZERO 0
   f("foo", 0);
   f(FOO, ZERO);
   assert(f("foo", 0));
   assert(f(FOO, ZERO));

(where "assert" is the std macro), i.e., end up with

   #define FOO "foo"
   #define ZERO 0
   f("foo");
   f(FOO);
   assert(f("foo"));
   assert(f(FOO));

But in the cases where the first argument uses the macro FOO,

   getLocForEndOfToken(e->getArg(0)->getLocEnd())

would return an invalid loc, and

   getLocForEndOfToken(SM.getSpellingLoc(e->getArg(0)->getLocEnd()))

would return the end of the

   #define FOO "foo"

line. (And determining the end of the range to be removed, e->getArg(1)->getLocEnd(), has a similar problem if the second argument uses the macro ZERO.)

What I think I need is the location l1 of the punctuation ("," in this caes) preceeding the second argument and the location l2 of the punctuation (")" in this case) following it. Then, for each of the two lN, if spellN = SM.getSpellingLoc(lN) is contained in SM.getExpansionRange(lN), I (hope) it would be sound to drop the range from spell1 to before spell2. (This would miss cases like

   #define COMMA ,
   #define RPAREN )
   f("foo" COMMA 0 RPAREN

but that's more acceptable for my use case than missing any of the cases given previously.) At least, that's my understanding so far (which might well be completely off track, though).

Input appreciated,
Stephan

Anyway, what I want to do is a rewriting Clang plugin that removes one
argument from specific function calls. But where I’m lost is how to
determine the SourceLocation of the comma preceding the given
argument, to do something like

You could remove this range:

[loc-for-end-of-token of end location of preceding argument, end
location of removed argument]

Yeah, but how do I get that loc-for-end-of-token?

See Lexer::getLocForEndOfToken() or
Preprocessor::getLocForEndOfToken() (the latter delegates to the former).

Ah, that was the missing link.

…but that is still not working when macros are involved (getLocForEndOfToken(l) returns an invalid loc if l points into a macro expansion). What I want to do is remove the second (zero-valued) argument from all calls to f in

#define FOO “foo”
#define ZERO 0
f(“foo”, 0);
f(FOO, ZERO);
assert(f(“foo”, 0));
assert(f(FOO, ZERO));

(where “assert” is the std macro), i.e., end up with

#define FOO “foo”
#define ZERO 0
f(“foo”);
f(FOO);
assert(f(“foo”));
assert(f(FOO));

But in the cases where the first argument uses the macro FOO,

getLocForEndOfToken(e->getArg(0)->getLocEnd())

would return an invalid loc, and

getLocForEndOfToken(SM.getSpellingLoc(e->getArg(0)->getLocEnd()))

would return the end of the

#define FOO “foo”

line. (And determining the end of the range to be removed, e->getArg(1)->getLocEnd(), has a similar problem if the second argument uses the macro ZERO.)

Try this helper function that deals with macros, and make sure you are using trunk clang:

static CharSourceRange makeFileRangeBetweenArgs(SourceLocation EndArg0,
SourceLocation EndArg1,
const SourceManager &SM,
const LangOptions &LangOpts) {
if (EndArg0.isInvalid() || EndArg1.isInvalid())
return CharSourceRange();

// if the two locations came from the same macro argument, move up to macro argument spelling locations.
if (EndArg0.isMacroID() && EndArg1.isMacroID() &&
SM.isMacroArgExpansion(EndArg0) && SM.isMacroArgExpansion(EndArg1) &&
SM.getImmediateExpansionRange(EndArg0).first == SM.getImmediateExpansionRange(EndArg1).first) {
EndArg0 = SM.getImmediateMacroCallerLoc(EndArg0);
EndArg1 = SM.getImmediateMacroCallerLoc(EndArg1);
return makeFileRangeBetweenArgs(EndArg0, EndArg1, SM, LangOpts);
}

EndArg0 = Lexer::getLocForEndOfToken(EndArg0, 0, SM, LangOpts);
EndArg1 = Lexer::getLocForEndOfToken(EndArg1, 0, SM, LangOpts);

if (EndArg0.isFileID() && EndArg1.isFileID())
return CharSourceRange::getCharRange(EndArg0, EndArg1);

return CharSourceRange();
}