Skip to content

Commit be54b84

Browse files
authored
[red-knot] Simplify visibility constraint handling for *-import definitions (#17486)
1 parent 45b5ded commit be54b84

File tree

3 files changed

+49
-55
lines changed

3 files changed

+49
-55
lines changed

crates/red_knot_python_semantic/src/semantic_index/builder.rs

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -355,15 +355,14 @@ impl<'db> SemanticIndexBuilder<'db> {
355355
self.current_use_def_map_mut().merge(state);
356356
}
357357

358-
/// Return a 2-element tuple, where the first element is the [`ScopedSymbolId`] of the
359-
/// symbol added, and the second element is a boolean indicating whether the symbol was *newly*
360-
/// added or not
361-
fn add_symbol(&mut self, name: Name) -> (ScopedSymbolId, bool) {
358+
/// Add a symbol to the symbol table and the use-def map.
359+
/// Return the [`ScopedSymbolId`] that uniquely identifies the symbol in both.
360+
fn add_symbol(&mut self, name: Name) -> ScopedSymbolId {
362361
let (symbol_id, added) = self.current_symbol_table().add_symbol(name);
363362
if added {
364363
self.current_use_def_map_mut().add_symbol(symbol_id);
365364
}
366-
(symbol_id, added)
365+
symbol_id
367366
}
368367

369368
fn add_attribute(&mut self, name: Name) -> ScopedSymbolId {
@@ -797,7 +796,7 @@ impl<'db> SemanticIndexBuilder<'db> {
797796
..
798797
}) => (name, &None, default),
799798
};
800-
let (symbol, _) = self.add_symbol(name.id.clone());
799+
let symbol = self.add_symbol(name.id.clone());
801800
// TODO create Definition for PEP 695 typevars
802801
// note that the "bound" on the typevar is a totally different thing than whether
803802
// or not a name is "bound" by a typevar declaration; the latter is always true.
@@ -895,20 +894,20 @@ impl<'db> SemanticIndexBuilder<'db> {
895894
self.declare_parameter(parameter);
896895
}
897896
if let Some(vararg) = parameters.vararg.as_ref() {
898-
let (symbol, _) = self.add_symbol(vararg.name.id().clone());
897+
let symbol = self.add_symbol(vararg.name.id().clone());
899898
self.add_definition(
900899
symbol,
901900
DefinitionNodeRef::VariadicPositionalParameter(vararg),
902901
);
903902
}
904903
if let Some(kwarg) = parameters.kwarg.as_ref() {
905-
let (symbol, _) = self.add_symbol(kwarg.name.id().clone());
904+
let symbol = self.add_symbol(kwarg.name.id().clone());
906905
self.add_definition(symbol, DefinitionNodeRef::VariadicKeywordParameter(kwarg));
907906
}
908907
}
909908

910909
fn declare_parameter(&mut self, parameter: &'db ast::ParameterWithDefault) {
911-
let (symbol, _) = self.add_symbol(parameter.name().id().clone());
910+
let symbol = self.add_symbol(parameter.name().id().clone());
912911

913912
let definition = self.add_definition(symbol, parameter);
914913

@@ -1139,7 +1138,7 @@ where
11391138
// The symbol for the function name itself has to be evaluated
11401139
// at the end to match the runtime evaluation of parameter defaults
11411140
// and return-type annotations.
1142-
let (symbol, _) = self.add_symbol(name.id.clone());
1141+
let symbol = self.add_symbol(name.id.clone());
11431142

11441143
// Record a use of the function name in the scope that it is defined in, so that it
11451144
// can be used to find previously defined functions with the same name. This is
@@ -1174,11 +1173,11 @@ where
11741173
);
11751174

11761175
// In Python runtime semantics, a class is registered after its scope is evaluated.
1177-
let (symbol, _) = self.add_symbol(class.name.id.clone());
1176+
let symbol = self.add_symbol(class.name.id.clone());
11781177
self.add_definition(symbol, class);
11791178
}
11801179
ast::Stmt::TypeAlias(type_alias) => {
1181-
let (symbol, _) = self.add_symbol(
1180+
let symbol = self.add_symbol(
11821181
type_alias
11831182
.name
11841183
.as_name_expr()
@@ -1215,7 +1214,7 @@ where
12151214
(Name::new(alias.name.id.split('.').next().unwrap()), false)
12161215
};
12171216

1218-
let (symbol, _) = self.add_symbol(symbol_name);
1217+
let symbol = self.add_symbol(symbol_name);
12191218
self.add_definition(
12201219
symbol,
12211220
ImportDefinitionNodeRef {
@@ -1286,7 +1285,7 @@ where
12861285
//
12871286
// For more details, see the doc-comment on `StarImportPlaceholderPredicate`.
12881287
for export in exported_names(self.db, referenced_module) {
1289-
let (symbol_id, newly_added) = self.add_symbol(export.clone());
1288+
let symbol_id = self.add_symbol(export.clone());
12901289
let node_ref = StarImportDefinitionNodeRef { node, symbol_id };
12911290
let star_import = StarImportPlaceholderPredicate::new(
12921291
self.db,
@@ -1295,28 +1294,15 @@ where
12951294
referenced_module,
12961295
);
12971296

1298-
// Fast path for if there were no previous definitions
1299-
// of the symbol defined through the `*` import:
1300-
// we can apply the visibility constraint to *only* the added definition,
1301-
// rather than all definitions
1302-
if newly_added {
1303-
self.push_additional_definition(symbol_id, node_ref);
1304-
self.current_use_def_map_mut()
1305-
.record_and_negate_star_import_visibility_constraint(
1306-
star_import,
1307-
symbol_id,
1308-
);
1309-
} else {
1310-
let pre_definition = self.flow_snapshot();
1311-
self.push_additional_definition(symbol_id, node_ref);
1312-
let constraint_id =
1313-
self.record_visibility_constraint(star_import.into());
1314-
let post_definition = self.flow_snapshot();
1315-
self.flow_restore(pre_definition.clone());
1316-
self.record_negated_visibility_constraint(constraint_id);
1317-
self.flow_merge(post_definition);
1318-
self.simplify_visibility_constraints(pre_definition);
1319-
}
1297+
let pre_definition =
1298+
self.current_use_def_map().single_symbol_snapshot(symbol_id);
1299+
self.push_additional_definition(symbol_id, node_ref);
1300+
self.current_use_def_map_mut()
1301+
.record_and_negate_star_import_visibility_constraint(
1302+
star_import,
1303+
symbol_id,
1304+
pre_definition,
1305+
);
13201306
}
13211307

13221308
continue;
@@ -1336,7 +1322,7 @@ where
13361322
self.has_future_annotations |= alias.name.id == "annotations"
13371323
&& node.module.as_deref() == Some("__future__");
13381324

1339-
let (symbol, _) = self.add_symbol(symbol_name.clone());
1325+
let symbol = self.add_symbol(symbol_name.clone());
13401326

13411327
self.add_definition(
13421328
symbol,
@@ -1754,7 +1740,7 @@ where
17541740
// which is invalid syntax. However, it's still pretty obvious here that the user
17551741
// *wanted* `e` to be bound, so we should still create a definition here nonetheless.
17561742
if let Some(symbol_name) = symbol_name {
1757-
let (symbol, _) = self.add_symbol(symbol_name.id.clone());
1743+
let symbol = self.add_symbol(symbol_name.id.clone());
17581744

17591745
self.add_definition(
17601746
symbol,
@@ -1841,7 +1827,7 @@ where
18411827
(ast::ExprContext::Del, _) => (false, true),
18421828
(ast::ExprContext::Invalid, _) => (false, false),
18431829
};
1844-
let (symbol, _) = self.add_symbol(id.clone());
1830+
let symbol = self.add_symbol(id.clone());
18451831

18461832
if is_use {
18471833
self.mark_symbol_used(symbol);
@@ -2245,7 +2231,7 @@ where
22452231
range: _,
22462232
}) = pattern
22472233
{
2248-
let (symbol, _) = self.add_symbol(name.id().clone());
2234+
let symbol = self.add_symbol(name.id().clone());
22492235
let state = self.current_match_case.as_ref().unwrap();
22502236
self.add_definition(
22512237
symbol,
@@ -2266,7 +2252,7 @@ where
22662252
rest: Some(name), ..
22672253
}) = pattern
22682254
{
2269-
let (symbol, _) = self.add_symbol(name.id().clone());
2255+
let symbol = self.add_symbol(name.id().clone());
22702256
let state = self.current_match_case.as_ref().unwrap();
22712257
self.add_definition(
22722258
symbol,

crates/red_knot_python_semantic/src/semantic_index/use_def.rs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,16 @@ impl<'db> UseDefMapBuilder<'db> {
775775
.add_and_constraint(self.scope_start_visibility, constraint);
776776
}
777777

778-
/// This method exists solely as a fast path for handling `*`-import visibility constraints.
778+
/// Snapshot the state of a single symbol at the current point in control flow.
779+
///
780+
/// This is only used for `*`-import visibility constraints, which are handled differently
781+
/// to most other visibility constraints. See the doc-comment for
782+
/// [`Self::record_and_negate_star_import_visibility_constraint`] for more details.
783+
pub(super) fn single_symbol_snapshot(&self, symbol: ScopedSymbolId) -> SymbolState {
784+
self.symbol_states[symbol].clone()
785+
}
786+
787+
/// This method exists solely for handling `*`-import visibility constraints.
779788
///
780789
/// The reason why we add visibility constraints for [`Definition`]s created by `*` imports
781790
/// is laid out in the doc-comment for [`StarImportPlaceholderPredicate`]. But treating these
@@ -784,12 +793,11 @@ impl<'db> UseDefMapBuilder<'db> {
784793
/// dominates. (Although `*` imports are not common generally, they are used in several
785794
/// important places by typeshed.)
786795
///
787-
/// To solve these regressions, it was observed that we could add a fast path for `*`-import
788-
/// definitions which added a new symbol to the global scope (as opposed to `*`-import definitions
789-
/// that provided redefinitions for *pre-existing* global-scope symbols). The fast path does a
790-
/// number of things differently to our normal handling of visibility constraints:
796+
/// To solve these regressions, it was observed that we could do significantly less work for
797+
/// `*`-import definitions. We do a number of things differently here to our normal handling of
798+
/// visibility constraints:
791799
///
792-
/// - It only applies and negates the visibility constraints to a single symbol, rather than to
800+
/// - We only apply and negate the visibility constraints to a single symbol, rather than to
793801
/// all symbols. This is possible here because, unlike most definitions, we know in advance that
794802
/// exactly one definition occurs inside the "if-true" predicate branch, and we know exactly
795803
/// which definition it is.
@@ -800,9 +808,9 @@ impl<'db> UseDefMapBuilder<'db> {
800808
/// the visibility constraints is only important for symbols that did not have any new
801809
/// definitions inside either the "if-predicate-true" branch or the "if-predicate-false" branch.
802810
///
803-
/// - It avoids multiple expensive calls to [`Self::snapshot`]. This is possible because we know
804-
/// the symbol is newly added, so we know the prior state of the symbol was
805-
/// [`SymbolState::undefined`].
811+
/// - We only snapshot the state for a single symbol prior to the definition, rather than doing
812+
/// expensive calls to [`Self::snapshot`]. Again, this is possible because we know
813+
/// that only a single definition occurs inside the "if-predicate-true" predicate branch.
806814
///
807815
/// - Normally we take care to check whether an "if-predicate-true" branch or an
808816
/// "if-predicate-false" branch contains a terminal statement: these can affect the visibility
@@ -815,17 +823,17 @@ impl<'db> UseDefMapBuilder<'db> {
815823
&mut self,
816824
star_import: StarImportPlaceholderPredicate<'db>,
817825
symbol: ScopedSymbolId,
826+
pre_definition_state: SymbolState,
818827
) {
819828
let predicate_id = self.add_predicate(star_import.into());
820829
let visibility_id = self.visibility_constraints.add_atom(predicate_id);
821830
let negated_visibility_id = self
822831
.visibility_constraints
823832
.add_not_constraint(visibility_id);
824833

825-
let mut post_definition_state = std::mem::replace(
826-
&mut self.symbol_states[symbol],
827-
SymbolState::undefined(self.scope_start_visibility),
828-
);
834+
let mut post_definition_state =
835+
std::mem::replace(&mut self.symbol_states[symbol], pre_definition_state);
836+
829837
post_definition_state
830838
.record_visibility_constraint(&mut self.visibility_constraints, visibility_id);
831839

crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ impl SymbolBindings {
314314
}
315315

316316
#[derive(Clone, Debug, PartialEq, Eq)]
317-
pub(super) struct SymbolState {
317+
pub(in crate::semantic_index) struct SymbolState {
318318
declarations: SymbolDeclarations,
319319
bindings: SymbolBindings,
320320
}

0 commit comments

Comments
 (0)