@@ -138,7 +138,7 @@ def _replace_placeholders(
138138
139139def _instrument (
140140 strings : tuple [str , ...], callable_infos : tuple [CallableInfo | None , ...]
141- ) -> t . Iterable [ str ]:
141+ ) -> tuple [ list [ str ], dict [ str , int ] ]:
142142 """
143143 Join the strings with placeholders in between where interpolations go.
144144
@@ -150,24 +150,24 @@ def _instrument(
150150 """
151151 count = len (strings )
152152
153- callable_placeholders : dict [int , str ] = {}
153+ placeholder_callables : dict [str , int ] = {}
154154
155+ parts = []
155156 for i , s in enumerate (strings ):
156- yield s
157+ parts . append ( s )
157158 # There are always count-1 placeholders between count strings.
158159 if i < count - 1 :
159160 placeholder = _placeholder (i )
160161
161162 # Special case for component callables: if the interpolation
162163 # is a callable, we need to make sure that any matching closing
163- # tag uses the same placeholder.
164+ # tag's placeholder is the same callable
164165 callable_info = callable_infos [i ]
165166 if callable_info :
166- placeholder = callable_placeholders .setdefault (
167- callable_info .id , placeholder
168- )
167+ placeholder_callables [placeholder ] = callable_info .id
169168
170- yield placeholder
169+ parts .append (placeholder )
170+ return parts , placeholder_callables
171171
172172
173173@lru_cache (maxsize = 0 if "pytest" in sys .modules else 512 )
@@ -190,8 +190,8 @@ def _instrument_and_parse(cached_template: CachedTemplate) -> Node:
190190 callable_infos = tuple (
191191 _callable_info (interpolation .value ) for interpolation in template .interpolations
192192 )
193- instrumented = _instrument (template .strings , callable_infos )
194- return parse_html (instrumented )
193+ instrumented , placeholder_callables = _instrument (template .strings , callable_infos )
194+ return parse_html (instrumented , placeholder_callables = placeholder_callables )
195195
196196
197197def _callable_info (value : object ) -> CallableInfo | None :
@@ -522,9 +522,26 @@ def _substitute_node(p_node: Node, interpolations: tuple[Interpolation, ...]) ->
522522 interpolation = interpolations [index ]
523523 value = format_interpolation (interpolation )
524524 return _node_from_value (value )
525- case Element (tag = tag , attrs = attrs , children = children ):
525+ case Element (
526+ tag = tag , attrs = attrs , children = children , component_info = component_info
527+ ):
526528 new_children = _substitute_and_flatten_children (children , interpolations )
527529 if (index := _find_placeholder (tag )) is not None :
530+ if component_info is None :
531+ raise TypeError (
532+ "Only callables can be used for interpolations located in tags."
533+ )
534+ elif component_info .endtag is not None :
535+ end_index = _find_placeholder (component_info .endtag )
536+ if end_index is None :
537+ # Avoid typecheck errors.
538+ raise ValueError (
539+ "The endtag of a component must be an interpolation."
540+ )
541+ elif interpolations [index ].value != interpolations [end_index ].value :
542+ raise ValueError (
543+ "The endtag interpolation's value should match the starttag interpolation's value."
544+ )
528545 component_attrs = _substitute_interpolated_attrs (attrs , interpolations )
529546 return _invoke_component (
530547 component_attrs , new_children , interpolations [index ]
0 commit comments