Skip to content

Commit 98ec059

Browse files
Preserve type information from docstrings if no type annotation is present and parameter has default value.
Fixes #574.
1 parent 62274c9 commit 98ec059

File tree

3 files changed

+95
-35
lines changed

3 files changed

+95
-35
lines changed

src/sphinx_autodoc_typehints/__init__.py

Lines changed: 72 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -808,45 +808,81 @@ def _inject_signature(
808808
lines: list[str],
809809
) -> None:
810810
for arg_name in signature.parameters:
811-
annotation = type_hints.get(arg_name)
812-
813-
default = signature.parameters[arg_name].default
814-
815-
if arg_name.endswith("_"):
816-
arg_name = f"{arg_name[:-1]}\\_" # noqa: PLW2901
817-
818-
insert_index = None
819-
for at, line in enumerate(lines):
820-
if _line_is_param_line_for_arg(line, arg_name):
821-
# Get the arg_name from the doc to match up for type in case it has a star prefix.
822-
# Line is in the correct format so this is guaranteed to return tuple[str, str].
823-
func = _get_sphinx_line_keyword_and_argument
824-
_, arg_name = func(line) # type: ignore[assignment, misc] # noqa: PLW2901
825-
insert_index = at
826-
break
827-
828-
if annotation is not None and insert_index is None and app.config.always_document_param_types:
829-
lines.append(f":param {arg_name}:")
830-
insert_index = len(lines)
831-
832-
if insert_index is not None:
833-
if annotation is None:
834-
type_annotation = f":type {arg_name}: "
835-
else:
836-
short_literals = app.config.python_display_short_literal_types
837-
formatted_annotation = add_type_css_class(
838-
format_annotation(annotation, app.config, short_literals=short_literals)
839-
)
840-
type_annotation = f":type {arg_name}: {formatted_annotation}"
811+
_inject_arg_signature(type_hints, signature, app, lines, arg_name)
812+
813+
814+
def _inject_arg_signature(
815+
type_hints: dict[str, Any],
816+
signature: inspect.Signature,
817+
app: Sphinx,
818+
lines: list[str],
819+
arg_name: str,
820+
) -> None:
821+
annotation = type_hints.get(arg_name)
822+
823+
default = signature.parameters[arg_name].default
824+
825+
if arg_name.endswith("_"):
826+
arg_name = f"{arg_name[:-1]}\\_"
827+
828+
insert_index = None
829+
for at, line in enumerate(lines):
830+
if _line_is_param_line_for_arg(line, arg_name):
831+
# Get the arg_name from the doc to match up for type in case it has a star prefix.
832+
# Line is in the correct format so this is guaranteed to return tuple[str, str].
833+
_, arg_name = _get_sphinx_line_keyword_and_argument(line) # type: ignore[assignment, misc]
834+
insert_index = at
835+
break
836+
837+
if annotation is not None and insert_index is None and app.config.always_document_param_types:
838+
lines.append(f":param {arg_name}:")
839+
insert_index = len(lines)
841840

842-
if app.config.typehints_defaults:
843-
formatted_default = format_default(app, default, annotation is not None)
844-
if formatted_default:
845-
type_annotation = _append_default(app, lines, insert_index, type_annotation, formatted_default)
841+
if insert_index is not None:
842+
update_only = False
846843

844+
if annotation is None:
845+
type_annotation, insert_index, update_only = _find_preexisting_type_annotation(
846+
lines,
847+
arg_name,
848+
insert_index,
849+
)
850+
851+
else:
852+
short_literals = app.config.python_display_short_literal_types
853+
formatted_annotation = add_type_css_class(
854+
format_annotation(annotation, app.config, short_literals=short_literals)
855+
)
856+
type_annotation = f":type {arg_name}: {formatted_annotation}"
857+
858+
if app.config.typehints_defaults:
859+
formatted_default = format_default(app, default, annotation is not None)
860+
if formatted_default:
861+
type_annotation = _append_default(app, lines, insert_index, type_annotation, formatted_default)
862+
863+
if update_only:
864+
lines[insert_index] = type_annotation
865+
else:
847866
lines.insert(insert_index, type_annotation)
848867

849868

869+
def _find_preexisting_type_annotation(
870+
lines: list[str],
871+
arg_name: str,
872+
insert_index: int,
873+
) -> tuple[str, int, bool]:
874+
"""
875+
Find a type entry in the input docstring that matches the given arg name.
876+
877+
If not found, a placeholder type entry that can be inserted at the given index is returned.
878+
"""
879+
type_annotation = f":type {arg_name}: "
880+
for at, line in enumerate(lines):
881+
if line.startswith(type_annotation):
882+
return line, at, True
883+
return type_annotation, insert_index, False
884+
885+
850886
def _append_default(
851887
app: Sphinx, lines: list[str], insert_index: int, type_annotation: str, formatted_default: str
852888
) -> str:
@@ -864,7 +900,8 @@ def _append_default(
864900
lines[append_index] += formatted_default
865901

866902
else: # add to last param doc line
867-
type_annotation += formatted_default
903+
separator = "" if type_annotation.endswith(" ") else ", "
904+
type_annotation = f"{type_annotation}{separator}{formatted_default}"
868905

869906
return type_annotation
870907

tests/roots/test-dummy/dummy_module_without_complete_typehints.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,14 @@ def function_with_defaults_and_some_typehints(x: int = 0, y=None) -> str: # noq
3535
:param x: foo
3636
:param y: bar
3737
"""
38+
39+
40+
def function_with_defaults_and_type_information_in_docstring(x, y=0) -> str: # noqa: ANN001
41+
"""
42+
Function docstring.
43+
44+
:type x: int
45+
:type y: int
46+
:param x: foo
47+
:param y: bar
48+
"""

tests/test_sphinx_autodoc_typehints.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,18 @@ def test_default_annotation_without_typehints(app: SphinxTestApp, status: String
10741074
10751075
Return type:
10761076
"str"
1077+
1078+
dummy_module_without_complete_typehints.function_with_defaults_and_type_information_in_docstring(x, y=0)
1079+
1080+
Function docstring.
1081+
1082+
Parameters:
1083+
* **x** ("int") -- foo
1084+
1085+
* **y** ("int", default: "0") -- bar
1086+
1087+
Return type:
1088+
"str"
10771089
"""
10781090
assert text_contents == dedent(expected_contents)
10791091

0 commit comments

Comments
 (0)