Fix #3824: fold a hoisted argument null-guard into the chained ctor call#3829
Fix #3824: fold a hoisted argument null-guard into the chained ctor call#3829sailro wants to merge 1 commit into
Conversation
…ined ctor call When an initializer argument contains an argument-validation null-check (e.g. `arg ?? throw new ArgumentNullException(...)`) and the argument is used more than once, the compiler hoists the null-check in front of the chained constructor call. MoveConstructorInitializer only inspected the first body statement, so the guard defeated it and the chained call was left as an illegal in-body `this..ctor(...)` / `base..ctor(...)`. Skip leading argument null-guards (if (param == null) throw ...;) when locating the chained call, and fold each guard back into the first argument that uses the parameter as `param ?? throw ...`. If a guard cannot be folded, the constructor is left unchanged. Assisted-by: Copilot:claude-opus-4.8:GitHub Copilot CLI
0b1f908 to
5b168f9
Compare
|
Thanks for bringing attention to this issue and for taking the time to prepare a fix — much appreciated! After digging into it, we concluded that this needs a more involved implementation at the ILAst level, as part of the null-coalescing transform. The main advantage of doing it there is the existing transform infrastructure and the precise semantic model: variables know all of their uses and the control flow is explicit, so the transform can reliably verify it only fires on the exact compiler-generated pattern. That information is reachable from the C# AST too, but only through an extra level of indirection, which makes such checks considerably more awkward there. I'm therefore closing this PR as superseded by #3852, which implements the fix that way. Thanks again for the contribution! |
Fixes #3824.
When a constructor initializer argument contains argument-validation that throws
(e.g.
arg ?? throw new ArgumentNullException(...)) and the argument is used bymore than one parameter, the compiler hoists the null-check in front of the
chained constructor call.
MoveConstructorInitializeronly inspected the firstbody statement, so the hoisted guard defeated it and the chained call was left as
an illegal in-body
this..ctor(...)/base..ctor(...)statement (a parse error).Fix
When locating the chained call, skip leading argument null-guards
(
if (param == null) throw ...;) and fold each one back into the first argumentthat uses the parameter, as
param ?? throw .... If a guard cannot be folded (itsparameter is not used by any argument), the constructor is left unchanged.
Before:
After:
Test
Pretty/ConstructorInitializersgains a#if CS70case where the null-check ishoisted because the parameter is used by more than one argument; it round-trips to
the folded initializer form.
Real-world occurrence: IbanNet
PatternToken(string)(net462).