Skip to content

Commit

Permalink
Merge branch 'dotnet/main' into out-of-proc-design-time-builds
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonmalinowski committed Aug 30, 2023
2 parents 1c53ab7 + b3e3c67 commit f60c2f6
Show file tree
Hide file tree
Showing 15 changed files with 357 additions and 42 deletions.
8 changes: 4 additions & 4 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
</Dependency>
</ProductDependencies>
<ToolsetDependencies>
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="8.0.0-beta.23425.2">
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="8.0.0-beta.23428.2">
<Uri>https://github.com/dotnet/arcade</Uri>
<Sha>90c167d5c57de4a8bced566379dbd893556c94e8</Sha>
<Sha>3d92d0bfd8c9006f5fe1687e465dc4f8aa068b00</Sha>
<SourceBuild RepoName="arcade" ManagedOnly="true" />
</Dependency>
<Dependency Name="Microsoft.DotNet.XliffTasks" Version="1.0.0-beta.23423.1" CoherentParentDependency="Microsoft.DotNet.Arcade.Sdk">
Expand All @@ -31,9 +31,9 @@
<Uri>https://github.com/dotnet/roslyn</Uri>
<Sha>5d10d428050c0d6afef30a072c4ae68776621877</Sha>
</Dependency>
<Dependency Name="Microsoft.DotNet.Helix.Sdk" Version="8.0.0-beta.23425.2">
<Dependency Name="Microsoft.DotNet.Helix.Sdk" Version="8.0.0-beta.23428.2">
<Uri>https://github.com/dotnet/arcade</Uri>
<Sha>90c167d5c57de4a8bced566379dbd893556c94e8</Sha>
<Sha>3d92d0bfd8c9006f5fe1687e465dc4f8aa068b00</Sha>
</Dependency>
<Dependency Name="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0-preview.23251.1">
<Uri>https://github.com/dotnet/roslyn-analyzers</Uri>
Expand Down
4 changes: 2 additions & 2 deletions eng/config/PublishData.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
"Shipping",
"NonShipping"
],
"vsBranch": "rel/d17.8",
"vsBranch": "main",
"vsMajorVersion": 17,
"insertionCreateDraftPR": false,
"insertionTitlePrefix": "[d17.8P2]"
Expand All @@ -179,7 +179,7 @@
],
"vsBranch": "main",
"vsMajorVersion": 17,
"insertionCreateDraftPR": false,
"insertionCreateDraftPR": true,
"insertionTitlePrefix": "[d17.8P3]"
},
"dev/jorobich/fix-pr-val": {
Expand Down
4 changes: 2 additions & 2 deletions global.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"xcopy-msbuild": "17.6.0-2"
},
"msbuild-sdks": {
"Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.23425.2",
"Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.23425.2"
"Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.23428.2",
"Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.23428.2"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ protected override BoundBlock CreateBlockFromLambdaExpressionBody(Binder lambdaB
throw ExceptionUtilities.Unreachable();
}

protected override BoundBlock BindLambdaBody(LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, BindingDiagnosticBag diagnostics)
protected override BoundBlock BindLambdaBodyCore(LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, BindingDiagnosticBag diagnostics)
{
return _bodyFactory(lambdaSymbol, lambdaBodyBinder, diagnostics);
}
Expand Down
30 changes: 23 additions & 7 deletions src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1770,16 +1770,32 @@ private ImmutableArray<BoundExpression> BuildArgumentsForErrorRecovery(AnalyzedA
{
case BoundKind.UnboundLambda:
{
// bind the argument against each applicable parameter
var unboundArgument = (UnboundLambda)argument;
foreach (var parameterList in parameterListList)

// If nested in other lambdas where type inference is involved,
// the target delegate type could be different each time.
// But if the lambda is explicitly typed, we can bind only once.
// https://github.com/dotnet/roslyn/issues/69093
if (unboundArgument.HasExplicitlyTypedParameterList &&
unboundArgument.HasExplicitReturnType(out _, out _) &&
unboundArgument.FunctionType is { } functionType &&
functionType.GetInternalDelegateType() is { } delegateType)
{
// Just assume we're not in an expression tree for the purposes of error recovery.
_ = unboundArgument.Bind(delegateType, isExpressionTree: false);
}
else
{
var parameterType = GetCorrespondingParameterType(analyzedArguments, i, parameterList);
if (parameterType?.Kind == SymbolKind.NamedType &&
(object)parameterType.GetDelegateType() != null)
// bind the argument against each applicable parameter
foreach (var parameterList in parameterListList)
{
// Just assume we're not in an expression tree for the purposes of error recovery.
var discarded = unboundArgument.Bind((NamedTypeSymbol)parameterType, isExpressionTree: false);
var parameterType = GetCorrespondingParameterType(analyzedArguments, i, parameterList);
if (parameterType?.Kind == SymbolKind.NamedType &&
(object)parameterType.GetDelegateType() != null)
{
// Just assume we're not in an expression tree for the purposes of error recovery.
var discarded = unboundArgument.Bind((NamedTypeSymbol)parameterType, isExpressionTree: false);
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2512,8 +2512,9 @@ private CSDiagnosticInfo NotFound(SyntaxNode where, string simpleName, int arity
// for attributes, suggest both, but not for verbatim name
if (options.IsAttributeTypeLookup() && !options.IsVerbatimNameAttributeTypeLookup())
{
// just recurse one level, so cheat and OR verbatim name option :)
NotFound(where, simpleName, arity, whereText + "Attribute", diagnostics, aliasOpt, qualifierOpt, options | LookupOptions.VerbatimNameAttributeTypeOnly);
string attributeName = arity > 0 ? $"{simpleName}Attribute<>" : $"{simpleName}Attribute";

NotFound(where, simpleName, arity, attributeName, diagnostics, aliasOpt, qualifierOpt, options | LookupOptions.VerbatimNameAttributeTypeOnly);
}

if ((object)qualifierOpt != null)
Expand Down
26 changes: 24 additions & 2 deletions src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,17 @@ public TypeWithAnnotations InferReturnType(ConversionsBase conversions, NamedTyp
public bool ParameterIsDiscard(int index) { return Data.ParameterIsDiscard(index); }
}

/// <summary>
/// Lambda binding state, recorded during testing only.
/// </summary>
internal sealed class LambdaBindingData
{
/// <summary>
/// Number of lambdas bound.
/// </summary>
internal int LambdaBindingCount;
}

internal abstract class UnboundLambdaState
{
private UnboundLambda _unboundLambda = null!; // we would prefer this readonly, but we have an initialization cycle.
Expand Down Expand Up @@ -555,7 +566,18 @@ internal UnboundLambdaState WithCaching(bool includeCache)
public abstract RefKind RefKind(int index);
public abstract ScopedKind DeclaredScope(int index);
public abstract ParameterSyntax? ParameterSyntax(int i);
protected abstract BoundBlock BindLambdaBody(LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, BindingDiagnosticBag diagnostics);

protected BoundBlock BindLambdaBody(LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, BindingDiagnosticBag diagnostics)
{
if (lambdaSymbol.DeclaringCompilation?.TestOnlyCompilationData is LambdaBindingData data)
{
Interlocked.Increment(ref data.LambdaBindingCount);
}

return BindLambdaBodyCore(lambdaSymbol, lambdaBodyBinder, diagnostics);
}

protected abstract BoundBlock BindLambdaBodyCore(LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, BindingDiagnosticBag diagnostics);

/// <summary>
/// Return the bound expression if the lambda has an expression body and can be reused easily.
Expand Down Expand Up @@ -1546,7 +1568,7 @@ protected override BoundBlock CreateBlockFromLambdaExpressionBody(Binder lambdaB
return lambdaBodyBinder.CreateBlockFromExpression((ExpressionSyntax)this.Body, expression, diagnostics);
}

protected override BoundBlock BindLambdaBody(LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, BindingDiagnosticBag diagnostics)
protected override BoundBlock BindLambdaBodyCore(LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, BindingDiagnosticBag diagnostics)
{
if (this.IsExpressionLambda)
{
Expand Down
51 changes: 43 additions & 8 deletions src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax
{
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.Syntax.InternalSyntax;

internal abstract partial class SyntaxParser : IDisposable
Expand All @@ -38,6 +36,15 @@ internal abstract partial class SyntaxParser : IDisposable
private int _resetStart;

private static readonly ObjectPool<BlendedNode[]> s_blendedNodesPool = new ObjectPool<BlendedNode[]>(() => new BlendedNode[32], 2);
private static readonly ObjectPool<ArrayElement<SyntaxToken>[]> s_lexedTokensPool = new ObjectPool<ArrayElement<SyntaxToken>[]>(() => new ArrayElement<SyntaxToken>[CachedTokenArraySize], 2);

// Array size held in token pool. This should be large enough to prevent most allocations, but
// not so large as to be wasteful when not in use.
private const int CachedTokenArraySize = 4096;

// Maximum index where a value has been written in _lexedTokens. This will allow Dispose
// to limit the range needed to clear when releasing the lexed token array back to the pool.
private int _maxWrittenLexedTokenIndex = -1;

private BlendedNode[] _blendedTokens;

Expand Down Expand Up @@ -65,7 +72,7 @@ protected SyntaxParser(
else
{
_firstBlender = default(Blender);
_lexedTokens = new ArrayElement<SyntaxToken>[32];
_lexedTokens = s_lexedTokensPool.Allocate();
}

// PreLex is not cancellable.
Expand Down Expand Up @@ -95,6 +102,14 @@ public void Dispose()
s_blendedNodesPool.ForgetTrackedObject(blendedTokens);
}
}

var lexedTokens = _lexedTokens;
if (lexedTokens != null)
{
_lexedTokens = null;

ReturnLexedTokensToPool(lexedTokens);
}
}

protected void ReInitialize()
Expand Down Expand Up @@ -123,11 +138,12 @@ protected bool IsIncremental
private void PreLex()
{
// NOTE: Do not cancel in this method. It is called from the constructor.
var size = Math.Min(4096, Math.Max(32, this.lexer.TextWindow.Text.Length / 2));
_lexedTokens = new ArrayElement<SyntaxToken>[size];
var size = Math.Min(CachedTokenArraySize, this.lexer.TextWindow.Text.Length / 2);
var lexer = this.lexer;
var mode = _mode;

_lexedTokens ??= s_lexedTokensPool.Allocate();

for (int i = 0; i < size; i++)
{
var token = lexer.Lex(mode);
Expand Down Expand Up @@ -369,6 +385,11 @@ private void AddLexedToken(SyntaxToken token)
this.AddLexedTokenSlot();
}

if (_tokenCount > _maxWrittenLexedTokenIndex)
{
_maxWrittenLexedTokenIndex = _tokenCount;
}

_lexedTokens[_tokenCount].Value = token;
_tokenCount++;
}
Expand Down Expand Up @@ -422,9 +443,23 @@ private void AddLexedTokenSlot()
}
else
{
var tmp = new ArrayElement<SyntaxToken>[_lexedTokens.Length * 2];
Array.Copy(_lexedTokens, tmp, _lexedTokens.Length);
_lexedTokens = tmp;
var lexedTokens = _lexedTokens;

Array.Resize(ref _lexedTokens, _lexedTokens.Length * 2);

ReturnLexedTokensToPool(lexedTokens);
}
}

private void ReturnLexedTokensToPool(ArrayElement<SyntaxToken>[] lexedTokens)
{
// Put lexedTokens back into the pool if it's correctly sized.
if (lexedTokens.Length == CachedTokenArraySize)
{
// Clear all written indexes in lexedTokens before releasing back to the pool
Array.Clear(lexedTokens, 0, _maxWrittenLexedTokenIndex + 1);

s_lexedTokensPool.Free(lexedTokens);
}
}

Expand Down
77 changes: 77 additions & 0 deletions src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -514,5 +514,82 @@ string makeExpectedOutput()
return builder.ToString();
}
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/69093")]
public void NestedLambdas(bool localFunctions)
{
const int overloads1Number = 20;
const int overloads2Number = 10;

/*
interface I0 { }
// ...
interface I9 { }
*/
var builder1 = new StringBuilder();
var interfacesNumber = Math.Max(overloads1Number, overloads2Number);
for (int i = 0; i < interfacesNumber; i++)
{
builder1.AppendLine($$"""interface I{{i}} { }""");
}

/*
void M1(System.Action<I0> a) { }
// ...
void M1(System.Action<I9> a) { }
*/
var builder2 = new StringBuilder();
for (int i = 0; i < overloads1Number; i++)
{
builder2.AppendLine($$"""void M1(System.Action<I{{i}}> a) { }""");
}

/*
void M2(I0 x, System.Func<string, I0> f) { }
// ...
void M2(I9 x, System.Func<string, I9> f) { }
*/
for (int i = 0; i < overloads2Number; i++)
{
builder2.AppendLine($$"""void M2(I{{i}} x, System.Func<string, I{{i}}> f) { }""");
}

// Local functions should be similarly fast as lambdas.
var inner = localFunctions ? """
M2(x, L0);
static I0 L0(string arg) {
arg = arg + "0";
return default;
}
""" : """
M2(x, static I0 (string arg) => {
arg = arg + "0";
return default;
});
""";

var source = $$"""
{{builder1}}
class C
{
{{builder2}}
void Main()
{
M1(x =>
{
{{inner}}
});
}
}
""";
RunInThread(() =>
{
var comp = CreateCompilation(source, options: TestOptions.DebugDll.WithConcurrentBuild(false));
var data = new LambdaBindingData();
comp.TestOnlyCompilationData = data;
comp.VerifyDiagnostics();
Assert.Equal(localFunctions ? 20 : 40, data.LambdaBindingCount);
}, timeout: TimeSpan.FromSeconds(5));
}
}
}
Loading

0 comments on commit f60c2f6

Please sign in to comment.