@@ -2360,43 +2360,7 @@ impl Compiler {
23602360 ..
23612361 } ) => {
23622362 self . enter_conditional_block ( ) ;
2363- match elif_else_clauses. as_slice ( ) {
2364- // Only if
2365- [ ] => {
2366- let after_block = self . new_block ( ) ;
2367- self . compile_jump_if ( test, false , after_block) ?;
2368- self . compile_statements ( body) ?;
2369- self . switch_to_block ( after_block) ;
2370- }
2371- // If, elif*, elif/else
2372- [ rest @ .., tail] => {
2373- let after_block = self . new_block ( ) ;
2374- let mut next_block = self . new_block ( ) ;
2375-
2376- self . compile_jump_if ( test, false , next_block) ?;
2377- self . compile_statements ( body) ?;
2378- emit ! ( self , PseudoInstruction :: Jump { delta: after_block } ) ;
2379-
2380- for clause in rest {
2381- self . switch_to_block ( next_block) ;
2382- next_block = self . new_block ( ) ;
2383- if let Some ( test) = & clause. test {
2384- self . compile_jump_if ( test, false , next_block) ?;
2385- } else {
2386- unreachable ! ( ) // must be elif
2387- }
2388- self . compile_statements ( & clause. body ) ?;
2389- emit ! ( self , PseudoInstruction :: Jump { delta: after_block } ) ;
2390- }
2391-
2392- self . switch_to_block ( next_block) ;
2393- if let Some ( test) = & tail. test {
2394- self . compile_jump_if ( test, false , after_block) ?;
2395- }
2396- self . compile_statements ( & tail. body ) ?;
2397- self . switch_to_block ( after_block) ;
2398- }
2399- }
2363+ self . compile_if ( test, body, elif_else_clauses) ?;
24002364 self . leave_conditional_block ( ) ;
24012365 }
24022366 ast:: Stmt :: While ( ast:: StmtWhile {
@@ -3899,6 +3863,22 @@ impl Compiler {
38993863 // Set qualname
39003864 self . set_qualname ( ) ;
39013865
3866+ // PEP 479: Wrap generator/coroutine body with StopIteration handler
3867+ let is_gen = is_async || self . current_symbol_table ( ) . is_generator ;
3868+ let stop_iteration_block = if is_gen {
3869+ let handler_block = self . new_block ( ) ;
3870+ emit ! (
3871+ self ,
3872+ PseudoInstruction :: SetupCleanup {
3873+ delta: handler_block
3874+ }
3875+ ) ;
3876+ self . push_fblock ( FBlockType :: StopIteration , handler_block, handler_block) ?;
3877+ Some ( handler_block)
3878+ } else {
3879+ None
3880+ } ;
3881+
39023882 // Handle docstring - store in co_consts[0] if present
39033883 let ( doc_str, body) = split_doc ( body, & self . opts ) ;
39043884 if let Some ( doc) = & doc_str {
@@ -3929,6 +3909,20 @@ impl Compiler {
39293909 self . arg_constant ( ConstantData :: None ) ;
39303910 }
39313911
3912+ // Close StopIteration handler and emit handler code
3913+ if let Some ( handler_block) = stop_iteration_block {
3914+ emit ! ( self , PseudoInstruction :: PopBlock ) ;
3915+ self . pop_fblock ( FBlockType :: StopIteration ) ;
3916+ self . switch_to_block ( handler_block) ;
3917+ emit ! (
3918+ self ,
3919+ Instruction :: CallIntrinsic1 {
3920+ func: oparg:: IntrinsicFunction1 :: StopIterationError
3921+ }
3922+ ) ;
3923+ emit ! ( self , Instruction :: Reraise { depth: 1u32 } ) ;
3924+ }
3925+
39323926 // Exit scope and create function object
39333927 let code = self . exit_scope ( ) ;
39343928 self . ctx = prev_ctx;
@@ -5182,6 +5176,84 @@ impl Compiler {
51825176 self . store_name ( name)
51835177 }
51845178
5179+ /// Compile an if statement with constant condition elimination.
5180+ /// = compiler_if in CPython codegen.c
5181+ fn compile_if (
5182+ & mut self ,
5183+ test : & ast:: Expr ,
5184+ body : & [ ast:: Stmt ] ,
5185+ elif_else_clauses : & [ ast:: ElifElseClause ] ,
5186+ ) -> CompileResult < ( ) > {
5187+ let constant = Self :: expr_constant ( test) ;
5188+
5189+ // If the test is constant false, skip the body entirely
5190+ if constant == Some ( false ) {
5191+ // The NOP is emitted by CPython to keep line number info
5192+ self . emit_nop ( ) ;
5193+ // Compile the elif/else chain (if any)
5194+ match elif_else_clauses {
5195+ [ ] => { } // nothing to do
5196+ [ first, rest @ ..] => {
5197+ if let Some ( elif_test) = & first. test {
5198+ // elif: recursively compile with elif as the new if
5199+ self . compile_if ( elif_test, & first. body , rest) ?;
5200+ } else {
5201+ // else: compile the else body directly
5202+ self . compile_statements ( & first. body ) ?;
5203+ }
5204+ }
5205+ }
5206+ return Ok ( ( ) ) ;
5207+ }
5208+
5209+ // If the test is constant true, compile body directly
5210+ if constant == Some ( true ) {
5211+ self . emit_nop ( ) ;
5212+ self . compile_statements ( body) ?;
5213+ return Ok ( ( ) ) ;
5214+ }
5215+
5216+ // Non-constant test: normal compilation
5217+ match elif_else_clauses {
5218+ // Only if
5219+ [ ] => {
5220+ let after_block = self . new_block ( ) ;
5221+ self . compile_jump_if ( test, false , after_block) ?;
5222+ self . compile_statements ( body) ?;
5223+ self . switch_to_block ( after_block) ;
5224+ }
5225+ // If, elif*, elif/else
5226+ [ rest @ .., tail] => {
5227+ let after_block = self . new_block ( ) ;
5228+ let mut next_block = self . new_block ( ) ;
5229+
5230+ self . compile_jump_if ( test, false , next_block) ?;
5231+ self . compile_statements ( body) ?;
5232+ emit ! ( self , PseudoInstruction :: Jump { delta: after_block } ) ;
5233+
5234+ for clause in rest {
5235+ self . switch_to_block ( next_block) ;
5236+ next_block = self . new_block ( ) ;
5237+ if let Some ( test) = & clause. test {
5238+ self . compile_jump_if ( test, false , next_block) ?;
5239+ } else {
5240+ unreachable ! ( ) // must be elif
5241+ }
5242+ self . compile_statements ( & clause. body ) ?;
5243+ emit ! ( self , PseudoInstruction :: Jump { delta: after_block } ) ;
5244+ }
5245+
5246+ self . switch_to_block ( next_block) ;
5247+ if let Some ( test) = & tail. test {
5248+ self . compile_jump_if ( test, false , after_block) ?;
5249+ }
5250+ self . compile_statements ( & tail. body ) ?;
5251+ self . switch_to_block ( after_block) ;
5252+ }
5253+ }
5254+ Ok ( ( ) )
5255+ }
5256+
51855257 fn compile_while (
51865258 & mut self ,
51875259 test : & ast:: Expr ,
@@ -5190,27 +5262,37 @@ impl Compiler {
51905262 ) -> CompileResult < ( ) > {
51915263 self . enter_conditional_block ( ) ;
51925264
5265+ let constant = Self :: expr_constant ( test) ;
5266+
5267+ // while False: body → skip body, compile orelse only
5268+ if constant == Some ( false ) {
5269+ self . emit_nop ( ) ;
5270+ self . compile_statements ( orelse) ?;
5271+ self . leave_conditional_block ( ) ;
5272+ return Ok ( ( ) ) ;
5273+ }
5274+
51935275 let while_block = self . new_block ( ) ;
51945276 let else_block = self . new_block ( ) ;
51955277 let after_block = self . new_block ( ) ;
51965278
5197- // Note: SetupLoop is no longer emitted (break/continue use direct jumps)
51985279 self . switch_to_block ( while_block) ;
5199-
5200- // Push fblock for while loop
52015280 self . push_fblock ( FBlockType :: WhileLoop , while_block, after_block) ?;
52025281
5203- self . compile_jump_if ( test, false , else_block) ?;
5282+ // while True: → no condition test, just NOP
5283+ if constant == Some ( true ) {
5284+ self . emit_nop ( ) ;
5285+ } else {
5286+ self . compile_jump_if ( test, false , else_block) ?;
5287+ }
52045288
52055289 let was_in_loop = self . ctx . loop_data . replace ( ( while_block, after_block) ) ;
52065290 self . compile_statements ( body) ?;
52075291 self . ctx . loop_data = was_in_loop;
52085292 emit ! ( self , PseudoInstruction :: Jump { delta: while_block } ) ;
52095293 self . switch_to_block ( else_block) ;
52105294
5211- // Pop fblock
52125295 self . pop_fblock ( FBlockType :: WhileLoop ) ;
5213- // Note: PopBlock is no longer emitted for loops
52145296 self . compile_statements ( orelse) ?;
52155297 self . switch_to_block ( after_block) ;
52165298
@@ -6998,6 +7080,40 @@ impl Compiler {
69987080 } ) => {
69997081 self . compile_jump_if ( operand, !condition, target_block) ?;
70007082 }
7083+ // `x is None` / `x is not None` → POP_JUMP_IF_NONE / POP_JUMP_IF_NOT_NONE
7084+ ast:: Expr :: Compare ( ast:: ExprCompare {
7085+ left,
7086+ ops,
7087+ comparators,
7088+ ..
7089+ } ) if ops. len ( ) == 1
7090+ && matches ! ( ops[ 0 ] , ast:: CmpOp :: Is | ast:: CmpOp :: IsNot )
7091+ && comparators. len ( ) == 1
7092+ && matches ! ( & comparators[ 0 ] , ast:: Expr :: NoneLiteral ( _) ) =>
7093+ {
7094+ self . compile_expression ( left) ?;
7095+ let is_not = matches ! ( ops[ 0 ] , ast:: CmpOp :: IsNot ) ;
7096+ // is None + jump_if_false → POP_JUMP_IF_NOT_NONE
7097+ // is None + jump_if_true → POP_JUMP_IF_NONE
7098+ // is not None + jump_if_false → POP_JUMP_IF_NONE
7099+ // is not None + jump_if_true → POP_JUMP_IF_NOT_NONE
7100+ let jump_if_none = condition != is_not;
7101+ if jump_if_none {
7102+ emit ! (
7103+ self ,
7104+ Instruction :: PopJumpIfNone {
7105+ delta: target_block,
7106+ }
7107+ ) ;
7108+ } else {
7109+ emit ! (
7110+ self ,
7111+ Instruction :: PopJumpIfNotNone {
7112+ delta: target_block,
7113+ }
7114+ ) ;
7115+ }
7116+ }
70017117 _ => {
70027118 // Fall back case which always will work!
70037119 self . compile_expression ( expression) ?;
@@ -8801,6 +8917,38 @@ impl Compiler {
88018917 self . code_stack . last_mut ( ) . expect ( "no code on stack" )
88028918 }
88038919
8920+ /// Evaluate whether an expression is a compile-time constant boolean.
8921+ /// Returns Some(true) for truthy constants, Some(false) for falsy constants,
8922+ /// None for non-constant expressions.
8923+ /// = expr_constant in CPython compile.c
8924+ fn expr_constant ( expr : & ast:: Expr ) -> Option < bool > {
8925+ match expr {
8926+ ast:: Expr :: BooleanLiteral ( ast:: ExprBooleanLiteral { value, .. } ) => Some ( * value) ,
8927+ ast:: Expr :: NoneLiteral ( _) => Some ( false ) ,
8928+ ast:: Expr :: EllipsisLiteral ( _) => Some ( true ) ,
8929+ ast:: Expr :: NumberLiteral ( ast:: ExprNumberLiteral { value, .. } ) => match value {
8930+ ast:: Number :: Int ( i) => {
8931+ let n: i64 = i. as_i64 ( ) . unwrap_or ( 1 ) ;
8932+ Some ( n != 0 )
8933+ }
8934+ ast:: Number :: Float ( f) => Some ( * f != 0.0 ) ,
8935+ ast:: Number :: Complex { real, imag, .. } => Some ( * real != 0.0 || * imag != 0.0 ) ,
8936+ } ,
8937+ ast:: Expr :: StringLiteral ( ast:: ExprStringLiteral { value, .. } ) => {
8938+ Some ( !value. to_str ( ) . is_empty ( ) )
8939+ }
8940+ ast:: Expr :: BytesLiteral ( ast:: ExprBytesLiteral { value, .. } ) => {
8941+ Some ( value. bytes ( ) . next ( ) . is_some ( ) )
8942+ }
8943+ ast:: Expr :: Tuple ( ast:: ExprTuple { elts, .. } ) => Some ( !elts. is_empty ( ) ) ,
8944+ _ => None ,
8945+ }
8946+ }
8947+
8948+ fn emit_nop ( & mut self ) {
8949+ emit ! ( self , Instruction :: Nop ) ;
8950+ }
8951+
88048952 /// Enter a conditional block (if/for/while/match/try/with)
88058953 /// PEP 649: Track conditional annotation context
88068954 fn enter_conditional_block ( & mut self ) {
@@ -9411,32 +9559,34 @@ impl EmitArg<bytecode::Label> for BlockIdx {
94119559// = _PyCompile_CleanDoc
94129560fn clean_doc ( doc : & str ) -> String {
94139561 let doc = expandtabs ( doc, 8 ) ;
9414- // First pass: find minimum indentation of any non-blank lines
9415- // after first line.
9562+ // First pass: find minimum indentation of non-blank lines AFTER the first line.
9563+ // A "blank line" is one containing only spaces (or empty) .
94169564 let margin = doc
9417- . lines ( )
9418- // Find the non-blank lines
9419- . filter ( |line| !line. trim ( ) . is_empty ( ) )
9420- // get the one with the least indentation
9421- . map ( |line| line. chars ( ) . take_while ( |c| c == & ' ' ) . count ( ) )
9422- . min ( ) ;
9423- if let Some ( margin) = margin {
9424- let mut cleaned = String :: with_capacity ( doc. len ( ) ) ;
9425- // copy first line without leading whitespace
9426- if let Some ( first_line) = doc. lines ( ) . next ( ) {
9427- cleaned. push_str ( first_line. trim_start ( ) ) ;
9428- }
9429- // copy subsequent lines without margin.
9430- for line in doc. split ( '\n' ) . skip ( 1 ) {
9431- cleaned. push ( '\n' ) ;
9432- let cleaned_line = line. chars ( ) . skip ( margin) . collect :: < String > ( ) ;
9433- cleaned. push_str ( & cleaned_line) ;
9434- }
9435-
9436- cleaned
9437- } else {
9438- doc. to_owned ( )
9439- }
9565+ . split ( '\n' )
9566+ . skip ( 1 ) // skip first line
9567+ . filter ( |line| line. chars ( ) . any ( |c| c != ' ' ) ) // non-blank lines only
9568+ . map ( |line| line. chars ( ) . take_while ( |c| * c == ' ' ) . count ( ) )
9569+ . min ( )
9570+ . unwrap_or ( 0 ) ;
9571+
9572+ let mut cleaned = String :: with_capacity ( doc. len ( ) ) ;
9573+ // Strip all leading spaces from the first line
9574+ if let Some ( first_line) = doc. split ( '\n' ) . next ( ) {
9575+ let trimmed = first_line. trim_start ( ) ;
9576+ // Early exit: no leading spaces on first line AND margin == 0
9577+ if trimmed. len ( ) == first_line. len ( ) && margin == 0 {
9578+ return doc. to_owned ( ) ;
9579+ }
9580+ cleaned. push_str ( trimmed) ;
9581+ }
9582+ // Subsequent lines: skip up to `margin` leading spaces
9583+ for line in doc. split ( '\n' ) . skip ( 1 ) {
9584+ cleaned. push ( '\n' ) ;
9585+ let skip = line. chars ( ) . take ( margin) . take_while ( |c| * c == ' ' ) . count ( ) ;
9586+ cleaned. push_str ( & line[ skip..] ) ;
9587+ }
9588+
9589+ cleaned
94409590}
94419591
94429592// copied from rustpython_common::str, so we don't have to depend on it just for this function
0 commit comments