gersemi icon indicating copy to clipboard operation
gersemi copied to clipboard

Add ability to customize "num items" inlining heuristic

Open alliepiper opened this issue 6 months ago • 10 comments

The "expand for more than 4 items heuristic" is useful, but I'd like to play around with that a bit more for my codebase.

Would it be easy to make the 'number of items' limit customizable, ideally both globally in the .gersemirc file and per-function through extensions / cmakeparseargs hints? It seems like this might be a customization point that would provide a lot of control to users while adding minimal maintenance complexity.

4 is suitable for most situations, but there are cases where I really want to force expansion. For example, I'd like add_library to always expand when there's more than one source file specified, and being able to set the max_inline_items to 3 for this function via an extension would accomplish this:

add_library(bar OBJECT source1.cpp)

add_library(
  foo
  OBJECT
  source1.cpp
  source2.cpp
)

Same with target_link_libraries:

target_link_libraries(kramble PRIVATE jaam)

target_link_libraries(
  foo
  PUBLIC
    bar
    baz
)

alliepiper avatar Nov 08 '25 16:11 alliepiper

I was about to file another issue requesting the ability to customize favour-expansion vs favour-inlining per function, but then noticed that all of my desired example usecases would actually be implementable by just overriding max_inline_items per function. I think this feature would give a lot of value.

alliepiper avatar Nov 08 '25 17:11 alliepiper

Usually, in these rare cases, when I really want to force expansion I just use empty comment:

target_link_libraries(
    foo
    PUBLIC #
        bar
        baz
)

It's worth noting that similar thing works with clang-format. I usually use it in cases like this one:

bool condition()
{
    return a() //
        or b() //
        or c() //
        or d() //
        or e();
}

I'll think about this issue but it's somewhere in the territory "I need to add another knob" and I try my best to avoid visiting this territory. Of course extensions were meant to be the solution where user knows better, so to speak, but I'm a little bit cautious about what should be exposed there.

One cool thing I've noticed about what your proposal is that, more or less, it's a natural extension to current favour-<x> because as far as I remember the implementation does this:

  • favour-inlining == max-inline-items: 4
  • favour-expansion == max-inline-items: 1

BlankSpruce avatar Nov 08 '25 17:11 BlankSpruce

That workaround will do nicely for these examples, thanks!

The other situation I encountered where I would like to modify this is for disabling expansion. For function and macro, I prefer to have all of the arguments inline unless they'd break the line length:

function(my_function arg_a arg_b arg_c arg_d arg_e ...)

Setting max-inline-items to a very large value would accomplish this, and I don't see a good workaround for this usecase currently. I can live with the expansion, but it'd be a nice QoL improvement to have control over this.

alliepiper avatar Nov 08 '25 18:11 alliepiper

Another oddity I ran into with the # workaround -- it seems to break the keyword formatters and produces:

set_target_properties(
  some_target_with_a_long_name
  PROPERTIES #
    CXX_STANDARD
    20
    CXX_STANDARD_REQUIRED
    ON
)

instead of:

set_target_properties(
  some_target_with_a_long_name
  PROPERTIES #
    CXX_STANDARD 20
    CXX_STANDARD_REQUIRED ON
)

alliepiper avatar Nov 08 '25 22:11 alliepiper

Nice catch. I've fixed in that 0.23.1.

BlankSpruce avatar Nov 09 '25 08:11 BlankSpruce

That workaround will do nicely for these examples, thanks!

The other situation I encountered where I would like to modify this is for disabling expansion. For function and macro, I prefer to have all of the arguments inline unless they'd break the line length:

function(my_function arg_a arg_b arg_c arg_d arg_e ...)

Setting max-inline-items to a very large value would accomplish this, and I don't see a good workaround for this usecase currently. I can live with the expansion, but it'd be a nice QoL improvement to have control over this.

See for yourself using this branch. For the sake of this demo I've used the name inlining_heuristic instead of max_inline_items because I feel this would be a little more descriptive what's going on. If you happen to do the experiment on NVIDIA/cccl or any other repository please let me know, perhaps seeing the advantages will convince me to just release this as a feature.

Short summary:

  • favour-inlining effectively is like setting "inlining_heuristic": 4 for all commands
  • favour-expansion effectively is like setting "inlining_heuristic": 1 for almost all of commands, exceptions are block/foreach/function/macro/if/elseif/else/endif/while/endwhile where it remains as "inlining_heuristic": 4
  • this demo branch has 0.23.9999 version for convenience

If something looks "funny" we can refine this experiment.

BlankSpruce avatar Nov 12 '25 19:11 BlankSpruce

Thanks! I got a chance to try this out today. I noticed #82 while adjusting cmake_parse_arguments, but I think that's unrelated to this branch.

I'm trying to force target_link_libraries to always expand when more than one library is listed in a group, e.g, these would be valid:

# Inline for a single target:
target_link_libraries(foo PRIVATE bar)

# List multiple targets:
target_link_libraries(foo
  PRIVATE
    bar
    baz
)

I can't find a heuristic setting that would allow this:

# inlining_heuristic unset:
target_link_libraries(foo PRIVATE bar baz)

# inlining_heuristic = 1
target_link_libraries(
  foo
  PRIVATE bar baz
)

I'd like this to be the same for target_compile_definitions, target_compile_options, target_include_directories -- inline when only a single lib/def/opt/dir is given, but expanded for multiple. I find this much more readable and less "soupy" when there's more than one multivalue keyword. Is this doable?

My current approach:

from gersemi.builtin_commands import builtin_commands

def set_max_inline_items(command, max_inline_items):
    command["inlining_heuristic"] = max_inline_items
    return command

command_definitions = {
    "target_link_libraries": set_max_inline_items(builtin_commands["target_link_libraries"], 1),
}

Setting to 9999 for function and macro works as desired to force-inline all args under the line limit.

alliepiper avatar Nov 19 '25 01:11 alliepiper

Interestingly, add_library with a heuristic of 1 behaves how I'd want:

# default
add_library(foo OBJECT foo.cpp)
add_library(bar OBJECT bar.cpp baz.cpp)

# heuristic = 1
add_library(foo OBJECT foo.cpp)
add_library(
  bar
  OBJECT
  bar.cpp
  baz.cpp
)

alliepiper avatar Nov 19 '25 01:11 alliepiper

Maybe something like a per-keyword keyword_inlining_heuristic property to compliment formatters / preprocessors would give a nice interface with the level of customization I'm looking for.

alliepiper avatar Nov 19 '25 01:11 alliepiper

Status update: I'm interested in pursuing this topic further but I need to suspend it for some time. I'm still open to issues found along the way (like #82) since bug fixing is a priority for me.

BlankSpruce avatar Nov 24 '25 18:11 BlankSpruce

I came across this exact issue, which motivated me to disable the custom definitions. +1 for this feature.

ahoarau avatar Dec 16 '25 16:12 ahoarau