Clang-expand: Expand function invocations into current scope
58 points
1 year ago
| 8 comments
| github.com
| HN
gpderetta
1 year ago
[-]
Very very nice. But it should really be an action in clangd instead of its own tool, for simple integration with LSP.

Also instead of always replacing the text, it could also be an overlay, where the function call is temporarily expanded in your IDE while in some special mode.

reply
6keZbCECT2uB
1 year ago
[-]
The choice between replacing and making it an overlay is up to your editor. I think it would be pretty to handle either choice as a plugin in your editor given the returned json.

I was surprised it wasn't combined with clangd.

reply
kevincox
1 year ago
[-]
rust-analyzer has this as well. I've never actually used it though. In most cases where I would have found it useful the function was so simple that I just did it manually without thinking. This also seems to do a better job cleaning up the resulting code than rust-analyzer does for example.

    let cell = self.cell_mut(pos);
Becomes:

    let cell = {
      let ref mut this = self;
      &mut this.board[usize::from(pos)]
    };
Instead of:

    let cell = &mut self.board[usize::from(pos)];
It did manage to simplify the argument (maybe because it was the same name?) but had to rename `self`.
reply
munro
1 year ago
[-]
wow seriously cool idea here, i think it could be expanded on.

currently my only tool is "jump to definition" & docstr tooltips, but if i could expand those definitions in place... i'd imagine i could grok the code much faster, because i could expand multiple functions and even nested functions, and read the code in one linear flow.

reply
ahaferburg
1 year ago
[-]
Makes me think of these two emails by John Carmack.

http://number-none.com/blow/john_carmack_on_inlined_code.htm...

> The real enemy addressed by inlining is unexpected dependency and mutation of state, which functional programming solves more directly and completely. However, if you are going to make a lot of state changes, having them all happen inline does have advantages; you should be made constantly aware of the full horror of what you are doing. When it gets to be too much to take, figure out how to factor blocks out into pure functions (and don.t let them slide back into impurity!).

reply
CrendKing
1 year ago
[-]
I sincerely don't understand when is this useful. It basically removes all the existing abstractions in the code, and replaces with inline code. Those abstractions were created by someone for some reasons. Blindly destroying them in the name of "refactoring" is just wrong. Refactoring should be process that involves intellectual creativity, involves deeply understanding the intricacy of dependencies and references, not mechanically expanding functions and call it done.

Of course, just like rust-analyzer's "Expand macro recursively" command, this could be used as an analysis utility, even better if it works not only on functions but also C/C++ macros.

reply
TeMPOraL
1 year ago
[-]
> Those abstractions were created by someone for some reasons.

Yes. But there is no optimal, best single abstraction - the best abstraction is always for a specific purpose you're looking at the code in question. Same code, same programmer, two different tasks - each may command a code structure opposite of the other.

The core problem is that we insist on working with a single-source-of-truth, plaintext representation - and that is not even remotely sufficient to express everything, every cross-cutting concern, in a readable way, at the same time. Inlining is a perfect example, because "few big functions" vs. "lots of small functions" is one of those holy wars that will not end, because the actual answer is "whichever works best, for you, at this moment".

A bluntly-put corollary:

> Refactoring should be process that involves intellectual creativity, involves deeply understanding the intricacy of dependencies and references, not mechanically expanding functions and call it done.

The sad thing is, all that creativity and exertion of mind is wasted work. The more you refactor your code for your current purpose, the harder it will be for you (or someone else) to work with the code for for a different, cross-cutting purpose. Past some point, we're just overfitting the medium of single, flat, plaintext representation.

A tool like this `clang-expand` is an example of a small part of a solution - ideally, you should be able to trivially do those transformations whenever you need, on whatever code you need, mostly for reading but also for writing. As in, inline a bunch of functions, filter out noise, make your changes, and have those changes propagate to where they should in "canonical representation" - the raw source code, which you don't generally look at, any more than you look at object files your compiler produces today.

reply
Akronymus
1 year ago
[-]
Removing bad abstractions. And you don't have to use such tools "blindly"

For me, such a tool, for other languages would have been useful many times as a way to make refactoring less tedious.

reply
MauranKilom
1 year ago
[-]
Maybe I'm the only one, but... why?

What about by-hand-inlining `std::find` makes the code better?

reply
sweetjuly
1 year ago
[-]
It's great for doing security reviews! Often times with crazy template and macro ridden code, it's a challenge to even find the implementation for something. Sometimes it's easier to compile the binary, throw it into Ghidra, and look at the disassembly and decompilation to grok what the code is actually doing than try to bounce your way through a dozen templates and types.
reply
Conscat
1 year ago
[-]
Clang has a statement attribute `[[clang::always_inline]]` that automates this at a call site, but GCC only has a function attribute for it. You could wrap the function in a `[[gnu::flatten]]` function that takes the callable as a non-type template parameter in C++20 to do this, though, but that could be more aggressive than what you want. It also won't work for operators as easily as this.

You can already expand macros at call site in any major C++ editor, so why not functions as well?

I would like this mainly just for making source exploration easier. Visual Studio and Clion have a "peek" feature that does something similar, but as a purely UI element, but Emacs' implementation of peek from LSP works much worse.

reply
Conscat
1 year ago
[-]
Correction, I actually just tried this. The wrapper does not work in GCC, but it does in Clang. https://godbolt.org/z/d3jMo36WY
reply
choppaface
1 year ago
[-]
Copy-pasta is typically a no-no, but this tool sure helps experiment especially when templates / types are complex.

Moreover things like SFINAE, variadic functions, and macros can make some libraries really opaque. Stuff like loggers, pub-subs, SERDES code... there will be a lot of template complexity and other indirection where this tool would help dramatically in peeling back the layers and could be used in place of breakpoints or a traditional debugger. (And it can often be hard to set up a debug session for large systems).

Can also be useful when you want to take an existing function and customize it for the call site versus write a new helper. For example maybe you want std::find_if() but instead of writing a lambda predicate you want some other code embedded into the body of the loop.

reply
the-smug-one
1 year ago
[-]
I just want to know what code is actually called. This is often a pain in C++. I assume this code handles SFINAE etc.
reply
TeMPOraL
1 year ago
[-]
Thank you! It's a tool I dreamed of having for years, but couldn't arse myself to try and write.

The output seems nice enough it should be possible to integrate it with Emacs to show as overlay, vs. actually replacing code - though in practice, I'd probably want a mix of both.

reply
smasher164
1 year ago
[-]
This is so cool! I want this for every language now.
reply