Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
ab6f95f
Add CFG and IOp
CyrusNajmabadi Nov 5, 2025
47ffa73
In progress
CyrusNajmabadi Nov 5, 2025
e26f05b
Fixes
CyrusNajmabadi Nov 5, 2025
f781408
Fix
CyrusNajmabadi Nov 5, 2025
f489a02
Cleanup
CyrusNajmabadi Nov 5, 2025
4fb9888
Cleanup
CyrusNajmabadi Nov 5, 2025
c5f3662
Fixup test
CyrusNajmabadi Nov 5, 2025
13baeda
Merge remote-tracking branch 'upstream/features/collection-expression…
CyrusNajmabadi Nov 5, 2025
e431b33
Remove prototype
CyrusNajmabadi Nov 5, 2025
d6078c5
Pass along error bit
CyrusNajmabadi Nov 5, 2025
d3946ca
Update test
CyrusNajmabadi Nov 6, 2025
51b41e2
Assert syntax
CyrusNajmabadi Nov 6, 2025
d4da134
Add assert
CyrusNajmabadi Nov 6, 2025
5d69b09
Make local functino static
CyrusNajmabadi Nov 6, 2025
2860c92
Refine assert
CyrusNajmabadi Nov 6, 2025
9ad9bff
Remove specialized kind
CyrusNajmabadi Nov 6, 2025
912a038
Update src/Compilers/CSharp/Portable/Operations/CSharpOperationFactor…
CyrusNajmabadi Nov 7, 2025
a63fa85
Simplify
CyrusNajmabadi Nov 7, 2025
a5f83e7
Tighten assert
CyrusNajmabadi Nov 7, 2025
3374363
Pass along correct info
CyrusNajmabadi Nov 7, 2025
0bf7671
Update src/Compilers/Core/Portable/Operations/OperationInterfaces.xml
CyrusNajmabadi Nov 7, 2025
801968e
Update src/Compilers/Test/Core/Compilation/CompilationExtensions.cs
CyrusNajmabadi Nov 7, 2025
b7f723f
Rename
CyrusNajmabadi Nov 7, 2025
d4e451b
Update names
CyrusNajmabadi Nov 7, 2025
3f34140
Merge branch 'creationArguments' of https://github.com/CyrusNajmabadi…
CyrusNajmabadi Nov 7, 2025
aa06ca1
renames
CyrusNajmabadi Nov 7, 2025
ab9c232
Use a more loosely typed array
CyrusNajmabadi Nov 7, 2025
d3146db
Simplify
CyrusNajmabadi Nov 7, 2025
dabb261
Use pattern
CyrusNajmabadi Nov 7, 2025
a5db155
Add checks
CyrusNajmabadi Nov 7, 2025
010c783
remove
CyrusNajmabadi Nov 8, 2025
3002bcb
Update src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.Co…
CyrusNajmabadi Nov 8, 2025
0161bfb
reorder
CyrusNajmabadi Nov 8, 2025
1a41d02
Merge branch 'creationArguments' of https://github.com/CyrusNajmabadi…
CyrusNajmabadi Nov 8, 2025
c433eb3
Merge remote-tracking branch 'upstream/main' into creationArguments
CyrusNajmabadi Nov 10, 2025
26f6f95
Update docs
CyrusNajmabadi Nov 10, 2025
ad0a637
Move local
CyrusNajmabadi Nov 10, 2025
ca616ed
Rename and inline
CyrusNajmabadi Nov 10, 2025
d9722d8
Merge remote-tracking branch 'upstream/features/collection-expression…
CyrusNajmabadi Nov 11, 2025
288f14a
Change frames
CyrusNajmabadi Nov 11, 2025
b0eb162
Add iop tessts
CyrusNajmabadi Nov 11, 2025
643233a
Update tests
CyrusNajmabadi Nov 11, 2025
783edae
Update tests
CyrusNajmabadi Nov 11, 2025
8eb9343
Update tests
CyrusNajmabadi Nov 11, 2025
9b10c18
Update tests
CyrusNajmabadi Nov 11, 2025
e1492ce
REvert
CyrusNajmabadi Nov 11, 2025
c1f51e4
Handle more cases
CyrusNajmabadi Nov 11, 2025
b4ca300
Filter out
CyrusNajmabadi Nov 11, 2025
a3b2e5f
Clean up usings
CyrusNajmabadi Nov 11, 2025
9f7cce5
Throw if we mix arguments and non-arguments
CyrusNajmabadi Nov 11, 2025
1db3441
add test
CyrusNajmabadi Nov 11, 2025
dacba39
Push all values first before popping
CyrusNajmabadi Nov 11, 2025
4772ed6
Merge branch 'features/collection-expression-arguments' into creation…
CyrusNajmabadi Nov 12, 2025
b21f43c
Apply suggestion from @CyrusNajmabadi
CyrusNajmabadi Nov 13, 2025
a0b96a4
Update comment
CyrusNajmabadi Nov 13, 2025
83c1141
Make check more precise
CyrusNajmabadi Nov 13, 2025
7bdad0d
Add validation
CyrusNajmabadi Nov 13, 2025
9727a59
Validate more
CyrusNajmabadi Nov 13, 2025
7ce3cef
Add iop test
CyrusNajmabadi Nov 13, 2025
7a2b9fc
Move tests
CyrusNajmabadi Nov 13, 2025
511ca83
Add iop tests
CyrusNajmabadi Nov 13, 2025
1505fdd
Add iop tests
CyrusNajmabadi Nov 13, 2025
5aa324d
remove assert
CyrusNajmabadi Nov 14, 2025
1ce3ae2
Update assert
CyrusNajmabadi Nov 14, 2025
24b8c48
Move assert
CyrusNajmabadi Nov 14, 2025
759a887
remove syntax check
CyrusNajmabadi Nov 14, 2025
4190ad6
remove syntax check
CyrusNajmabadi Nov 14, 2025
006b6db
Introduce a new bound node to make processing collection builder argu…
CyrusNajmabadi Nov 17, 2025
b293d53
Update docs
CyrusNajmabadi Nov 19, 2025
0514752
CFG and tests
CyrusNajmabadi Nov 19, 2025
f33bfe6
Update and add tests
CyrusNajmabadi Nov 19, 2025
9d182f8
Update and add tests
CyrusNajmabadi Nov 19, 2025
5daf3a8
Tweak docs
CyrusNajmabadi Nov 19, 2025
9769388
Tweak docs
CyrusNajmabadi Nov 19, 2025
e07de89
Update
CyrusNajmabadi Nov 19, 2025
9f12cd5
More CFG work
CyrusNajmabadi Nov 20, 2025
7d8fec2
Add new iop type
CyrusNajmabadi Nov 20, 2025
8847d30
Thread through and update tests
CyrusNajmabadi Nov 20, 2025
8393c63
Thread through and update tests
CyrusNajmabadi Nov 20, 2025
033fa45
Update tests
CyrusNajmabadi Nov 20, 2025
393762f
Update tests
CyrusNajmabadi Nov 20, 2025
dfe52e6
Update tests
CyrusNajmabadi Nov 20, 2025
db3fba0
Update tests
CyrusNajmabadi Nov 20, 2025
70a5174
Move to end
CyrusNajmabadi Nov 20, 2025
bddf433
Update public api
CyrusNajmabadi Nov 20, 2025
c59c928
Move tests
CyrusNajmabadi Nov 20, 2025
7a67ee4
Renames
CyrusNajmabadi Nov 20, 2025
ad1e9c2
Renames
CyrusNajmabadi Nov 20, 2025
524865e
Update semantic search
CyrusNajmabadi Nov 24, 2025
e6ab5dd
Merge branch 'features/collection-expression-arguments' into creation…
CyrusNajmabadi Nov 25, 2025
00490ae
Update docs
CyrusNajmabadi Nov 26, 2025
ab0d6f2
remove links
CyrusNajmabadi Nov 26, 2025
f19c06d
Update src/Compilers/Core/Portable/Operations/OperationInterfaces.xml
CyrusNajmabadi Dec 3, 2025
ebcca93
Remove unnecessary using
CyrusNajmabadi Dec 3, 2025
d1c8c44
Already debug
CyrusNajmabadi Dec 3, 2025
681b216
Update src/Compilers/CSharp/Portable/Operations/CSharpOperationFactor…
CyrusNajmabadi Dec 3, 2025
2b5b8c1
Revert
CyrusNajmabadi Dec 3, 2025
2ab435a
Add tests
CyrusNajmabadi Dec 3, 2025
93f6a3a
update gen
CyrusNajmabadi Dec 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,7 @@ internal bool CheckValueKind(SyntaxNode node, BoundExpression expr, BindValueKin
return CheckSimpleAssignmentValueKind(node, assignment, valueKind, diagnostics);

case BoundKind.ValuePlaceholder:
case BoundKind.CollectionBuilderElementsPlaceholder:
// Strict RValue
break;

Expand Down Expand Up @@ -4361,6 +4362,7 @@ internal SafeContext GetValEscape(BoundExpression expr)
case BoundKind.InterpolatedStringArgumentPlaceholder:
case BoundKind.AwaitableValuePlaceholder:
case BoundKind.ValuePlaceholder:
case BoundKind.CollectionBuilderElementsPlaceholder:
return GetPlaceholderScope((BoundValuePlaceholderBase)expr);

case BoundKind.Local:
Expand Down Expand Up @@ -5002,6 +5004,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, SafeContext
case BoundKind.AwaitableValuePlaceholder:
case BoundKind.InterpolatedStringArgumentPlaceholder:
case BoundKind.ValuePlaceholder:
case BoundKind.CollectionBuilderElementsPlaceholder:
if (!GetPlaceholderScope((BoundValuePlaceholderBase)expr).IsConvertibleTo(escapeTo))
{
Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, expr.Syntax);
Expand Down
28 changes: 16 additions & 12 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ private readonly struct CollectionExpressionConverter(
private readonly BindingDiagnosticBag _diagnostics = diagnostics;

private BoundCollectionExpression CreateCollectionExpression(
CollectionExpressionTypeKind collectionTypeKind, ImmutableArray<BoundNode> elements, BoundObjectOrCollectionValuePlaceholder? placeholder = null, BoundExpression? collectionCreation = null, MethodSymbol? collectionBuilderMethod = null, BoundValuePlaceholder? collectionBuilderElementsPlaceholder = null)
CollectionExpressionTypeKind collectionTypeKind, ImmutableArray<BoundNode> elements, BoundObjectOrCollectionValuePlaceholder? placeholder = null, BoundExpression? collectionCreation = null, MethodSymbol? collectionBuilderMethod = null, BoundCollectionBuilderElementsPlaceholder? collectionBuilderElementsPlaceholder = null)
{
return new BoundCollectionExpression(
_node.Syntax,
Expand Down Expand Up @@ -1153,13 +1153,23 @@ static BoundCollectionExpressionSpreadElement bindSpreadElement(
ErrorCode.ERR_CollectionArgumentsNotSupportedForType,
_node.WithElement.Syntax.GetFirstToken().GetLocation(),
_targetType);
return null;
}

return CreateCollectionExpression(collectionTypeKind, elements);
}

private readonly BoundCollectionExpression? TryConvertCollectionExpressionArrayInterfaceType(ImmutableArray<BoundNode> elements)
{
if (_node.WithElement?.Arguments.Length > 0 &&
_targetType.IsReadOnlyArrayInterface(out _))
{
// For the read-only array interfaces (IEnumerable<E>, IReadOnlyCollection<E>, IReadOnlyList<E>), only
// the parameterless `with()` is allowed.
_diagnostics.Add(ErrorCode.ERR_CollectionArgumentsMustBeEmpty, _node.WithElement.Syntax.GetFirstToken().GetLocation());
return null;
}

return CreateCollectionExpression(
CollectionExpressionTypeKind.ArrayInterface,
elements,
Expand All @@ -1176,13 +1186,6 @@ static BoundCollectionExpressionSpreadElement bindSpreadElement(
var withSyntax = withElement.Syntax;
if (@this._targetType.IsReadOnlyArrayInterface(out _))
{
// For the read-only array interfaces (IEnumerable<E>, IReadOnlyCollection<E>, IReadOnlyList<E>), only
// the parameterless `with()` is allowed.
if (withElement.Arguments.Length > 0)
{
@this._diagnostics.Add(ErrorCode.ERR_CollectionArgumentsMustBeEmpty, withSyntax.GetFirstToken().GetLocation());
}

// Note: we intentionally report null here. Even though the code has `with()` in it, we're not actually
// going to call a particular constructor. The lowering phase will properly handle creating a read-only
// interface instance.
Expand Down Expand Up @@ -1257,7 +1260,7 @@ static BoundCollectionExpressionSpreadElement bindSpreadElement(
collectionBuilderMethod: collectionBuilderMethod,
collectionBuilderElementsPlaceholder: collectionBuilderElementsPlaceholder);

static (BoundExpression? collectionCreation, MethodSymbol? collectionBuilderMethod, BoundValuePlaceholder? elementsPlaceholder) bindCollectionBuilderInfo(
static (BoundExpression? collectionCreation, MethodSymbol? collectionBuilderMethod, BoundCollectionBuilderElementsPlaceholder? elementsPlaceholder) bindCollectionBuilderInfo(
ref readonly CollectionExpressionConverter @this)
{
var namedType = (NamedTypeSymbol)@this._targetType;
Expand Down Expand Up @@ -1340,7 +1343,7 @@ static BoundCollectionExpressionSpreadElement bindSpreadElement(
// CollectionBuilder.Create<T1, T2, ..>(a, b, c, <placeholder for [x, y, z]>).

var readonlySpanParameter = collectionBuilderMethod.Parameters.Last();
var collectionBuilderElementsPlaceholder = new BoundValuePlaceholder(syntax, readonlySpanParameter.Type) { WasCompilerGenerated = true };
var collectionBuilderElementsPlaceholder = new BoundCollectionBuilderElementsPlaceholder(syntax, readonlySpanParameter.Type) { WasCompilerGenerated = true };

var arguments = projectionCall.Arguments;
var argumentNames = projectionCall.ArgumentNamesOpt;
Expand Down Expand Up @@ -1371,8 +1374,9 @@ static BoundCollectionExpressionSpreadElement bindSpreadElement(
invokedAsExtensionMethod: false,
argsToParamsOpt: argsToParams,
defaultArguments: projectionCall.DefaultArguments,
resultKind: LookupResultKind.Viable,
type: collectionBuilderMethod.ReturnType)
resultKind: projectionCall.ResultKind,
type: collectionBuilderMethod.ReturnType,
hasErrors: projectionCall.HasErrors)
{
WasCompilerGenerated = @this._node.WithElement is null,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@ internal partial class BoundCollectionExpression
{
private partial void Validate()
{
var collectionCreation = this.CollectionCreation;
while (collectionCreation is BoundConversion conversion)
collectionCreation = conversion.Operand;

Debug.Assert(collectionCreation
is null
or BoundObjectCreationExpression
or BoundCall
or BoundNewT
or BoundBadExpression);

if (collectionCreation is BoundCall boundCall)
{
Debug.Assert(
boundCall.Arguments is [.., BoundCollectionBuilderElementsPlaceholder placeHolder] &&
placeHolder == this.CollectionBuilderElementsPlaceholder);
}

if (this.CollectionTypeKind == CollectionExpressionTypeKind.CollectionBuilder)
{
Debug.Assert(this.CollectionCreation is not null);
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ internal partial class BoundValuePlaceholder
public sealed override bool IsEquivalentToThisReference => throw ExceptionUtilities.Unreachable();
}

internal partial class BoundCollectionBuilderElementsPlaceholder
{
public sealed override bool IsEquivalentToThisReference => throw ExceptionUtilities.Unreachable();
}

internal partial class BoundInterpolatedStringHandlerPlaceholder
{
public sealed override bool IsEquivalentToThisReference => false;
Expand Down
9 changes: 6 additions & 3 deletions src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
</Node>

<!-- This node represents the ReadOnlySpan<T> for the elements passed as the last argument to a collection builder
method. It does not survive lowering. -->
<Node Name="BoundCollectionBuilderElementsPlaceholder" Base="BoundValuePlaceholderBase" />

<!-- only used by codegen -->
<Node Name="BoundDup" Base="BoundExpression">
<!-- when duplicating a local or parameter, must remember original ref kind -->
Expand Down Expand Up @@ -1942,7 +1946,7 @@
<Field Name="ArgumentNamesOpt" Type="ImmutableArray&lt;(string Name, Location Location)?&gt;" Null="allow"/>
<Field Name="ArgumentRefKindsOpt" Type="ImmutableArray&lt;RefKind&gt;" Null="allow"/>
</Node>

<Node Name="BoundCollectionExpression" Base="BoundCollectionExpressionBase" HasValidate="true">
<!-- Non-null type is required for this node kind -->
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
Expand All @@ -1960,9 +1964,8 @@
expression types. For example, if the collection is `[with(a, b, c), x, y, z]` initial binding will produce
`Factory.Create(a, b, c, <placeholder>)`. The last arg will be replaced in lowering with the ReadOnlySpan for
and will naturally be visited during all binding phases. It is just the packaging and movement into the final
and will naturally be visited during all binding phases. It ist just the packaging and movement into the final
arg that the lowering phase concerns itself with. -->
<Field Name="CollectionBuilderElementsPlaceholder" Type="BoundValuePlaceholder?" Null="allow" SkipInVisitor="true"/>
<Field Name="CollectionBuilderElementsPlaceholder" Type="BoundCollectionBuilderElementsPlaceholder?" Null="allow" SkipInVisitor="true"/>
<Field Name="WasTargetTyped" Type="bool" />
<Field Name="HasWithElement" Type="bool" />
<Field Name="UnconvertedCollectionExpression" Type="BoundUnconvertedCollectionExpression" Null="disallow" SkipInVisitor="true"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3708,6 +3708,11 @@ public override BoundNode VisitValuePlaceholder(BoundValuePlaceholder node)
return null;
}

public override BoundNode VisitCollectionBuilderElementsPlaceholder(BoundCollectionBuilderElementsPlaceholder node)
{
return null;
}

public sealed override BoundNode VisitOutVariablePendingInference(OutVariablePendingInference node)
{
throw ExceptionUtilities.Unreachable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11622,6 +11622,12 @@ private TypeWithAnnotations GetDeclaredParameterResult(ParameterSymbol parameter
return null;
}

public override BoundNode? VisitCollectionBuilderElementsPlaceholder(BoundCollectionBuilderElementsPlaceholder node)
{
VisitPlaceholderWithReplacement(node);
return null;
}

public override BoundNode? VisitEventAccess(BoundEventAccess node)
{
var updatedSymbol = VisitMemberAccess(node, node.ReceiverOpt, node.EventSymbol);
Expand Down
Loading
Loading