@@ -766,32 +766,68 @@ impl<'db> UseDefMapBuilder<'db> {
766766 . add_and_constraint ( self . scope_start_visibility , constraint) ;
767767 }
768768
769- #[ must_use = "A `*`-import visibility constraint must always be negated after it is added" ]
770- pub ( super ) fn record_star_import_visibility_constraint (
769+ /// This method exists solely as a fast path for handling `*`-import visibility constraints.
770+ ///
771+ /// The reason why we add visibility constraints for [`Definition`]s created by `*` imports
772+ /// is laid out in the doc-comment for [`StarImportPlaceholderPredicate`]. But treating these
773+ /// visibility constraints in the use-def map the same way as all other visibility constraints
774+ /// was shown to lead to [significant regressions] for small codebases where typeshed
775+ /// dominates. (Although `*` imports are not common generally, they are used in several
776+ /// important places by typeshed.)
777+ ///
778+ /// To solve these regressions, it was observed that we could add a fast path for `*`-import
779+ /// definitions which added a new symbol to the global scope (as opposed to `*`-import definitions
780+ /// that provided redefinitions for *pre-existing* global-scope symbols). The fast path does a
781+ /// number of things differently to our normal handling of visibility constraints:
782+ ///
783+ /// - It only applies and negates the visibility constraints to a single symbol, rather than to
784+ /// all symbols. This is possible here because, unlike most definitions, we know in advance that
785+ /// exactly one definition occurs inside the "if-true" predicate branch, and we know exactly
786+ /// which definition it is.
787+ ///
788+ /// Doing things this way is cheaper in and of itself. However, it also allows us to avoid
789+ /// calling [`Self::simplify_visibility_constraints`] after the constraint has been applied to
790+ /// the "if-predicate-true" branch and negated for the "if-predicate-false" branch. Simplifying
791+ /// the visibility constraints is only important for symbols that did not have any new
792+ /// definitions inside either the "if-predicate-true" branch or the "if-predicate-false" branch.
793+ ///
794+ /// - It avoids multiple expensive calls to [`Self::snapshot`]. This is possible because we know
795+ /// the symbol is newly added, so we know the prior state of the symbol was
796+ /// [`SymbolState::undefined`].
797+ ///
798+ /// - Normally we take care to check whether an "if-predicate-true" branch or an
799+ /// "if-predicate-false" branch contains a terminal statement: these can affect the visibility
800+ /// of symbols defined inside either branch. However, in the case of `*`-import definitions,
801+ /// this is unnecessary (and therefore not done in this method), since we know that a `*`-import
802+ /// predicate cannot create a terminal statement inside either branch.
803+ ///
804+ /// [significant regressions]: https://github.com/astral-sh/ruff/pull/17286#issuecomment-2786755746
805+ pub ( super ) fn record_and_negate_star_import_visibility_constraint (
771806 & mut self ,
772807 star_import : StarImportPlaceholderPredicate < ' db > ,
773808 symbol : ScopedSymbolId ,
774- ) -> StarImportVisibilityConstraintId {
809+ ) {
775810 let predicate_id = self . add_predicate ( star_import. into ( ) ) ;
776811 let visibility_id = self . visibility_constraints . add_atom ( predicate_id) ;
777- self . symbol_states [ symbol]
812+ let negated_visibility_id = self
813+ . visibility_constraints
814+ . add_not_constraint ( visibility_id) ;
815+
816+ let mut post_definition_state = std:: mem:: replace (
817+ & mut self . symbol_states [ symbol] ,
818+ SymbolState :: undefined ( self . scope_start_visibility ) ,
819+ ) ;
820+ post_definition_state
778821 . record_visibility_constraint ( & mut self . visibility_constraints , visibility_id) ;
779- StarImportVisibilityConstraintId ( visibility_id)
780- }
781822
782- pub ( super ) fn negate_star_import_visibility_constraint (
783- & mut self ,
784- symbol_id : ScopedSymbolId ,
785- constraint : StarImportVisibilityConstraintId ,
786- ) {
787- let negated_constraint = self
788- . visibility_constraints
789- . add_not_constraint ( constraint. into_scoped_constraint_id ( ) ) ;
790- self . symbol_states [ symbol_id]
791- . record_visibility_constraint ( & mut self . visibility_constraints , negated_constraint) ;
792- self . scope_start_visibility = self
793- . visibility_constraints
794- . add_and_constraint ( self . scope_start_visibility , negated_constraint) ;
823+ self . symbol_states [ symbol]
824+ . record_visibility_constraint ( & mut self . visibility_constraints , negated_visibility_id) ;
825+
826+ self . symbol_states [ symbol] . merge (
827+ post_definition_state,
828+ & mut self . narrowing_constraints ,
829+ & mut self . visibility_constraints ,
830+ ) ;
795831 }
796832
797833 /// This method resets the visibility constraints for all symbols to a previous state
@@ -1042,24 +1078,3 @@ impl<'db> UseDefMapBuilder<'db> {
10421078 }
10431079 }
10441080}
1045-
1046- /// Newtype wrapper over [`ScopedVisibilityConstraintId`] to improve type safety.
1047- ///
1048- /// By returning this type from [`UseDefMapBuilder::record_star_import_visibility_constraint`]
1049- /// rather than [`ScopedVisibilityConstraintId`] directly, we ensure that
1050- /// [`UseDefMapBuilder::negate_star_import_visibility_constraint`] must be called after the
1051- /// visibility constraint has been added, and we ensure that
1052- /// [`super::SemanticIndexBuilder::record_negated_visibility_constraint`] *cannot* be called with
1053- /// the narrowing constraint (which would lead to incorrect behaviour).
1054- ///
1055- /// This type is defined here rather than in the [`super::visibility_constraints`] module
1056- /// because it should only ever be constructed and deconstructed from methods in the
1057- /// [`UseDefMapBuilder`].
1058- #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
1059- pub ( super ) struct StarImportVisibilityConstraintId ( ScopedVisibilityConstraintId ) ;
1060-
1061- impl StarImportVisibilityConstraintId {
1062- fn into_scoped_constraint_id ( self ) -> ScopedVisibilityConstraintId {
1063- self . 0
1064- }
1065- }
0 commit comments