@@ -247,7 +247,7 @@ impl<'src> Parser<'src> {
247247 self . error_if_not_basedpython (
248248 "`protocol` class syntax is not valid in .py files" . to_string ( ) ,
249249 ) ;
250- return Some ( self . parse_protocol_def ( start) ) ;
250+ return Some ( self . parse_protocol_def ( start, DecoratorList :: new ( ) ) ) ;
251251 }
252252 // `enum class E:` / `enum class E[T]:` — a "based enum" (an algebraic
253253 // sum type when its body has payload variants, an idiomatic `Enum` when
@@ -258,7 +258,7 @@ impl<'src> Parser<'src> {
258258 self . error_if_not_basedpython (
259259 "`enum class` declarations are not valid in .py files" . to_string ( ) ,
260260 ) ;
261- return Some ( self . parse_enum_def ( start) ) ;
261+ return Some ( self . parse_enum_def ( start, DecoratorList :: new ( ) ) ) ;
262262 }
263263 // a bare `enum E:` (no `class`) is not valid — based enums are written
264264 // `enum class E:`. report it but still parse the body for recovery
@@ -269,7 +269,7 @@ impl<'src> Parser<'src> {
269269 ) ,
270270 self . current_token_range ( ) ,
271271 ) ;
272- return Some ( self . parse_enum_def ( start) ) ;
272+ return Some ( self . parse_enum_def ( start, DecoratorList :: new ( ) ) ) ;
273273 }
274274 if kw == "decorator" && self . peek ( ) == TokenKind :: Def {
275275 self . error_if_not_basedpython (
@@ -358,6 +358,18 @@ impl<'src> Parser<'src> {
358358 } else {
359359 self . peek_nth ( idx) . 0
360360 } ;
361+ // an introducer keyword (`enum class`, `protocol`) after a
362+ // modifier chain — `private enum class E:`, `export protocol P:`.
363+ // dispatch through the modifier path so the chain is carried
364+ // as decorators on the introduced class
365+ if ( text == "protocol" && following == TokenKind :: Name )
366+ || ( text == "enum" && following == TokenKind :: Class )
367+ {
368+ self . error_if_not_basedpython ( format ! (
369+ "`{kw}` is a basedpython modifier and is not valid in .py files"
370+ ) ) ;
371+ return Some ( self . parse_with_modifier ( start, DecoratorList :: new ( ) ) ) ;
372+ }
361373 return match following {
362374 TokenKind :: Equal if idx > 0 => {
363375 self . error_if_not_basedpython ( format ! (
@@ -524,7 +536,8 @@ impl<'src> Parser<'src> {
524536 if self . at ( TokenKind :: Def ) || self . at ( TokenKind :: Class ) || self . at ( TokenKind :: Async ) {
525537 break ;
526538 }
527- // another modifier keyword follows — keep looping
539+ // another modifier keyword follows — keep looping. an `enum class` /
540+ // `protocol` introducer also ends the chain (handled after the loop)
528541 if self . at ( TokenKind :: Name ) {
529542 let kw = self . src_text ( self . current_token_range ( ) ) ;
530543 // any modifier keyword may follow another, in any order — reuse
@@ -548,6 +561,20 @@ impl<'src> Parser<'src> {
548561 } )
549562 } else if self . at ( TokenKind :: Def ) {
550563 Stmt :: FunctionDef ( self . parse_function_definition ( decorators, start) )
564+ } else if self . at ( TokenKind :: Name )
565+ && self . src_text ( self . current_token_range ( ) ) == "protocol"
566+ && self . peek ( ) == TokenKind :: Name
567+ {
568+ // `private protocol P:` — the modifier decorators ride alongside the
569+ // synthetic `protocol_class` marker; both lower by disjoint edits
570+ self . parse_protocol_def ( start, decorators)
571+ } else if self . at ( TokenKind :: Name )
572+ && self . src_text ( self . current_token_range ( ) ) == "enum"
573+ && self . peek ( ) == TokenKind :: Class
574+ {
575+ // `private enum class E:` — the enum lowering reads the visibility
576+ // markers to prefix its synthesized `class` line
577+ self . parse_enum_def ( start, decorators)
551578 } else {
552579 Stmt :: ClassDef ( self . parse_class_definition ( decorators, start) )
553580 }
@@ -853,13 +880,16 @@ impl<'src> Parser<'src> {
853880 /// requiring an explicit `class` keyword. Produces a `ClassDef` with a synthetic
854881 /// `Decorator` (name `"protocol_class"`) that the `modifiers` transform rewrites
855882 /// to `class Foo(Protocol):`.
856- fn parse_protocol_def ( & mut self , start : TextSize ) -> Stmt {
883+ fn parse_protocol_def ( & mut self , start : TextSize , mut decorators : DecoratorList ) -> Stmt {
857884 let protocol_start = self . current_token_range ( ) . start ( ) ;
858885 self . bump ( TokenKind :: Name ) ; // consume "protocol"
859886 let class_name_start = self . current_token_range ( ) . start ( ) ;
860887 let decorator_range = TextRange :: new ( protocol_start, class_name_start) ;
861888
862- let decorator = ast:: Decorator {
889+ // the synthetic `protocol_class` marker follows any modifier decorators
890+ // (`private protocol P:`); the `modifiers` transform consumes each by a
891+ // disjoint range edit, so order between them does not matter
892+ decorators. push ( ast:: Decorator {
863893 expression : Expr :: Name ( ast:: ExprName {
864894 id : Name :: new_static ( "protocol_class" ) ,
865895 ctx : ExprContext :: Invalid ,
@@ -868,7 +898,7 @@ impl<'src> Parser<'src> {
868898 } ) ,
869899 range : decorator_range,
870900 node_index : AtomicNodeIndex :: NONE ,
871- } ;
901+ } ) ;
872902
873903 let name = self . parse_identifier ( ) ;
874904 let type_params = self . try_parse_type_params ( ) ;
@@ -884,7 +914,7 @@ impl<'src> Parser<'src> {
884914
885915 Stmt :: ClassDef ( ast:: StmtClassDef {
886916 range : self . node_range ( start) ,
887- decorator_list : vec ! [ decorator ] . into ( ) ,
917+ decorator_list : decorators ,
888918 name,
889919 type_params : type_params. map ( Box :: new) ,
890920 arguments,
@@ -903,7 +933,7 @@ impl<'src> Parser<'src> {
903933 ///
904934 /// [`ClassDef`]: ast::StmtClassDef
905935 /// [`AnnAssign`]: ast::StmtAnnAssign
906- fn parse_enum_def ( & mut self , start : TextSize ) -> Stmt {
936+ fn parse_enum_def ( & mut self , start : TextSize , mut decorators : DecoratorList ) -> Stmt {
907937 let enum_start = self . current_token_range ( ) . start ( ) ;
908938 self . bump ( TokenKind :: Name ) ; // consume "enum"
909939 // the canonical surface is `enum class E:`; the `class` keyword is part
@@ -913,7 +943,11 @@ impl<'src> Parser<'src> {
913943 let name_start = self . current_token_range ( ) . start ( ) ;
914944 let decorator_range = TextRange :: new ( enum_start, name_start) ;
915945
916- let decorator = ast:: Decorator {
946+ // the synthetic `enum_def` marker follows any modifier decorators
947+ // (`private enum class E:`); the enum lowering re-emits the enum from
948+ // scratch and reads the visibility markers to prefix the synthesized
949+ // `class` line, so the standard `private`/`export` class path applies
950+ decorators. push ( ast:: Decorator {
917951 expression : Expr :: Name ( ast:: ExprName {
918952 id : Name :: new_static ( "enum_def" ) ,
919953 ctx : ExprContext :: Invalid ,
@@ -922,7 +956,7 @@ impl<'src> Parser<'src> {
922956 } ) ,
923957 range : decorator_range,
924958 node_index : AtomicNodeIndex :: NONE ,
925- } ;
959+ } ) ;
926960
927961 let name = self . parse_identifier ( ) ;
928962 let type_params = self . try_parse_type_params ( ) ;
@@ -952,7 +986,7 @@ impl<'src> Parser<'src> {
952986
953987 Stmt :: ClassDef ( ast:: StmtClassDef {
954988 range : self . node_range ( start) ,
955- decorator_list : vec ! [ decorator ] . into ( ) ,
989+ decorator_list : decorators ,
956990 name,
957991 type_params : type_params. map ( Box :: new) ,
958992 arguments : None ,
0 commit comments