Shortening default(T) is the same problem as removing the redundant cast
around a lambda whose delegate type the context already fixes, so it uses
the same mechanism: ConvertTo makes the explicit type implicit when the
conversion is an identity conversion and the caller allows an implicit
one. The literal keeps the type it was shortened from, so any later
conversion to a different type - or any context that requires an explicit
type, such as an overload resolution recheck falling back to CastArguments
- can spell default(T) out again. That keeps the value intact where the
bare literal would change it, e.g. "object o = default(SomeStruct)", which
boxes a non-null struct while "default" would be null.
Because the shortened literal resolves to DefaultLiteralResolveResult,
CallBuilder's existing overload resolution recheck sees a real default
literal and rejects ambiguous calls on its own; no separate bookkeeping
about which arguments may stay untyped is needed. Only the contexts that
supply no target type at all restore the explicit form: an awaited
expression, and arguments of operator methods, which later become operator
or cast syntax rather than calls.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
The span and tuple tests need types the legacy reference mscorlib predates, so
each of them opened System.Runtime.dll from the ref-assembly toolset into its own
SimpleCompilation - five copies of the same block, five reads per test run. One
shared lazy compilation covers all of them, and it includes the test assembly so
the operator and extension-method fixtures the conversion tests rely on resolve.
Part of #829.
Assisted-by: Claude:claude-opus-5:Claude Code
Auditing against the first-class-span-types proposal turned up three
deviations, each now pinned by resolver unit tests whose expectations were
established by compiling probe programs with the C# 14 compiler.
Lower-bound type inference recursed into Span<T> targets as another
lower-bound inference, but Span<T> is invariant and the spec demands an
exact element inference there: M<T>(Span<T>, T) with (Span<string>, object)
must fail inference (CS0411), not unify to T=object.
Better-conversion-target compared ReadOnlySpan element types where the spec
compares the span types, admitting numeric and user-defined element
conversions the span types do not share: overloads taking ReadOnlySpan<int>
and ReadOnlySpan<long> are ambiguous (CS0121), not resolvable. The general
mutual-convertibility rule already implements the spec's span-type test, so
the element-level block is simply removed; the ReadOnlySpan-over-Span
identity rule stays, since it deliberately inverts that general rule.
The explicit span conversion did not exist at all, and with it the rule that
user-defined conversions are not considered between span-convertible types.
The visible consequence: string[] to Span<object> classified as an implicit
user-defined conversion via op_Implicit(object[]) plus array covariance,
where the compiler reports CS0266 - only the explicit span conversion
exists. Span conversions are also no longer considered for extension
receivers during method group conversion (CS0123), while invocations keep
them.
Part of #829.
Assisted-by: Claude:claude-fable-5:Claude Code
A cast must not reuse an implicit tuple conversion: its elements have to be
classified as cast conversions, which changes the outcome whenever an element
converts through a user-defined operator. Roslyn encodes the same rule in
ClassifyConversionFromTypeForCast via ExplicitConversionMayDifferFromImplicit,
but on our side it rested on an unexplained flag with nothing covering it, so
the flag read as removable. The comments and the test say why it stays.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
Dumping the implicit/explicit classification of all pairs from a 120-type
universe and diffing it against what csc actually compiles surfaced two
divergences (16 affected pairs), both proven by a compiling-and-running
snippet and captured here as ignored known-bug tests:
1. Nullable conversions derived from tuple conversions are missing:
csc accepts "(long, object)? a = t;" for t of type (int, string) as
well as the lifted and explicit forms, CSharpConversions returns None.
The ECMA spec's 10.6.1 does not list tuple conversions as liftable,
so this is a case of Roslyn exceeding the spec.
2. An explicit user-defined conversion to a nullable target is rejected
when the operator result additionally needs an explicit numeric
conversion: csc accepts "(int?)new ImplicitToLong()" (operator to
long, then explicit long -> int?), CSharpConversions returns None.
The same sweep confirmed the SByte..Decimal TypeCode range in
ImplicitEnumerationConversion is correct: csc accepts zero constants of
any numeric type (0.0, 0f, 0m) for enum conversion and rejects '\0',
matching the implementation exactly.
Assisted-by: Claude:claude-fable-5:Claude Code
Audit the conversion test suites against the conversions chapter of the
draft-v8 C# standard and add tests for every rule that had none:
exhaustive implicit/explicit numeric conversion matrix, tuple/ValueTuple
and nullable-annotation identity, interpolated-string / throw-expression /
tuple-literal conversions, the boxing rule set including variance-based
boxing and unboxing, delegate-to-System.Delegate and IReadOnlyList<T>
reference conversions, type-parameter variance and effective-base-class
casts, generic method groups (inference, explicit type arguments, no
inference from the return type), the anonymous-function compatibility
checks CSharpConversions performs itself (via a LambdaResolveResult test
double), standard-conversion exclusion of user-defined operators, and
operators declared in base classes of the source type.
Two rules are implementation gaps rather than test gaps and get ignored
placeholder tests naming the gap: default literal conversions (10.2.16)
are an explicit TODO in CSharpConversions, and switch expressions
(10.2.18) have no ResolveResult representation. The full
section-by-section map is in
Analyses/ILSpy/2026-07-24_conversions-spec-coverage.md.
Assisted-by: Claude:claude-fable-5:Claude Code
Convert the commented-out block in ExplicitConversionsTest the same way as
the implicit ConversionTest block: type-parameter casts via
DefaultTypeParameter with cross-referencing constraints, user-defined
operators as fixture types in the test assembly, constant sources via
ConstantResolveResult. The rr.Input asserts of the originals were resolver
artifacts and are dropped; UseDefinedExplicitConversion_Lifted instead
exercises the ResolveResult-based ExplicitConversion entry point.
Also extend PreferAmbiguousConversionOverReferenceConversion with the
overload-resolution half of the original NRefactory test (the ambiguous
conversion must not prevent M(BB) from being chosen over M(object)), which
the first revival pass had reduced to the conversion classification alone.
Assisted-by: Claude:claude-fable-5:Claude Code
The block of tests inherited from NRefactory's ConversionsTest was
commented out because it depended on ResolverTestBase (full AST +
CSharpResolver). Rewrite all of them against CSharpConversions directly:
hand-built MethodGroupResolveResults over fixture types compiled into the
test assembly (extension methods injected via the internal
extensionMethods field), DefaultTypeParameter instances with
cross-referencing constraints, and metadata-backed fixture interfaces for
the ExpansiveInheritance termination test.
Two deviations from the NRefactory originals:
MethodGroupConversion_RefArgumentObjectVsDynamic now expects a valid
conversion, because object/dynamic mismatch in a ref parameter is an
identity conversion and current csc accepts the assignment (verified);
PreferUserDefinedConversionOverReferenceConversion resolved an invocation,
so it is recast as an OverloadResolution test over FakeMethod candidates.
Assisted-by: Claude:claude-fable-5:Claude Code