Skip to content

Commit e65bbb9

Browse files
authored
Create a new type for the current document's environment state (#13151)
1 parent c7eaf17 commit e65bbb9

File tree

20 files changed

+427
-139
lines changed

20 files changed

+427
-139
lines changed

doc/extdev/envapi.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,52 @@ Build environment API
4747

4848
.. autoattribute:: parser
4949

50+
**Per-document attributes**
51+
52+
.. attribute:: current_document
53+
54+
Temporary data storage while reading a document.
55+
56+
Extensions may use the mapping interface provided by
57+
``env.current_document`` to store data relating to the current document,
58+
but should use a unique prefix to avoid name clashes.
59+
60+
.. important::
61+
Only the following attributes constitute the public API.
62+
The type itself and any methods or other attributes remain private,
63+
experimental, and will be changed or removed without notice.
64+
65+
.. attribute:: current_document.docname
66+
:type: str
67+
68+
The document name ('docname') for the current document.
69+
70+
.. attribute:: current_document.default_role
71+
:type: str
72+
73+
The default role for the current document.
74+
Set by the :dudir:`default-role` directive.
75+
76+
.. attribute:: current_document.default_domain
77+
:type: Domain | None
78+
79+
The default domain for the current document.
80+
Set by the :rst:dir:`default-domain` directive.
81+
82+
.. attribute:: current_document.highlight_language
83+
:type: str
84+
85+
The default language for syntax highlighting.
86+
Set by the :rst:dir:`highlight` directive to override
87+
the :confval:`highlight_language` config value.
88+
89+
.. attribute:: current_document._parser
90+
:type: Parser | None
91+
92+
*This attribute is experimental and may be changed without notice.*
93+
94+
The parser being used to parse the current document.
95+
5096
**Utility methods**
5197

5298
.. automethod:: doc2path

sphinx/builders/__init__.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313
from docutils import nodes
1414
from docutils.utils import DependencyList
1515

16-
from sphinx.environment import CONFIG_CHANGED_REASON, CONFIG_OK, BuildEnvironment
16+
from sphinx.environment import (
17+
CONFIG_CHANGED_REASON,
18+
CONFIG_OK,
19+
BuildEnvironment,
20+
_CurrentDocument,
21+
)
1722
from sphinx.environment.adapters.asset import ImageAdapter
1823
from sphinx.errors import SphinxError
1924
from sphinx.locale import __
@@ -620,7 +625,7 @@ def read_doc(self, docname: str, *, _cache: bool = True) -> None:
620625
filename = str(self.env.doc2path(docname))
621626
filetype = get_filetype(self.app.config.source_suffix, filename)
622627
publisher = self.app.registry.get_publisher(self.app, filetype)
623-
self.env.temp_data['_parser'] = publisher.parser
628+
self.env.current_document._parser = publisher.parser
624629
# record_dependencies is mutable even though it is in settings,
625630
# explicitly re-initialise for each document
626631
publisher.settings.record_dependencies = DependencyList()
@@ -640,7 +645,7 @@ def read_doc(self, docname: str, *, _cache: bool = True) -> None:
640645
self.env.all_docs[docname] = time.time_ns() // 1_000
641646

642647
# cleanup
643-
self.env.temp_data.clear()
648+
self.env.current_document = _CurrentDocument()
644649
self.env.ref_context.clear()
645650

646651
self.write_doctree(docname, doctree, _cache=_cache)

sphinx/directives/__init__.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -284,9 +284,9 @@ def run(self) -> list[Node]:
284284
# needed for association of version{added,changed} directives
285285
object_name: ObjDescT = self.names[0]
286286
if isinstance(object_name, tuple):
287-
self.env.temp_data['object'] = str(object_name[0])
287+
self.env.current_document.obj_desc_name = str(object_name[0])
288288
else:
289-
self.env.temp_data['object'] = str(object_name)
289+
self.env.current_document.obj_desc_name = str(object_name)
290290
self.before_content()
291291
content_children = self.parse_content_to_nodes(allow_section_headings=True)
292292
content_node = addnodes.desc_content('', *content_children)
@@ -296,7 +296,7 @@ def run(self) -> list[Node]:
296296
'object-description-transform', self.domain, self.objtype, content_node
297297
)
298298
DocFieldTransformer(self).transform_all(content_node)
299-
self.env.temp_data['object'] = ''
299+
self.env.current_document.obj_desc_name = ''
300300
self.after_content()
301301

302302
if node['no-typesetting']:
@@ -335,7 +335,7 @@ def run(self) -> list[Node]:
335335
)
336336
if role:
337337
docutils.register_role('', role) # type: ignore[arg-type]
338-
self.env.temp_data['default_role'] = role_name
338+
self.env.current_document.default_role = role_name
339339
else:
340340
literal_block = nodes.literal_block(self.block_text, self.block_text)
341341
reporter = self.state.reporter
@@ -362,13 +362,8 @@ class DefaultDomain(SphinxDirective):
362362

363363
def run(self) -> list[Node]:
364364
domain_name = self.arguments[0].lower()
365-
# if domain_name not in env.domains:
366-
# # try searching by label
367-
# for domain in env.domains.sorted():
368-
# if domain.label.lower() == domain_name:
369-
# domain_name = domain.name
370-
# break
371-
self.env.temp_data['default_domain'] = self.env.domains.get(domain_name)
365+
default_domain = self.env.domains.get(domain_name)
366+
self.env.current_document.default_domain = default_domain
372367
return []
373368

374369

sphinx/directives/code.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def run(self) -> list[Node]:
4848
linenothreshold = self.options.get('linenothreshold', sys.maxsize)
4949
force = 'force' in self.options
5050

51-
self.env.temp_data['highlight_language'] = language
51+
self.env.current_document.highlight_language = language
5252
return [
5353
addnodes.highlightlang(
5454
lang=language, force=force, linenothreshold=linenothreshold
@@ -159,8 +159,9 @@ def run(self) -> list[Node]:
159159
# no highlight language specified. Then this directive refers the current
160160
# highlight setting via ``highlight`` directive or ``highlight_language``
161161
# configuration.
162-
literal['language'] = self.env.temp_data.get(
163-
'highlight_language', self.config.highlight_language
162+
literal['language'] = (
163+
self.env.current_document.highlight_language
164+
or self.config.highlight_language
164165
)
165166
extra_args = literal['highlight_args'] = {}
166167
if hl_lines is not None:

sphinx/directives/patches.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,9 @@ def run(self) -> list[Node]:
115115
# no highlight language specified. Then this directive refers the current
116116
# highlight setting via ``highlight`` directive or ``highlight_language``
117117
# configuration.
118-
node['language'] = self.env.temp_data.get(
119-
'highlight_language', self.config.highlight_language
118+
node['language'] = (
119+
self.env.current_document.highlight_language
120+
or self.config.highlight_language
120121
)
121122

122123
if 'number-lines' in self.options:

sphinx/domains/c/__init__.py

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -220,19 +220,19 @@ def describe_signature(
220220

221221
def run(self) -> list[Node]:
222222
env = self.state.document.settings.env # from ObjectDescription.run
223-
if 'c:parent_symbol' not in env.temp_data:
223+
if env.current_document.c_parent_symbol is None:
224224
root = env.domaindata['c']['root_symbol']
225-
env.temp_data['c:parent_symbol'] = root
225+
env.current_document.c_parent_symbol = root
226226
env.ref_context['c:parent_key'] = root.get_lookup_key()
227227

228228
# When multiple declarations are made in the same directive
229229
# they need to know about each other to provide symbol lookup for function parameters.
230230
# We use last_symbol to store the latest added declaration in a directive.
231-
env.temp_data['c:last_symbol'] = None
231+
env.current_document.c_last_symbol = None
232232
return super().run()
233233

234234
def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration:
235-
parent_symbol: Symbol = self.env.temp_data['c:parent_symbol']
235+
parent_symbol: Symbol = self.env.current_document.c_parent_symbol
236236

237237
max_len = (
238238
self.env.config.c_maximum_signature_line_length
@@ -254,7 +254,7 @@ def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration:
254254
# the possibly inner declarations.
255255
name = _make_phony_error_name()
256256
symbol = parent_symbol.add_name(name)
257-
self.env.temp_data['c:last_symbol'] = symbol
257+
self.env.current_document.c_last_symbol = symbol
258258
raise ValueError from e
259259

260260
try:
@@ -264,15 +264,15 @@ def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration:
264264
# append the new declaration to the sibling list
265265
assert symbol.siblingAbove is None
266266
assert symbol.siblingBelow is None
267-
symbol.siblingAbove = self.env.temp_data['c:last_symbol']
267+
symbol.siblingAbove = self.env.current_document.c_last_symbol
268268
if symbol.siblingAbove is not None:
269269
assert symbol.siblingAbove.siblingBelow is None
270270
symbol.siblingAbove.siblingBelow = symbol
271-
self.env.temp_data['c:last_symbol'] = symbol
271+
self.env.current_document.c_last_symbol = symbol
272272
except _DuplicateSymbolError as e:
273273
# Assume we are actually in the old symbol,
274274
# instead of the newly created duplicate.
275-
self.env.temp_data['c:last_symbol'] = e.symbol
275+
self.env.current_document.c_last_symbol = e.symbol
276276
msg = __(
277277
'Duplicate C declaration, also defined at %s:%s.\n'
278278
"Declaration is '.. c:%s:: %s'."
@@ -298,15 +298,15 @@ def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration:
298298
return ast
299299

300300
def before_content(self) -> None:
301-
last_symbol: Symbol = self.env.temp_data['c:last_symbol']
301+
last_symbol: Symbol = self.env.current_document.c_last_symbol
302302
assert last_symbol
303-
self.oldParentSymbol = self.env.temp_data['c:parent_symbol']
303+
self.oldParentSymbol = self.env.current_document.c_parent_symbol
304304
self.oldParentKey: LookupKey = self.env.ref_context['c:parent_key']
305-
self.env.temp_data['c:parent_symbol'] = last_symbol
305+
self.env.current_document.c_parent_symbol = last_symbol
306306
self.env.ref_context['c:parent_key'] = last_symbol.get_lookup_key()
307307

308308
def after_content(self) -> None:
309-
self.env.temp_data['c:parent_symbol'] = self.oldParentSymbol
309+
self.env.current_document.c_parent_symbol = self.oldParentSymbol
310310
self.env.ref_context['c:parent_key'] = self.oldParentKey
311311

312312

@@ -410,8 +410,8 @@ def run(self) -> list[Node]:
410410
name = _make_phony_error_name()
411411
symbol = root_symbol.add_name(name)
412412
stack = [symbol]
413-
self.env.temp_data['c:parent_symbol'] = symbol
414-
self.env.temp_data['c:namespace_stack'] = stack
413+
self.env.current_document.c_parent_symbol = symbol
414+
self.env.current_document.c_namespace_stack = stack
415415
self.env.ref_context['c:parent_key'] = symbol.get_lookup_key()
416416
return []
417417

@@ -435,14 +435,12 @@ def run(self) -> list[Node]:
435435
except DefinitionError as e:
436436
logger.warning(e, location=self.get_location())
437437
name = _make_phony_error_name()
438-
old_parent = self.env.temp_data.get('c:parent_symbol', None)
438+
old_parent = self.env.current_document.c_parent_symbol
439439
if not old_parent:
440440
old_parent = self.env.domaindata['c']['root_symbol']
441441
symbol = old_parent.add_name(name)
442-
stack = self.env.temp_data.get('c:namespace_stack', [])
443-
stack.append(symbol)
444-
self.env.temp_data['c:parent_symbol'] = symbol
445-
self.env.temp_data['c:namespace_stack'] = stack
442+
self.env.current_document.c_namespace_stack.append(symbol)
443+
self.env.current_document.c_parent_symbol = symbol
446444
self.env.ref_context['c:parent_key'] = symbol.get_lookup_key()
447445
return []
448446

@@ -455,21 +453,19 @@ class CNamespacePopObject(SphinxDirective):
455453
option_spec: ClassVar[OptionSpec] = {}
456454

457455
def run(self) -> list[Node]:
458-
stack = self.env.temp_data.get('c:namespace_stack', None)
459-
if not stack or len(stack) == 0:
456+
stack = self.env.current_document.c_namespace_stack
457+
if len(stack) == 0:
460458
logger.warning(
461459
'C namespace pop on empty stack. Defaulting to global scope.',
462460
location=self.get_location(),
463461
)
464-
stack = []
465462
else:
466463
stack.pop()
467464
if len(stack) > 0:
468465
symbol = stack[-1]
469466
else:
470467
symbol = self.env.domaindata['c']['root_symbol']
471-
self.env.temp_data['c:parent_symbol'] = symbol
472-
self.env.temp_data['c:namespace_stack'] = stack
468+
self.env.current_document.c_parent_symbol = symbol
473469
self.env.ref_context['c:parent_key'] = symbol.get_lookup_key()
474470
return []
475471

@@ -488,9 +484,9 @@ def __init__(
488484
self.aliasOptions = aliasOptions
489485
self.document = document
490486
if env is not None:
491-
if 'c:parent_symbol' not in env.temp_data:
487+
if env.current_document.c_parent_symbol is None:
492488
root = env.domaindata['c']['root_symbol']
493-
env.temp_data['c:parent_symbol'] = root
489+
env.current_document.c_parent_symbol = root
494490
env.ref_context['c:parent_key'] = root.get_lookup_key()
495491
self.parentKey = env.ref_context['c:parent_key']
496492
else:
@@ -735,7 +731,7 @@ def run(self) -> tuple[list[Node], list[system_message]]:
735731
# see below
736732
node = addnodes.desc_inline('c', text, text, classes=[self.class_type])
737733
return [node], []
738-
parent_symbol = self.env.temp_data.get('c:parent_symbol', None)
734+
parent_symbol = self.env.current_document.c_parent_symbol
739735
if parent_symbol is None:
740736
parent_symbol = self.env.domaindata['c']['root_symbol']
741737
# ...most if not all of these classes should really apply to the individual references,

sphinx/domains/changeset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def changesets(self) -> dict[str, list[ChangeSet]]:
133133
def note_changeset(self, node: addnodes.versionmodified) -> None:
134134
version = node['version']
135135
module = self.env.ref_context.get('py:module')
136-
objname = self.env.temp_data.get('object', '')
136+
objname = self.env.current_document.obj_desc_name
137137
changeset = ChangeSet(
138138
node['type'],
139139
self.env.docname,

0 commit comments

Comments
 (0)