Infer the C# type of ldlen - #4075
Merged
Merged
Conversation
dgrunwald
approved these changes
Aug 29, 2026
siegfriedpammer
force-pushed
the
fix/3704-ldlen-infertype
branch
from
August 29, 2026 23:29
aa593fc to
a21e1dd
Compare
NullPropagationTransform only rewrites "x != null ? x.Chain : fallback" into "x?.Chain ?? fallback" when the chain's inferred type is a non-nullable value type, and InferType had no case for ldlen. Array length therefore came back as UnknownType, so "arr?.Length ?? 0" was left as a ternary. The inferred type mirrors ExpressionBuilder.VisitLdLen, which decides between Array.Length and Array.LongLength from the result type alone. Found while investigating #3704, where the surviving ternary also keeps the tested array in a stack slot and strands the typeof of a dynamic call's static target. That issue is fixed separately in #4072, whose DynamicTests cases pinned the ternary as expected output; those blocks round-trip exactly now, so they are gone. Also carries a review follow-up that missed #4072: the static-target test in VisitDynamicInvokeMemberInstruction is a plain null check, the way DynamicInvokeMemberInstruction itself tests the field, rather than a pattern match binding a name it does not need. Assisted-by: Claude:claude-opus-5[1m]:Claude Code
siegfriedpammer
force-pushed
the
fix/3704-ldlen-infertype
branch
from
August 30, 2026 09:55
a21e1dd to
40b9076
Compare
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.
NullPropagationTransformonly rewritesx != null ? x.Chain : fallbackintox?.Chain ?? fallbackwhen the chain's inferred type is a non-nullable value type.InferTypehad no case forldlen, so array length came back asSpecialType.UnknownType,NullableType.IsNonNullableValueTypereturned false, and the rewrite was skipped:The access chain itself was fine - the same chain without the
??fallback takes theMatchDefaultValue(Nullable<int>)branch, never consultsInferType, and producedGetData()?.Lengthcorrectly. Only the coalescing branch was blocked.The inferred type mirrors
ExpressionBuilder.VisitLdLen, which picksArray.LengthorArray.LongLengthfromResultTypealone - note the split is== StackType.I4, not!= StackType.I8, becauseILReaderemitsLdLen(StackType.I, ...)and onlyExpressionTransforms.VisitConvfolds it down toldlen.i4.Two other
InferTypeconsumers see a real type where they previously sawUnknown, both toward more accuracy:AllStoresUseConsistentTypeinExpressionBuilder, andCheckImplicitTruncationinTransformAssignment, which otherwise falls through to "assume that the value might be changed by truncation".Found while investigating #3704, where the surviving ternary also keeps the tested array in a stack slot and strands the
typeofof a dynamic call's static target. That issue is fixed separately in #4072; this change is independent of it.Why the
DynamicTestscases shrink to one#4072 added four members whose premise was that temporary: the array survived as its own statement, so the
typeofcould not reach the call site by ordinary inlining. Inferring the type ofldlenremoves the temporary, so the target now reaches the call site by inlining and #4072'sStaticTargetTypepath is no longer taken there. Measured by reverting #4072's source changes and runningDynamicTests:DynamicAwait(untouched by this PR)They no longer detect the regression they were written for, and with the temporary gone all four take the same route through
VisitDynamicInvokeMemberInstruction. One is kept asStaticTargetOnDynamicCall; the old name asserted a shape the source no longer produces.DynamicAwaitkeeps the #3704 regression covered, so nothing is lost by dropping the other three along with the callees that existed only for them.Full
ICSharpCode.Decompiler.Testsrun: 3542 passed, 0 failed, 45 skipped.🤖 Generated with Claude Code