Conversation
`extern int a[]; sizeof a` compiled and answered 0; gcc rejects it (6.5.3.4p1). #C90 closed the type-name form and left this one, because neither the type nor the completeness helpers can settle it: int[], int[n] and int[m] all intern to one TypeId and the extent lives on the declarator. Symbol::array_is_variably_modified records whether one was given -- the same shape as the other declaration facts the symbol carries -- so a VLA's sizeof keeps working and an incomplete array's is refused. Two further defects had to go with it. The file-scope declarator loop built an absent extent as size.unwrap_or(0), where parse_declarator keeps None, so `int a[];` was indistinguishable from the GNU zero-length `int a[0];` and the two paths disagreed about what incomplete looks like. And a redeclaration reused the existing symbol with the existing type, so `extern int a[]; int a[4];` left the object incomplete for good; 6.2.7p4 makes the composite carry whichever extent is known. Also corrects TODO.md, which still said #C38 had a reproducer but no fix -- it was fixed, and re-probing at -O0 and -O2 under qemu agrees. Closes #C112. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`_Atomic struct S s; s.a` compiled in silence. C11 6.5.2.3p5 makes it
undefined behaviour -- the access touches part of an object whose
atomicity covers all of it, so the lock the type promises is not taken.
gcc warns; the severity is a warning rather than a rejection because the
standard makes it undefined rather than a constraint violation.
This was the last entry on TODO.md's "C11 Atomics -- remaining semantic
validation" list, and the omission meant the one operation _Atomic
exists to prevent was the one c17 never mentioned.
Both access forms warn, `p->m` judging the pointee's atomicity since
that is the object it names, and the message distinguishes a structure
from a union as gcc's does. The mirror image must stay silent and the
accept-side test pins it: it is the object's atomicity that counts, not
the member's, so `struct { _Atomic int a; } s; s.a` is an ordinary
access to an atomic member.
Recorded at #C113.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One missing mechanism behind three symptoms. `int v; int w = v;` was accepted and silently yielded zero. `const int c = 5; int w = c;` was accepted and right, but only because nothing folded it. And `int w = c + 1;` one step along was rejected -- valid code gcc compiles. Fixing any one alone leaves the others wrong, which is why the narrow attempt during #C101 was reverted. gcc's rule, measured: a const-qualified object with a visible constant initializer folds in a static initializer, in arbitrary arithmetic and at every arithmetic type, silently and even under -pedantic; without an initializer, or through extern, it does not; a non-const object never does. The folding is scoped by a ConstScope parameter threaded through the evaluator rather than a flag on the linearizer, because it is a property of the question being asked and not of compiler state -- a flag would let the answer depend on evaluation order. C makes a const object no kind of constant expression, so the lax scope must not reach an array size, a case label, _Static_assert, an enumerator or a bit-field width; all five stay strict and are pinned. eval_static_address moves to the lax scope too, being reachable only from the initializer path, which is what makes `int *p = &a[c-3];` compile. Closes #C102. Filed #C114: _Static_assert refuses `1.5 > 1.0`, whose result is int and so is an integer constant expression -- c17 judges it on the operand type instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Filed as noise -- 567 warnings in a CPython build -- and the noise was the smaller half. glibc declares register_t with __attribute__((__mode__(__word__))), so c17 sized it 4 bytes against gcc's 8: a wrong type, silently, in a header any program may include. A machine mode names a width, and the attribute replaces the declared type with the one of that width in the same family, keeping the declared signedness -- so `typedef unsigned u8 __attribute__((mode(QI)));` is unsigned and the `int` spelling signed. Integer modes QI/HI/SI/DI/TI and the synonyms word/pointer; floating HF/SF/DF/XF/TF; and the complex forms HC/SC/DC/XC/TC, which were 285 of the warnings on their own -- <bits/floatn.h> declares __cfloat128 with mode(TC). XF and TF are both sixteen bytes and not interchangeable, one being the x87 extended format and the other IEEE binary128, so they map to their own types rather than to a width; the test divides by three and checks the precision rather than trusting sizeof. A vector mode still warns: ignoring one would change what the program computes, and vector types do not exist here. A full CPython -O2 build now emits zero mode warnings, and 552 diagnostics in total against 1,105. Closes #C85. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`__builtin_add_overflow(-1, 5u, &u128)` reported overflow where the answer is 4. The builtin asks whether the mathematical result is representable, and the ordinary lowering answers by computing in a type twice the destination's width and asking whether narrowing lost anything -- which has nothing to compare against when the destination is itself 128 bits, so a fallback examined the result directly on operands already converted to the destination. That conversion is not value-preserving for a negative operand with an unsigned destination. For two genuinely 128-bit operands the answer still needs 129 bits, as #C62 recorded. But when both operands are narrower the exact computation fits, and only the check changes: from "did narrowing lose anything" to "is the exact value representable", which for a signed exact value in an unsigned destination is just "is it negative". The bound is counted in magnitude bits rather than assumed, and that is the part worth keeping: `(u64)-1 * (u64)-1` is just under 2^128, which unsigned __int128 holds and __int128 does not, so a shortcut gated on "narrower than the destination" alone would let the exact computation wrap and then check a value it never saw. A pre-existing test caught exactly that. Verified against gcc on 24 rows and on aarch64 under qemu. Closes #C62. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Filed from one symptom -- `_Static_assert(1.5 > 1.0, "x")` refused --
and the symptom understated it twice over.
The expression was not being refused as non-constant; it was folded to
false, because the parser's constant evaluator truncates a floating
literal to i128 before folding, so `1.5 > 1.0` became `1 > 1`. That
folder evaluates array sizes, enumerators, case labels and
_Static_assert, so `enum E { X = 1.5 > 1.0 }` was 0 and
`int a[1.5 > 1.0 ? 4 : 8]` took the wrong branch and allocated eight
elements. The same arm compared signed whatever the operand types, so
`(unsigned)-1 > 0` was false too. And a cast from floating arithmetic
evaluated its operand through the integer folder, making
`(int)(1.5 + 1.5)` two rather than three.
A comparison of floating operands has an integer result and so is an
integer constant expression however its operands are spelled;
unsignedness comes from the operands under the usual arithmetic
conversions. Both folders were fixed, since each evaluates a different
set of contexts.
The duplication is the standing hazard and is filed as #C115: two
implementations of 6.6 that have drifted, only one of which had the
unsigned-comparison and width-masked-shift treatment earlier work gave
it. Closes #C114.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
linearize_atomic.rs says the lock-free-size aggregate gap is 'Recorded in
doc/TODO.md'. Nothing was ever recorded there, so a deferral with a good
reason was invisible to anyone reading the documents rather than the
source -- the same failure as the stale #C38 note this series already
corrected.
The gap itself: `_Atomic struct S { int a; }` read as a whole object
warns and falls through to a non-atomic struct copy, where gcc emits a
plain 4-byte load. Scalars of 1, 2, 4 and 8 bytes are lock-free and an
aggregate of the same size gets nothing; anything larger is refused,
which is deliberate per #X1. Doing it properly needs the
value-versus-address convention for small aggregates settled first.
Filed as #C116, with the TODO.md entry the comment promised.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit added the TODO.md entry and corrected the source comment; this is the audit entry that goes with them. Split only because the first attempt's edit did not apply. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the cc crate to fix several C17/GCC-compatibility issues around machine-mode attributes, sizeof on incomplete arrays, constant-expression folding (including static-initializer “const object” folding), atomic aggregate member diagnostics, and 128-bit checked-arithmetic overflow semantics. It also adds/extends diagnostics and codegen tests plus corresponding audit/TODO documentation updates.
Changes:
- Implement
__attribute__((mode(M)))for scalar/complex modes (keeping vector modes as warnings) and add tests ensuring implemented modes are silent and type selection matches expectations. - Reject
sizeofon incomplete array expressions while preserving VLA and completion-by-redeclaration behavior; add diagnostics + codegen coverage. - Scope constant folding so static initializers can fold visible
constobjects (gcc behavior) without weakening strict constant-expression contexts; fix float/unsigned comparison folding and add coverage.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| cc/tests/diagnostics/mod.rs | Adds diagnostics coverage for implemented mode, incomplete-array sizeof expression rejection, atomic-member warnings, and static-initializer folding boundaries. |
| cc/tests/codegen/misc.rs | Adds runtime/codegen validation for array sizeof behavior and const folding in static initializers. |
| cc/tests/c99/types.rs | Adds codegen test asserting mode(M) selects correct types/signedness/precision. |
| cc/tests/c99/expressions.rs | Adds test pinning correct constant folding for float and unsigned comparisons. |
| cc/tests/builtins/intrinsics.rs | Adds coverage for 128-bit checked arithmetic builtins with mixed signedness. |
| cc/symbol.rs | Records whether an array declarator was variably modified to distinguish VLA vs incomplete for sizeof expression operands. |
| cc/parse/parser.rs | Implements mode mapping, fixes array extent handling and redeclaration completion, and improves parser-side constant folding behavior. |
| cc/parse/expression.rs | Enforces sizeof-expression completeness for incomplete arrays, and adds atomic aggregate member-access warning emission. |
| cc/ir/linearize.rs | Adjusts 128-bit checked-arithmetic lowering to correctly detect representability in mixed-signedness cases. |
| cc/ir/linearize_stmt.rs | Introduces ConstScope and folds visible const objects in static initializers; improves float-compare folding in const eval. |
| cc/ir/linearize_init.rs | Switches initializer folding to initializer-scoped const evaluation and diagnoses non-constant object reads. |
| cc/ir/linearize_atomic.rs | Updates comment to match new audit/TODO references for the recorded atomic-aggregate deferral. |
| cc/doc/TODO.md | Updates TODOs to reflect closed findings and better document remaining atomic-aggregate limitation. |
| cc/audit.md | Marks multiple findings fixed and records new behavior/tests for #C85/#C112/#C102/#C113/#C114/#C62, etc. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Two defects in the mode attribute #C85 added, found by CI and by review within a day of it landing. TF and TC mapped to _Float128 on every target, but arch::has_float128() is `os != MacOS` and c17 deliberately predefines no __FLT128_* family there -- precisely so <float.h> cannot advertise a type whose every operation fails to link. The mode attribute handed that type back anyway, so mode(TF) on Apple arm64 needed __divtf3 and __multf3 and turned macOS CI red while both Linux targets passed. They now warn and leave the declared type unchanged, as a vector mode does, gated on TypeTable::has_float128 rather than a second spelling of the condition. Separately, apply_pending_mode was called at the three typedef sites only, while pending_mode is set for any declarator and -- unlike pending_alignas, cleared at four declaration boundaries -- was never cleared. A mode on a non-typedef was therefore dropped and then survived onto whatever came next: `int a __attribute__((mode(QI))); typedef int T;` left a four bytes and made T one. The mode now applies wherever a declarator's type becomes final, before alignment attaches to it, and is cleared at all four boundaries. Fixing that produced a clean instance of #C54: the `mut` binding one site needed is in parse_declaration, which is #[cfg(test)]-gated, so cargo build succeeded and only cargo test failed. Recorded at #C117. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`enum E { A = 1 << 64 };` exited 101 on an unreachable! in
enum_underlying_type, and so did `3 << 63` and `1 << 100`. Its premise --
"a non-negative maximum always fits u64 or overflows i128" -- is false:
nothing clamps an enumerator to 64 bits and the folder computes in i128.
It is now the same diagnostic the signed branch already had. A compiler
diagnoses rather than aborting, whatever else is wrong.
One divergence remains and is deliberate: gcc accepts these, folding the
shift to 0 because it truncates to the expression's type, and c17
rejects them because neither folder truncates. That is #C115, and this
path stops being reached once it lands.
The test asserts the message rather than mere failure -- a panic also
exits non-zero, so an exit-status-only test would have passed against
the crash. Recorded at #C118.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two defects that meet on the wide bit-field.
emit_bitfield_load_bytewise walked every byte of the access span with no
"does this byte hold any of the field's bits?" test -- the skip its
store sibling has always had. A field in a window wider than itself read
the object's padding, and every __int128 bit-field is such a field: its
window is sixteen bytes. The stray bytes were also shifted past the
64-bit carrier's width, which x86-64 masks (folding padding into the
result) and aarch64's assembler rejects, so the same source would not
build there. The comment claiming every shift was in range held only for
the packed case the helper was written for.
The brace initializer then converted twice: to the member's type,
correctly, and again to a type derived from the span, where
bitfield_storage_type answers `unsigned int` for anything but 1, 2, 4 or
8 bytes. A 64-bit field kept 32 bits of its initializer. The second
conversion is gone; emit_bitfield_store takes the member-typed value as
the assignment path already hands it over, which is why `p.a = ~0ULL`
was right and `= {~0ULL}` was not.
The test dirties the padding deliberately -- with it zero the load
defect is invisible, which is how #C98's width cap came to promise that
widths up to 64 "keep working".
Recorded at #C119 and #C120.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`__builtin_fabsl(-3.5L)` returned 2.5e-4932. Both it and __builtin_signbitl lowered to the *double* opcode -- the code said so, "treat as 64-bit for now" -- whose emitter moves the argument as a double and calls fabs/__signbit. On x86-64 a long double is the 80-bit x87 format, so that read its mantissa. They are ordinary calls to fabsl and __signbitl now, built by the same synthesize-a-declaration route the __builtin___*_chk family uses. That gets the long-double ABI from the call path, which already carries one for __mulxc3, so no backend work was needed; the Fabsl and Signbitl AST variants and their lowering arms are gone with it. The signbit family is also normalised to 0/1. 7.12.3.6 permits any nonzero value and the library entry points return the sign bit in place -- 8, 128 and 512 for the three widths, which did not agree with one another. gcc is no more consistent: 512 on x86-64 for a runtime long double, 1 for a constant one, 1 for both on aarch64. All conforming, so this is predictability rather than a fix, and it is recorded as such. The test reads its operands from an array; the constant form was always right, which is what hid this. Recorded at #C121. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`char big[5000000000];` compiled clean and reported sizeof 536870911. size_bits answers in a u32, and the Array arm capped at u32::MAX with a comment saying so; nothing bounded an extent except the negative check, so a wrong sizeof propagated into every layout, offset and copy built from that type. gcc accepts these -- its own bound is far higher -- so this is an implementation limit rather than a constraint violation, and the message says which. TypeTable::MAX_OBJECT_BYTES names the bound and Parser::derive_array_type enforces it at the one place the three declarator loops now share. It measures against the element type, so the outer dimension of char a[16385][32768] is checked against an inner extent already known to be in range. A member list can reach the bound with no single member near it, so the struct and union specifier checks the laid-out size too; that arm's `(size * 8) as u32` had been a silent truncation rather than a cap. Both saturations remain as floors and are now unreachable from source. Recorded at #C122, with #C123 filed for what the probe turned up beside it: a rejected abstract array declarator reports the backtracking fallback's error, not the real one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There were two C17 6.6 walks -- one in the parser for array bounds,
enumerators, case labels, bit-field widths and _Static_assert, one in
the linearizer for static initializers and branch folding -- and they
had drifted. The parser had a floating-comparison arm the linearizer
lacked, the linearizer had _Alignof and the const-object scope the
parser lacked, and each carried fixes the other did not, so the same
expression could be one value in a declaration and another in the
initializer beside it.
They are one walk now, in cc/constexpr.rs, over a ConstEnv trait whose
only members are what the two hosts genuinely answer differently: what
an identifier means, which struct a member path starts from, and how a
floating subexpression folds. The parser's copy of eval_pointer_constant
and its now-dead float helpers went with it.
The drift had a wrong answer in it. The parser's FloatLit arm truncated
a floating literal to an integer, which 6.6p6 admits only as the
immediate operand of a cast, so `int a[1.5];`, `enum E { X = 1.5 };`,
`struct S { int b:1.5; };` and `_Static_assert(1.5, "")` all compiled
where gcc rejects each -- and at block scope the array did not merely
compile, it became a variable length array sized from a double. The arm
is gone, check_array_size_type gives gcc's "size of array has
non-integer type" instead of a misleading VLA message, and
__builtin_constant_p asks the floating fold as well, since a floating
constant is still a constant.
The review's second point closes with it: the subscript in a pointer
constant stays in the scope that reached it, so
`const int c = 5; int *p = &a[c - 3];` folds.
Recorded at #C124, with #C125 filed for the -Wshift-count-overflow
warning c17 still does not emit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…l by size C evaluates every operation *in* a type, and the result of one is an operand of the next. The folder carried a full-width i128 instead, so `-1u / 3u` was 0 rather than 1431655765 -- the negative was still negative when the division saw it -- `(unsigned)-1 % 7u` was 4294967295 rather than 3, `-1u >> 1` was an arithmetic shift, and a cast did not convert at all: `(unsigned char)-1` stayed -1 and `(short)70000` stayed 70000. constexpr::normalize reduces every node's value to what its own type can hold. The cast falls out of that for free, since a cast node's type is the type cast to, and _Bool gets its 0-or-1 conversion rather than the low byte. Division, remainder and right shift additionally ask the operand's signedness, which matters only at 128 bits: there is no narrower type to reduce to, and (unsigned __int128)-1 really is a negative i128. Thirty-five folded expressions were diffed against gcc. That exposed a second bug, latent until now. 6.4.4.1p5 picks the first of unsigned int, unsigned long, unsigned long long that can represent the value, but the suffix arm returned unsigned int unconditionally, so `sizeof 0xaaaaaaaaaaaaaaabu` was 4. Harmless while constants were carried at full width; fatal once they narrow. The CPython gate caught it: math.comb(5, 2) returned 85899345930, from a static const uint64_t table whose 0xaaaaaaaaaaaaaaabu entry was emitted as .quad 2863311531. Recorded at #C126 and #C127, with #C128 filed for what the differential harness turned up beside them: `#x` stringification drops every multi-character punctuator. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`#define S(x) #x` then `S(a >> b)` yielded "a b", and so did <<, >>=, >=, &&, ||, ++ and every other operator of more than one character. SpecialToken numbers those from 256 up, and both stringify_arg and tokens_to_text printed only codes below that, letting the rest fall through a catch-all. 6.10.3.2p2 asks for the spelling of the preprocessing token, so an assertion or logging macro was printing something that was not the expression it had been given. SpecialToken::spelling supplies the missing text as an exhaustive match, so a punctuator cannot be added without one, and from_code turns the stored u32 back into the variant. -E was never affected; it rebuilds its text by another route. Found while diffing folded constants against gcc, where a #e label in the harness came out as `-1u1`. Twenty-seven stringifications now agree with gcc. Recorded at #C128. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
show_include_chain and its " (through ...)" note were live code, but the only three functions that ever set Stream::include_pos -- Stream::included, StreamTable::add_included and init_included_stream -- were #[cfg(test)], and every production registration went through init_stream, which leaves it None. Gating a constructor had made un-gated code unreachable: a full CPython build produced zero "through" lines across 552 diagnostics, so a warning inside a system header never said which #include brought it in. The three are un-gated, and include_file and include_builtin call init_included_stream with the #include directive's own position, which both already had to hand. The same CPython build now produces 547. Position::bad was the other gated constructor and goes the other way: nothing in the compiler ever made a bad position, so do_diag's is_bad() early return was dead. Both are deleted rather than un-gated, and the unit test that existed only to call them is replaced by one that checks the chain is recorded in order. Part of #C54, recorded at #C129. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#C54 filed twelve #[cfg(test)] items as dead. Counting callers says
otherwise for eight: TypeTable::params and is_variadic have 17 and 15 in
parse::test_parser, Function::dominates has 10 in ir::dominate,
diag::{stream_name, stream_prev, error_count, warning_count} back this
module's registry and counter tests, and TypeId::is_valid and
Linearizer::new_no_ssa have one each. Those are test support and
#[cfg(test)] is the right way to say so.
What is dead is opt.rs's four helpers -- get_pseudo, get_const_val,
get_const_fval, is_const -- under a header reading "used by tests and
future passes". No pass used them, the only callers were four tests of
the helpers themselves, and every real consumer has its own get_pseudo.
Helpers, gated imports and self-referential tests are gone.
Two headers were wrong about the code beneath them. types.rs's
"Test-only methods (used by tests but not production code)" sat above
format_type with 27 production callers, unsized_array_levels with 13,
is_composite_complete with 5 and array_size with 4; diag.rs's "for
tests" said nothing about why. Both now describe what is there.
Part of #C54, recorded at #C130.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Parser::variable_array_levels was line-for-line TypeTable::unsized_array_levels, and cflow, ctags and cxref each carried a byte-identical exit_code() -- the one that has to consult both error counters, since the front end keeps its own and a tool reading only one exits 0 after reporting errors. The first is gone; the second lives in cc/tools.rs, a module the c17 binary does not compile. The third duplicate was not equivalent. cflow::format_type was a partial copy that dropped everything below the top level, printing int[] whatever the extent, int() whatever the parameters, and bare `struct` for a tagged one. It calls TypeTable::format_type now. Swapping it over surfaced two defects in the shared formatter, fixed here. A multi-dimensional array printed its extents backwards, so `int m[4][8]` came out `int[8][4]` in cflow output and in every diagnostic that named the type. And a pointer was written `char*` where gcc and the source write `char *`, with `char **` joined. Recorded at #C131, with #C132 filed for what remains: a pointer to an array is still spelled `int[8] *` rather than gcc's `int (*)[8]`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
parse_declaration and parse_function_def were #[cfg(test)] copies of the declaration and function-definition halves of parse_external_decl, and they had drifted: neither cleared pending_alignas, pending_mode or pending_fn_attrs, which the real entry point does first. So 52 unit tests in parse::test_parser were checking a parser no translation unit ever ran through -- the same failure mode as the include-chain trace, a #[cfg(test)] gate on something production also needs. Both copies are deleted. parse_decl and parse_func call parse_external_decl and destructure the ExternalDecl, failing loudly on the wrong arm. All 1051 unit tests pass unchanged against the production path, so nothing in the drift had been load-bearing -- but nothing had been checking either. Recorded at #C133, which closes #C54: the include chain (#C129), the genuinely dead helpers (#C130), the duplicated helpers (#C131) and this were its four parts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The close was meant to ride along with the #C133 commit and did not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
No description provided.