-
Notifications
You must be signed in to change notification settings - Fork 13.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cleanup old trans #38302
Merged
+1,904
−5,020
Merged
Cleanup old trans #38302
Changes from 1 commit
Commits
Show all changes
103 commits
Select commit
Hold shift + click to select a range
bf7d453
Refactor Block into BlockAndBuilder
Mark-Simulacrum 3f17ab9
Remove unreachable and terminated from Block
Mark-Simulacrum 59ef51c
Replace build.rs with calling functions on builder directly
Mark-Simulacrum e77d928
Add notes regarding malloc_raw_dyn being unwind incompatible
Mark-Simulacrum ad0a901
Remove *_builder
Mark-Simulacrum fec59c5
Replace lpad().and_then(..) with None
Mark-Simulacrum 3dbd141
Remove unused map_block
Mark-Simulacrum 8f3d824
Remove common::Block.
Mark-Simulacrum 86b2bdb
Rename LandingPad to Funclet
Mark-Simulacrum ed989d3
Simple cleanups/inlines in cleanup
Mark-Simulacrum 5a36f88
Remove debug_loc from CleanupScope and privatize various helpers
Mark-Simulacrum ec518a0
Remove EarlyExitLabel, it only has one variant and is as such useless
Mark-Simulacrum 2842912
Remove DropValue.is_immediate
Mark-Simulacrum 48715a1
Reformatting
Mark-Simulacrum da971b7
Pull out get_or_create landing pad to avoid issues with dynamic borro…
Mark-Simulacrum 85ef02d
Only one DropValue per CleanupScope
Mark-Simulacrum 6412f31
Propagate CleanupScope::needs_invoke being always true
Mark-Simulacrum 91707dc
Merge need_invoke and needs_invoke
Mark-Simulacrum 51dfba1
Refactor Vec<CleanupScope> into Option<CleanupScope>.
Mark-Simulacrum 28d00e7
Remove cleanup scope from FunctionContext
Mark-Simulacrum cd57bbe
Refactor get_landing_pad to take a CleanupScope
Mark-Simulacrum 3265afa
Inline and simplify Callee::call duplicates.
Mark-Simulacrum 6441c97
Remove push_ctxt
Mark-Simulacrum 6710af3
Slightly simplify tvec::slice_for_each
Mark-Simulacrum 3169169
Deduplicate store_operand_direct and store_operand
Mark-Simulacrum c7f8b0c
Eagerly evaluate landing pads for cleanup scopes
Mark-Simulacrum b10d89a
Move around code in cleanup for a more logical ordering, and fix comm…
Mark-Simulacrum 14ae76d
Unbox FunctionDebugContextData.
Mark-Simulacrum dda6c8c
Inline base::malloc_raw_dyn.
Mark-Simulacrum 5bdcc22
Remove FIXME
Mark-Simulacrum da23332
Remove remaining traces of block_arena
Mark-Simulacrum e0ccc81
Remove needless allows
Mark-Simulacrum 28f511c
Remove global Builder
Mark-Simulacrum bc0b172
Remove BlockAndBuilder.funclet
Mark-Simulacrum be981dc
Start FunctionContext privatization and reduction
Mark-Simulacrum 8201645
Remove DebugLoc.
Mark-Simulacrum 5262113
Remove fcx.span
Mark-Simulacrum bf8614b
Rename Builder::alloca to dynamic_alloca
Mark-Simulacrum cbbdb73
Remove FunctionContext::cleanup, replacing it with a Drop impl.
Mark-Simulacrum c4f6173
Replace init with get_entry_block.
Mark-Simulacrum 8ed1120
Minor cleanup to context
Mark-Simulacrum 755850f
Merge OwnedBuilder and Builder
Mark-Simulacrum 85ab080
Remove global builder
Mark-Simulacrum 05d107d
Inline validate_substs
Mark-Simulacrum 65f0400
Remove FunctionContext.landingpad_alloca.
Mark-Simulacrum 9c38a54
Inline FunctionContext.mir
Mark-Simulacrum cc1e210
Inline trans_exchange_free
Mark-Simulacrum 449c6d8
Simplify basic_block.rs
Mark-Simulacrum 88b2024
Cleanup instruction counting
Mark-Simulacrum 21bd747
Remove unused functions in abi
Mark-Simulacrum 9a19853
Remove unused imports
Mark-Simulacrum 937001a
Refactor Callee::call to take bcx by-reference.
Mark-Simulacrum 1804131
Remove Ref::clone for MirContext mir
Mark-Simulacrum f051c60
Reduce extensions to FunctionContext in cleanup.
Mark-Simulacrum c693bcc
Inline memfill and merge with memset_intrinsic.
Mark-Simulacrum 611e90b
Simplify intrinsic match statement
Mark-Simulacrum 99816a6
Further simplify intrinsic matching
Mark-Simulacrum b48e74b
Rename 'blk and 'bcx to 'a
Mark-Simulacrum 515d14f
Inline/Replace finish with build_return_block
Mark-Simulacrum 97a2096
Inline and cleanup build_return_block
Mark-Simulacrum 1173db0
Inline last remaining use of Callee::call and delete unused code
Mark-Simulacrum a802b9f
Inline get_funclet
Mark-Simulacrum fc8c280
Remove lifetime parameter
Mark-Simulacrum 2b9a0ef
Move debug_context to MirContext from FunctionContext
Mark-Simulacrum a42a342
Move param_env onto SharedCrateContext, and move functions which need…
Mark-Simulacrum e10695f
Move param_substs onto MirContext
Mark-Simulacrum 0a71b38
Remove llretslotptr from FunctionContext
Mark-Simulacrum 4c7041e
Don't special case abort/unreachable intrinsics
Mark-Simulacrum 2bda3b7
Inline and simplify init_cpad
Mark-Simulacrum 63a0d85
Make add_incoming_to_phi call slightly less confusing.
Mark-Simulacrum f9f1406
Rebase fixes
Mark-Simulacrum 6e3d8cd
Fix and cleanup callee shims
Mark-Simulacrum dd1890f
Remove unreachable call to unreachable
Mark-Simulacrum 7f5dffb
Make debuginfo take debug_context instead of MirContext
Mark-Simulacrum a445199
Remove public ccx function on MirContext
Mark-Simulacrum 0256f60
Move debug info check into create_function_debug_context
Mark-Simulacrum 5301d38
Remove unused bcx from LocalAnalyzer.
Mark-Simulacrum 7f87163
Simplify funclets creation.
Mark-Simulacrum 5ef85dd
Change param_env to empty_param_env
Mark-Simulacrum 22bf541
Clean up uses of set_personality_fn.
Mark-Simulacrum f11721a
Add helper function to set debug locations
Mark-Simulacrum f103ea4
Remove unecessary logic.
Mark-Simulacrum 88202c5
Replace bcx.ccx() with bcx.ccx
Mark-Simulacrum 15c9e5e
Mutate llargs instead of reconstructing it.
Mark-Simulacrum 6fac0a1
Change *.fcx.ccx to *.ccx
Mark-Simulacrum d55e739
Do not use BAB after calling unreachable.
Mark-Simulacrum 6f368e6
Use fn_ty directly
Mark-Simulacrum 0d5a8ad
Move get_landing_pad onto DropVal.
Mark-Simulacrum 6a1ec55
Remove needless check
Mark-Simulacrum b9f1064
Inline make_drop_glue
Mark-Simulacrum 98a13ff
Remove outdated comment
Mark-Simulacrum 295ea0d
Reduce coerce_unsized_into to one call
Mark-Simulacrum 15b9b27
slice_for_each gives a reference already
Mark-Simulacrum bd009dc
Remove fn_ty from FunctionContext
Mark-Simulacrum 3198797
Remove outdated comment
Mark-Simulacrum 57914f6
Move eh_personality() onto CrateContext
Mark-Simulacrum 07cf2a9
Simplify callee by removing is_indirect branch.
Mark-Simulacrum 654131c
Add unreachable() after calls to eh_unwind_resume.
Mark-Simulacrum a811f60
Simplify get_landing_pad by inlining UnwindKind.
Mark-Simulacrum a9b5c63
Move eh_unwind_resume into CrateContext
Mark-Simulacrum c1bc5e5
Improve cache quality for eh_personality.
Mark-Simulacrum 079abd0
Reuse cleanup pad declared at start of block.
Mark-Simulacrum 0013d4c
Fix rebase errors.
Mark-Simulacrum File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Move around code in cleanup for a more logical ordering, and fix comm…
…ents
- 1.85.1
- 1.85.0
- 1.84.1
- 1.84.0
- 1.83.0
- 1.82.0
- 1.81.0
- 1.80.1
- 1.80.0
- 1.79.0
- 1.78.0
- 1.77.2
- 1.77.1
- 1.77.0
- 1.76.0
- 1.75.0
- 1.74.1
- 1.74.0
- 1.73.0
- 1.72.1
- 1.72.0
- 1.71.1
- 1.71.0
- 1.70.0
- 1.69.0
- 1.68.2
- 1.68.1
- 1.68.0
- 1.67.1
- 1.67.0
- 1.66.1
- 1.66.0
- 1.65.0
- 1.64.0
- 1.63.0
- 1.62.1
- 1.62.0
- 1.61.0
- 1.60.0
- 1.59.0
- 1.58.1
- 1.58.0
- 1.57.0
- 1.56.1
- 1.56.0
- 1.55.0
- 1.54.0
- 1.53.0
- 1.52.1
- 1.52.0
- 1.51.0
- 1.50.0
- 1.49.0
- 1.48.0
- 1.47.0
- 1.46.0
- 1.45.2
- 1.45.1
- 1.45.0
- 1.44.1
- 1.44.0
- 1.43.1
- 1.43.0
- 1.42.0
- 1.41.1
- 1.41.0
- 1.40.0
- 1.39.0
- 1.38.0
- 1.37.0
- 1.36.0
- 1.35.0
- 1.34.2
- 1.34.1
- 1.34.0
- 1.33.0
- 1.32.0
- 1.31.1
- 1.31.0
- 1.30.1
- 1.30.0
- 1.29.2
- 1.29.1
- 1.29.0
- 1.28.0
- 1.27.2
- 1.27.1
- 1.27.0
- 1.26.2
- 1.26.1
- 1.26.0
- 1.25.0
- 1.24.1
- 1.24.0
- 1.23.0
- 1.22.1
- 1.22.0
- 1.21.0
- 1.20.0
- 1.19.0
- 1.18.0
- 1.17.0
- 1.16.0
commit b10d89a0961bb8682dc3e6d2781c6e390c6cf25c
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,108 +11,12 @@ | |
//! ## The Cleanup module | ||
//! | ||
//! The cleanup module tracks what values need to be cleaned up as scopes | ||
//! are exited, either via panic or just normal control flow. The basic | ||
//! idea is that the function context maintains a stack of cleanup scopes | ||
//! that are pushed/popped as we traverse the AST tree. There is typically | ||
//! at least one cleanup scope per AST node; some AST nodes may introduce | ||
//! additional temporary scopes. | ||
//! are exited, either via panic or just normal control flow. | ||
//! | ||
//! Cleanup items can be scheduled into any of the scopes on the stack. | ||
//! Typically, when a scope is popped, we will also generate the code for | ||
//! each of its cleanups at that time. This corresponds to a normal exit | ||
//! from a block (for example, an expression completing evaluation | ||
//! successfully without panic). However, it is also possible to pop a | ||
//! block *without* executing its cleanups; this is typically used to | ||
//! guard intermediate values that must be cleaned up on panic, but not | ||
//! if everything goes right. See the section on custom scopes below for | ||
//! more details. | ||
//! | ||
//! Cleanup scopes come in three kinds: | ||
//! | ||
//! - **AST scopes:** each AST node in a function body has a corresponding | ||
//! AST scope. We push the AST scope when we start generate code for an AST | ||
//! node and pop it once the AST node has been fully generated. | ||
//! - **Loop scopes:** loops have an additional cleanup scope. Cleanups are | ||
//! never scheduled into loop scopes; instead, they are used to record the | ||
//! basic blocks that we should branch to when a `continue` or `break` statement | ||
//! is encountered. | ||
//! - **Custom scopes:** custom scopes are typically used to ensure cleanup | ||
//! of intermediate values. | ||
//! | ||
//! ### When to schedule cleanup | ||
//! | ||
//! Although the cleanup system is intended to *feel* fairly declarative, | ||
//! it's still important to time calls to `schedule_clean()` correctly. | ||
//! Basically, you should not schedule cleanup for memory until it has | ||
//! been initialized, because if an unwind should occur before the memory | ||
//! is fully initialized, then the cleanup will run and try to free or | ||
//! drop uninitialized memory. If the initialization itself produces | ||
//! byproducts that need to be freed, then you should use temporary custom | ||
//! scopes to ensure that those byproducts will get freed on unwind. For | ||
//! example, an expression like `box foo()` will first allocate a box in the | ||
//! heap and then call `foo()` -- if `foo()` should panic, this box needs | ||
//! to be *shallowly* freed. | ||
//! | ||
//! ### Long-distance jumps | ||
//! | ||
//! In addition to popping a scope, which corresponds to normal control | ||
//! flow exiting the scope, we may also *jump out* of a scope into some | ||
//! earlier scope on the stack. This can occur in response to a `return`, | ||
//! `break`, or `continue` statement, but also in response to panic. In | ||
//! any of these cases, we will generate a series of cleanup blocks for | ||
//! each of the scopes that is exited. So, if the stack contains scopes A | ||
//! ... Z, and we break out of a loop whose corresponding cleanup scope is | ||
//! X, we would generate cleanup blocks for the cleanups in X, Y, and Z. | ||
//! After cleanup is done we would branch to the exit point for scope X. | ||
//! But if panic should occur, we would generate cleanups for all the | ||
//! scopes from A to Z and then resume the unwind process afterwards. | ||
//! | ||
//! To avoid generating tons of code, we cache the cleanup blocks that we | ||
//! create for breaks, returns, unwinds, and other jumps. Whenever a new | ||
//! cleanup is scheduled, though, we must clear these cached blocks. A | ||
//! possible improvement would be to keep the cached blocks but simply | ||
//! generate a new block which performs the additional cleanup and then | ||
//! branches to the existing cached blocks. | ||
//! | ||
//! ### AST and loop cleanup scopes | ||
//! | ||
//! AST cleanup scopes are pushed when we begin and end processing an AST | ||
//! node. They are used to house cleanups related to rvalue temporary that | ||
//! get referenced (e.g., due to an expression like `&Foo()`). Whenever an | ||
//! AST scope is popped, we always trans all the cleanups, adding the cleanup | ||
//! code after the postdominator of the AST node. | ||
//! | ||
//! AST nodes that represent breakable loops also push a loop scope; the | ||
//! loop scope never has any actual cleanups, it's just used to point to | ||
//! the basic blocks where control should flow after a "continue" or | ||
//! "break" statement. Popping a loop scope never generates code. | ||
//! | ||
//! ### Custom cleanup scopes | ||
//! | ||
//! Custom cleanup scopes are used for a variety of purposes. The most | ||
//! common though is to handle temporary byproducts, where cleanup only | ||
//! needs to occur on panic. The general strategy is to push a custom | ||
//! cleanup scope, schedule *shallow* cleanups into the custom scope, and | ||
//! then pop the custom scope (without transing the cleanups) when | ||
//! execution succeeds normally. This way the cleanups are only trans'd on | ||
//! unwind, and only up until the point where execution succeeded, at | ||
//! which time the complete value should be stored in an lvalue or some | ||
//! other place where normal cleanup applies. | ||
//! | ||
//! To spell it out, here is an example. Imagine an expression `box expr`. | ||
//! We would basically: | ||
//! | ||
//! 1. Push a custom cleanup scope C. | ||
//! 2. Allocate the box. | ||
//! 3. Schedule a shallow free in the scope C. | ||
//! 4. Trans `expr` into the box. | ||
//! 5. Pop the scope C. | ||
//! 6. Return the box as an rvalue. | ||
//! | ||
//! This way, if a panic occurs while transing `expr`, the custom | ||
//! cleanup scope C is pushed and hence the box will be freed. The trans | ||
//! code for `expr` itself is responsible for freeing any other byproducts | ||
//! that may be in play. | ||
//! Typically, when a scope is finished, we generate the cleanup code. This | ||
//! corresponds to a normal exit from a block (for example, an expression | ||
//! completing evaluation successfully without panic). | ||
use llvm::{BasicBlockRef, ValueRef}; | ||
use base::{self, Lifetime}; | ||
|
@@ -131,9 +35,17 @@ pub struct CleanupScope<'tcx> { | |
pub landing_pad: Option<BasicBlockRef>, | ||
} | ||
|
||
#[derive(Copy, Clone, Debug)] | ||
pub struct CustomScopeIndex { | ||
index: usize | ||
#[derive(Copy, Clone)] | ||
pub struct DropValue<'tcx> { | ||
val: ValueRef, | ||
ty: Ty<'tcx>, | ||
skip_dtor: bool, | ||
} | ||
|
||
impl<'tcx> DropValue<'tcx> { | ||
fn trans<'blk>(&self, funclet: Option<&'blk Funclet>, bcx: &BlockAndBuilder<'blk, 'tcx>) { | ||
glue::call_drop_glue(bcx, self.val, self.ty, self.skip_dtor, funclet) | ||
} | ||
} | ||
|
||
#[derive(Copy, Clone, Debug)] | ||
|
@@ -142,6 +54,44 @@ enum UnwindKind { | |
CleanupPad(ValueRef), | ||
} | ||
|
||
impl UnwindKind { | ||
/// Generates a branch going from `bcx` to `to_llbb` where `self` is | ||
/// the exit label attached to the start of `bcx`. | ||
/// | ||
/// Transitions from an exit label to other exit labels depend on the type | ||
/// of label. For example with MSVC exceptions unwind exit labels will use | ||
/// the `cleanupret` instruction instead of the `br` instruction. | ||
fn branch(&self, bcx: &BlockAndBuilder, to_llbb: BasicBlockRef) { | ||
match *self { | ||
UnwindKind::CleanupPad(pad) => { | ||
bcx.cleanup_ret(pad, Some(to_llbb)); | ||
} | ||
UnwindKind::LandingPad => { | ||
bcx.br(to_llbb); | ||
} | ||
} | ||
} | ||
|
||
fn get_funclet(&self, bcx: &BlockAndBuilder) -> Option<Funclet> { | ||
match *self { | ||
UnwindKind::CleanupPad(_) => { | ||
let pad = bcx.cleanup_pad(None, &[]); | ||
Funclet::msvc(pad) | ||
}, | ||
UnwindKind::LandingPad => Funclet::gnu(), | ||
} | ||
} | ||
} | ||
|
||
impl PartialEq for UnwindKind { | ||
fn eq(&self, label: &UnwindKind) -> bool { | ||
match (*self, *label) { | ||
(UnwindKind::LandingPad, UnwindKind::LandingPad) | | ||
(UnwindKind::CleanupPad(..), UnwindKind::CleanupPad(..)) => true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a weird implementation, is it ever used? |
||
_ => false, | ||
} | ||
} | ||
} | ||
impl<'blk, 'tcx> FunctionContext<'blk, 'tcx> { | ||
pub fn trans_scope( | ||
&self, | ||
|
@@ -186,9 +136,7 @@ impl<'blk, 'tcx> FunctionContext<'blk, 'tcx> { | |
}; | ||
|
||
debug!("schedule_drop_adt_contents(val={:?}, ty={:?}) skip_dtor={}", | ||
Value(val), | ||
ty, | ||
drop.skip_dtor); | ||
Value(val), ty, drop.skip_dtor); | ||
|
||
Some(CleanupScope::new(self, drop)) | ||
} | ||
|
@@ -259,27 +207,9 @@ impl<'tcx> CleanupScope<'tcx> { | |
UnwindKind::LandingPad | ||
}; | ||
|
||
// Generate the cleanup block and branch to it. | ||
let cleanup_llbb = CleanupScope::trans_cleanups_to_exit_scope(fcx, val, drop_val); | ||
val.branch(&mut pad_bcx, cleanup_llbb); | ||
|
||
return pad_bcx.llbb(); | ||
} | ||
|
||
/// Used when the caller wishes to jump to an early exit, such as a return, | ||
/// break, continue, or unwind. This function will generate all cleanups | ||
/// between the top of the stack and the exit `label` and return a basic | ||
/// block that the caller can branch to. | ||
fn trans_cleanups_to_exit_scope<'a>( | ||
fcx: &FunctionContext<'a, 'tcx>, | ||
label: UnwindKind, | ||
drop_val: &DropValue<'tcx> | ||
) -> BasicBlockRef { | ||
debug!("trans_cleanups_to_exit_scope label={:?}`", label); | ||
|
||
// Generate a block that will resume unwinding to the calling function | ||
let bcx = fcx.build_new_block("resume"); | ||
match label { | ||
match val { | ||
UnwindKind::LandingPad => { | ||
let addr = fcx.landingpad_alloca.get().unwrap(); | ||
let lp = bcx.load(addr); | ||
|
@@ -299,68 +229,14 @@ impl<'tcx> CleanupScope<'tcx> { | |
let mut cleanup = fcx.build_new_block("clean_custom_"); | ||
|
||
// Insert cleanup instructions into the cleanup block | ||
drop_val.trans(label.get_funclet(&cleanup).as_ref(), &cleanup); | ||
drop_val.trans(val.get_funclet(&cleanup).as_ref(), &cleanup); | ||
|
||
// Insert instruction into cleanup block to branch to the exit | ||
label.branch(&mut cleanup, bcx.llbb()); | ||
|
||
debug!("trans_cleanups_to_exit_scope: llbb={:?}", cleanup.llbb()); | ||
|
||
cleanup.llbb() | ||
} | ||
} | ||
|
||
impl UnwindKind { | ||
/// Generates a branch going from `bcx` to `to_llbb` where `self` is | ||
/// the exit label attached to the start of `bcx`. | ||
/// | ||
/// Transitions from an exit label to other exit labels depend on the type | ||
/// of label. For example with MSVC exceptions unwind exit labels will use | ||
/// the `cleanupret` instruction instead of the `br` instruction. | ||
fn branch(&self, bcx: &BlockAndBuilder, to_llbb: BasicBlockRef) { | ||
match *self { | ||
UnwindKind::CleanupPad(pad) => { | ||
bcx.cleanup_ret(pad, Some(to_llbb)); | ||
} | ||
UnwindKind::LandingPad => { | ||
bcx.br(to_llbb); | ||
} | ||
} | ||
} | ||
|
||
fn get_funclet(&self, bcx: &BlockAndBuilder) -> Option<Funclet> { | ||
match *self { | ||
UnwindKind::CleanupPad(_) => { | ||
let pad = bcx.cleanup_pad(None, &[]); | ||
Funclet::msvc(pad) | ||
}, | ||
UnwindKind::LandingPad => Funclet::gnu(), | ||
} | ||
} | ||
} | ||
val.branch(&mut cleanup, bcx.llbb()); | ||
|
||
impl PartialEq for UnwindKind { | ||
fn eq(&self, label: &UnwindKind) -> bool { | ||
match (*self, *label) { | ||
(UnwindKind::LandingPad, UnwindKind::LandingPad) | | ||
(UnwindKind::CleanupPad(..), UnwindKind::CleanupPad(..)) => true, | ||
_ => false, | ||
} | ||
} | ||
} | ||
|
||
/////////////////////////////////////////////////////////////////////////// | ||
// Cleanup types | ||
// Branch into the cleanup block | ||
val.branch(&mut pad_bcx, cleanup.llbb()); | ||
|
||
#[derive(Copy, Clone)] | ||
pub struct DropValue<'tcx> { | ||
val: ValueRef, | ||
ty: Ty<'tcx>, | ||
skip_dtor: bool, | ||
} | ||
|
||
impl<'tcx> DropValue<'tcx> { | ||
fn trans<'blk>(&self, funclet: Option<&'blk Funclet>, bcx: &BlockAndBuilder<'blk, 'tcx>) { | ||
glue::call_drop_glue(bcx, self.val, self.ty, self.skip_dtor, funclet) | ||
return pad_bcx.llbb(); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be inlined?