Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/982.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
|commands| Fix autocompletion not working for options of nested :func:`~ext.commands.injection`\s.
29 changes: 20 additions & 9 deletions disnake/ext/commands/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -1103,13 +1103,31 @@ def collect_params(
)


def apply_injection_autocompleters(
injection: Injection, params: list[ParamInfo], location: str
) -> None:
"""Assign an injection's autocompleters to the matching collected params *in-place*"""
if not injection.autocompleters:
return

lookup = {p.name: p for p in params}
for name, func in injection.autocompleters.items():
param = lookup.get(name)
if param is None:
msg = f"Option '{name}' doesn't exist in '{location}'"
raise ValueError(msg)
param.autocomplete = func


def collect_nested_params(function: Callable[..., Any]) -> list[ParamInfo]:
"""Collect all options from a function"""
# TODO: Have these be actually sorted properly and not have injections always at the end
_, _, paraminfos, injections = collect_params(function)

for injection in injections.values():
paraminfos += collect_nested_params(injection.function)
nested = collect_nested_params(injection.function)
apply_injection_autocompleters(injection, nested, injection.function.__name__)
paraminfos += nested

return sorted(paraminfos, key=lambda param: not param.required)

Expand Down Expand Up @@ -1198,14 +1216,7 @@ def expand_params(command: AnySlashCommand) -> list[Option]:

for injection in injections.values():
collected = collect_nested_params(injection.function)
if injection.autocompleters:
lookup = {p.name: p for p in collected}
for name, func in injection.autocompleters.items():
param = lookup.get(name)
if param is None:
msg = f"Option '{name}' doesn't exist in '{command.qualified_name}'"
raise ValueError(msg)
param.autocomplete = func
apply_injection_autocompleters(injection, collected, command.qualified_name)
params += collected

params = sorted(params, key=lambda param: not param.required)
Expand Down
53 changes: 53 additions & 0 deletions tests/ext/commands/test_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,56 @@ def func(
assert cog is None
assert inter is not None
assert params.keys() == {"a"}


class TestNestedInjectionAutocomplete:
def test_nested_injection_autocomplete(self) -> None:
@commands.injection()
def inner(a: str, b: str) -> str:
return a + b

@inner.autocomplete("a")
async def autocomp_a(inter, value) -> list[str]:
return [value]

@commands.injection()
def outer(c: str, d: str = inner) -> str: # type: ignore[assignment]
return c + d

@outer.autocomplete("c")
async def autocomp_c(inter, value) -> list[str]:
return [value]

@commands.slash_command()
async def cmd(
inter: disnake.ApplicationCommandInteraction,
arg: str = outer, # type: ignore[assignment]
) -> None: ...

assert cmd.autocompleters.keys() == {"a", "c"}
assert {o.name: o.autocomplete for o in cmd.body.options} == {
"a": True,
"b": False,
"c": True,
}

def test_nested_injection_unknown_option(self) -> None:
@commands.injection()
def inner(a: str) -> str:
return a

@inner.autocomplete("nonexistent")
async def autocomp(inter, value) -> list[str]:
return [value]

@commands.injection()
def outer(b: str, c: str = inner) -> str: # type: ignore[assignment]
return b + c

with pytest.raises(ValueError, match="Option 'nonexistent' doesn't exist"):

@commands.slash_command()
async def cmd(
inter: disnake.ApplicationCommandInteraction,
arg: str = outer, # type: ignore[assignment]
) -> None: ...