Once a named argument is hoisted, the call becomes a CallWithNamedArgs
block, and inlining moves the Current getter into the block's first
argument store. The foreach detection could not move it back out:
CanInlineIntoSlot refused the block, and for an instance call the store
of the 'this' argument in front counted as a write the getter cannot be
moved past. That store only writes the block's own variable, so the
getter is checked against the stored value instead, the same way
CanExtendNamedArgument allows inlining into that position.
Roslyn writes [Nullable] for an anonymous type as if the type was not
generic, so its type arguments have no flags. Taking one per argument
shifted every annotation after the anonymous type, which showed on the
cached lambda fields in <>c, and tripped the dummy-flag assert when the
shifted stream landed on a generic struct. [Dynamic] and
[TupleElementNames] do have entries for those arguments (checked against
what Roslyn emits), so only the nullable index skips them.
vbc builds an exception filter as a single non-short-circuiting
expression: `isinst`, then the user's `when` conditions, combined with
`and`. DetectCatchWhenConditionBlocks only knew the branch chain csc
emits, so the type test stayed inside the filter and the handler kept the
`object` variable it has in IL:
catch (object obj) when ((obj is Exception) & (num2 != 0) & (num == 0))
{
ProjectData.SetProjectError((Exception)obj);
A catch type has to derive from Exception, so that does not compile.
The conjunction form is matched too now, lifting the test to the catch
type as the block form already does. The other conjuncts stop running for
a non-matching exception once the test moves, so the filter has to be
pure for this to be invisible; PropagateExceptionVariable then drops the
castclass in the handler:
catch (Exception ex) when ((num2 != 0) & (num == 0))
{
ProjectData.SetProjectError(ex);
Closes#3659
vbc builds an exception filter as a single non-short-circuiting
expression: `isinst`, then the user's `when` conditions, combined with
`and`. DetectCatchWhenConditionBlocks only knew the branch chain csc
emits, so the type test stayed inside the filter and the handler kept the
`object` variable it has in IL:
catch (object obj) when ((obj is Exception) & (num2 != 0) & (num == 0))
{
ProjectData.SetProjectError((Exception)obj);
A catch type has to derive from Exception, so that does not compile.
The conjunction form is matched too now, lifting the test to the catch
type as the block form already does. The other conjuncts stop running for
a non-matching exception once the test moves, so the filter has to be
pure for this to be invisible; PropagateExceptionVariable then drops the
castclass in the handler:
catch (Exception ex) when ((num2 != 0) & (num == 0))
{
ProjectData.SetProjectError(ex);
Closes#3659
vbc builds an exception filter as a single non-short-circuiting
expression: `isinst`, then the user's `when` conditions, combined with
`and`. DetectCatchWhenConditionBlocks only knew the branch chain csc
emits, so the type test stayed inside the filter and the handler kept the
`object` variable it has in IL:
catch (object obj) when ((obj is Exception) & (num2 != 0) & (num == 0))
{
ProjectData.SetProjectError((Exception)obj);
A catch type has to derive from Exception, so that does not compile.
The conjunction form is matched too now, lifting the test to the catch
type as the block form already does. The other conjuncts stop running for
a non-matching exception once the test moves, so the filter has to be
pure for this to be invisible; PropagateExceptionVariable then drops the
castclass in the handler:
catch (Exception ex) when ((num2 != 0) & (num == 0))
{
ProjectData.SetProjectError(ex);
Closes#3659
CachedDelegateInitializationWithField only fired when the instruction
after the caching `if` read the cache field exactly once. A discarded
conversion -- `_ = (Action<int>)M;` -- reads it zero times: Roslyn emits
the null check and the store, and nothing else. The transform bailed out,
so the `<>O` cache class and its `<0>__M` field survived into the output
under names no C# compiler will accept.
Zero usages is now handled: the `if` is replaced by the delegate
construction it guarded, which is what the source expressed, and the
caching disappears with the field. The rest of the method is checked for
loads of the same field first, since only then is this `if` the sole
initialization.
Closes#3965