提交 edbc6eee 编写于 作者: A Andy Gocke 提交者: GitHub

Merge pull request #13390 from agocke/features/local-functions

This is a collective PR for local functions that represents all the work around the design changes allowing local functions to be referenced from anywhere in scope. The first commit implements the necessary changes to lowering and the second implements the changes to definite assignment.

Both changes have already been reviewed.
......@@ -1035,28 +1035,6 @@ private NamedTypeSymbol ConstructNamedTypeUnlessTypeArgumentOmitted(SyntaxNode t
Debug.Assert(members.Count > 0);
if (!hasErrors)
{
// The common case is that if that members contains a local function symbol,
// there is only one element. Still do a foreach for potential error cases.
foreach (var member in members)
{
if (!(member is LocalFunctionSymbol))
{
continue;
}
Debug.Assert(members.Count == 1 && member.Locations.Length == 1);
var localSymbolLocation = member.Locations[0];
bool usedBeforeDecl =
syntax.SyntaxTree == localSymbolLocation.SourceTree &&
syntax.SpanStart < localSymbolLocation.SourceSpan.Start;
if (usedBeforeDecl)
{
Error(diagnostics, ErrorCode.ERR_VariableUsedBeforeDeclaration, syntax, syntax);
}
}
}
switch (members[0].Kind)
{
case SymbolKind.Method:
......
......@@ -353,6 +353,7 @@
<Compile Include="FlowAnalysis\ControlFlowPass.cs" />
<Compile Include="FlowAnalysis\CSharpDataFlowAnalysis.cs" />
<Compile Include="FlowAnalysis\DataFlowPass.cs" />
<Compile Include="FlowAnalysis\DataFlowPass.LocalFunctions.cs" />
<Compile Include="FlowAnalysis\DataFlowPass.VariableIdentifier.cs" />
<Compile Include="FlowAnalysis\DataFlowsInWalker.cs" />
<Compile Include="FlowAnalysis\DataFlowsOutWalker.cs" />
......@@ -401,6 +402,7 @@
<Compile Include="Lowering\LambdaRewriter\LambdaRewriter.Analysis.cs" />
<Compile Include="Lowering\LambdaRewriter\LambdaRewriter.cs" />
<Compile Include="Lowering\LambdaRewriter\LambdaFrame.cs" />
<Compile Include="Lowering\LambdaRewriter\LambdaRewriter.LocalFunctionReferenceRewriter.cs" />
<Compile Include="Lowering\LambdaRewriter\SynthesizedLambdaMethod.cs" />
<Compile Include="Lowering\LocalRewriter\DynamicSiteContainer.cs" />
<Compile Include="Lowering\LocalRewriter\LocalRewriter.cs" />
......
......@@ -218,6 +218,7 @@ protected override void VisitStatement(BoundStatement statement)
case BoundKind.Block:
case BoundKind.ThrowStatement:
case BoundKind.LabeledStatement:
case BoundKind.LocalFunctionStatement:
base.VisitStatement(statement);
break;
case BoundKind.StatementList:
......@@ -232,7 +233,10 @@ protected override void VisitStatement(BoundStatement statement)
private void CheckReachable(BoundStatement statement)
{
if (!this.State.Alive && !this.State.Reported && !statement.WasCompilerGenerated && statement.Syntax.Span.Length != 0)
if (!this.State.Alive &&
!this.State.Reported &&
!statement.WasCompilerGenerated &&
statement.Syntax.Span.Length != 0)
{
var firstToken = statement.Syntax.GetFirstToken();
Diagnostics.Add(ErrorCode.WRN_UnreachableCode, new SourceLocation(firstToken));
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.CSharp.Symbols;
using System;
using System.Collections.Immutable;
using System.Diagnostics;
namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class DataFlowPass
{
private readonly SmallDictionary<LocalFunctionSymbol, LocalFuncUsages> _localFuncVarUsages =
new SmallDictionary<LocalFunctionSymbol, LocalFuncUsages>();
private class LocalFuncUsages
{
public BitVector ReadVars = BitVector.Empty;
public BitVector WrittenVars = BitVector.Empty;
public bool LocalFuncVisited { get; set; } = false;
}
/// <summary>
/// At the local function's use site, checks that all variables read
/// are assigned and assigns all variables that are definitely assigned
/// to be definitely assigned.
/// </summary>
private void ReplayReadsAndWrites(LocalFunctionSymbol localFunc,
SyntaxNode syntax,
bool writes)
{
_usedLocalFunctions.Add(localFunc);
// First process the reads
ReplayVarUsage(localFunc,
syntax,
isWrite: false);
// Now the writes
if (writes)
{
ReplayVarUsage(localFunc,
syntax,
isWrite: true);
}
}
private void ReplayVarUsage(LocalFunctionSymbol localFunc,
SyntaxNode syntax,
bool isWrite)
{
LocalFuncUsages usages = GetOrCreateLocalFuncUsages(localFunc);
var state = isWrite ? usages.WrittenVars : usages.ReadVars;
// Start at slot 1 (slot 0 just indicates reachability)
for (int slot = 1; slot < state.Capacity; slot++)
{
if (state[slot])
{
if (isWrite)
{
SetSlotAssigned(slot);
}
else
{
var symbol = variableBySlot[slot].Symbol;
CheckAssigned(symbol, syntax, slot);
}
}
}
usages.LocalFuncVisited = true;
}
private int RootSlot(int slot)
{
while (true)
{
var varInfo = variableBySlot[slot];
if (varInfo.ContainingSlot == 0)
{
return slot;
}
else
{
slot = varInfo.ContainingSlot;
}
}
}
public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatement localFunc)
{
var oldMethodOrLambda = this.currentMethodOrLambda;
this.currentMethodOrLambda = localFunc.Symbol;
var oldPending = SavePending(); // we do not support branches into a lambda
// Local functions don't affect outer state and are analyzed
// with everything unassigned and reachable
var savedState = this.State;
this.State = this.ReachableState();
var usages = GetOrCreateLocalFuncUsages(localFunc.Symbol);
var oldReads = usages.ReadVars;
usages.ReadVars = BitVector.Empty;
if (!localFunc.WasCompilerGenerated) EnterParameters(localFunc.Symbol.Parameters);
var oldPending2 = SavePending();
VisitAlways(localFunc.Body);
RestorePending(oldPending2); // process any forward branches within the lambda body
ImmutableArray<PendingBranch> pendingReturns = RemoveReturns();
RestorePending(oldPending);
LeaveParameters(localFunc.Symbol.Parameters, localFunc.Syntax, null);
LocalState stateAtReturn = this.State;
foreach (PendingBranch pending in pendingReturns)
{
this.State = pending.State;
if (pending.Branch.Kind == BoundKind.ReturnStatement)
{
// ensure out parameters are definitely assigned at each return
LeaveParameters(localFunc.Symbol.Parameters, pending.Branch.Syntax, null);
IntersectWith(ref stateAtReturn, ref this.State);
}
else
{
// other ways of branching out of a lambda are errors, previously reported in control-flow analysis
}
}
// Check for changes to the read and write sets
if (RecordChangedVars(ref usages.WrittenVars,
ref stateAtReturn.Assigned,
ref oldReads,
ref usages.ReadVars) &&
usages.LocalFuncVisited)
{
stateChangedAfterUse = true;
usages.LocalFuncVisited = false;
}
this.State = savedState;
this.currentMethodOrLambda = oldMethodOrLambda;
return null;
}
private void RecordReadInLocalFunction(int slot)
{
var localFunc = GetNearestLocalFunctionOpt(currentMethodOrLambda);
Debug.Assert(localFunc != null);
var usages = GetOrCreateLocalFuncUsages(localFunc);
usages.ReadVars[slot] = true;
}
private bool RecordChangedVars(ref BitVector oldWrites,
ref BitVector newWrites,
ref BitVector oldReads,
ref BitVector newReads)
{
bool anyChanged = RecordCapturedChanges(ref oldWrites, ref newWrites);
anyChanged |= RecordCapturedChanges(ref oldReads, ref newReads);
return anyChanged;
}
private bool RecordCapturedChanges(ref BitVector oldState,
ref BitVector newState)
{
// Build a list of variables that are both captured and assigned
var capturedMask = GetCapturedBitmask(ref newState);
var capturedAndSet = newState;
capturedAndSet.IntersectWith(capturedMask);
// Union and check to see if there are any changes
return oldState.UnionWith(capturedAndSet);
}
private BitVector GetCapturedBitmask(ref BitVector state)
{
BitVector mask = BitVector.Empty;
for (int slot = 1; slot < state.Capacity; slot++)
{
if (IsCapturedInLocalFunction(slot))
{
mask[slot] = true;
}
}
return mask;
}
private bool IsCapturedInLocalFunction(int slot,
ParameterSymbol rangeVariableUnderlyingParameter = null)
{
if (slot <= 0) return false;
// Find the root slot, since that would be the only
// slot, if any, that is captured in a local function
var rootVarInfo = variableBySlot[RootSlot(slot)];
var rootSymbol = rootVarInfo.Symbol;
// A variable is captured in a local function iff its
// container is higher in the tree than the nearest
// local function
var nearestLocalFunc = GetNearestLocalFunctionOpt(currentMethodOrLambda);
return (object)nearestLocalFunc != null &&
IsCaptured(rootSymbol, nearestLocalFunc, rangeVariableUnderlyingParameter);
}
private LocalFuncUsages GetOrCreateLocalFuncUsages(LocalFunctionSymbol localFunc)
{
LocalFuncUsages usages;
if (!_localFuncVarUsages.TryGetValue(localFunc, out usages))
{
usages = new LocalFuncUsages();
_localFuncVarUsages[localFunc] = usages;
}
return usages;
}
private static LocalFunctionSymbol GetNearestLocalFunctionOpt(Symbol symbol)
{
while (symbol != null)
{
if (symbol.Kind == SymbolKind.Method &&
((MethodSymbol)symbol).MethodKind == MethodKind.LocalFunction)
{
return (LocalFunctionSymbol)symbol;
}
symbol = symbol.ContainingSymbol;
}
return null;
}
}
}
......@@ -91,9 +91,7 @@ public override BoundNode VisitRangeVariable(BoundRangeVariable node)
return null;
}
protected override void ReportUnassigned(
Symbol symbol,
SyntaxNode node)
protected override void ReportUnassigned(Symbol symbol, SyntaxNode node)
{
// TODO: how to handle fields of structs?
if (RegionContains(node.Span) && !(symbol is FieldSymbol))
......
......@@ -51,12 +51,12 @@ internal abstract partial class PreciseAbstractFlowPass<LocalState> : BoundTreeV
private readonly PooledDictionary<LabelSymbol, LocalState> _labels;
/// <summary>
/// Set to true after an analysis scan if the analysis was incomplete due to a backward
/// "goto" branch changing some analysis result. In this case the caller scans again (until
/// Set to true after an analysis scan if the analysis was incomplete due to state changing
/// after it was used by another analysis component. In this case the caller scans again (until
/// this is false). Since the analysis proceeds by monotonically changing the state computed
/// at each label, this must terminate.
/// </summary>
internal bool backwardBranchChanged;
protected bool stateChangedAfterUse;
/// <summary>
/// See property PendingBranches
......@@ -376,11 +376,11 @@ protected ImmutableArray<PendingBranch> Analyze(ref bool badRegion)
this.State = ReachableState();
_pendingBranches.Clear();
if (_trackExceptions) _pendingBranches.Add(new PendingBranch(null, ReachableState()));
this.backwardBranchChanged = false;
this.stateChangedAfterUse = false;
this.Diagnostics.Clear();
returns = this.Scan(ref badRegion);
}
while (this.backwardBranchChanged);
while (this.stateChangedAfterUse);
return returns;
}
......@@ -651,7 +651,7 @@ private void LoopTail(BoundLoopStatement node)
if (IntersectWith(ref oldState, ref this.State))
{
_loopHeadState[node] = oldState;
this.backwardBranchChanged = true;
this.stateChangedAfterUse = true;
}
}
......@@ -848,13 +848,13 @@ protected void RestorePending(SavedPending oldPending)
case BoundKind.LabeledStatement:
{
var label = (BoundLabeledStatement)node;
backwardBranchChanged |= ResolveBranches(label.Label, label);
stateChangedAfterUse |= ResolveBranches(label.Label, label);
}
break;
case BoundKind.LabelStatement:
{
var label = (BoundLabelStatement)node;
backwardBranchChanged |= ResolveBranches(label.Label, label);
stateChangedAfterUse |= ResolveBranches(label.Label, label);
}
break;
case BoundKind.SwitchSection:
......@@ -862,7 +862,7 @@ protected void RestorePending(SavedPending oldPending)
var sec = (BoundSwitchSection)node;
foreach (var label in sec.SwitchLabels)
{
backwardBranchChanged |= ResolveBranches(label.Label, sec);
stateChangedAfterUse |= ResolveBranches(label.Label, sec);
}
}
break;
......@@ -871,7 +871,7 @@ protected void RestorePending(SavedPending oldPending)
var sec = (BoundPatternSwitchSection)node;
foreach (var label in sec.SwitchLabels)
{
backwardBranchChanged |= ResolveBranches(label.Label, sec);
stateChangedAfterUse |= ResolveBranches(label.Label, sec);
}
}
break;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.CSharp.Symbols;
using System.Collections.Immutable;
using System.Diagnostics;
namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed partial class LambdaRewriter : MethodToClassRewriter
{
/// <summary>
/// This pass is expected to run on partially lowered methods
/// previously containing one or more local functions. At this
/// point all local functions should have been rewritten into
/// proper closure classes and have frames and proxies generated
/// for them.
///
/// The only thing left is to visit all "references" to local functions
/// and rewrite them to be references to the rewritten form.
/// </summary>
private sealed class LocalFunctionReferenceRewriter : BoundTreeRewriterWithStackGuard
{
private readonly LambdaRewriter _lambdaRewriter;
public LocalFunctionReferenceRewriter(LambdaRewriter lambdaRewriter)
{
_lambdaRewriter = lambdaRewriter;
}
public override BoundNode VisitCall(BoundCall node)
{
if (node.Method.MethodKind == MethodKind.LocalFunction)
{
BoundExpression receiver;
MethodSymbol method;
var arguments = node.Arguments;
_lambdaRewriter.RemapLocalFunction(node.Syntax, node.Method, out receiver, out method, ref arguments);
node = node.Update(receiver, method, arguments);
}
return base.VisitCall(node);
}
public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationExpression node)
{
if (node.MethodOpt?.MethodKind == MethodKind.LocalFunction)
{
BoundExpression receiver;
MethodSymbol method;
var arguments = default(ImmutableArray<BoundExpression>);
_lambdaRewriter.RemapLocalFunction(
node.Syntax, node.MethodOpt, out receiver, out method, ref arguments);
return new BoundDelegateCreationExpression(
node.Syntax, receiver, method, isExtensionMethod: false, type: node.Type);
}
return base.VisitDelegateCreationExpression(node);
}
public override BoundNode VisitConversion(BoundConversion conversion)
{
if (conversion.ConversionKind == ConversionKind.MethodGroup &&
conversion.SymbolOpt?.MethodKind == MethodKind.LocalFunction)
{
BoundExpression receiver;
MethodSymbol method;
var arguments = default(ImmutableArray<BoundExpression>);
_lambdaRewriter.RemapLocalFunction(
conversion.Syntax, conversion.SymbolOpt, out receiver, out method, ref arguments);
return new BoundDelegateCreationExpression(
conversion.Syntax, receiver, method, isExtensionMethod: false, type: conversion.Type);
}
return base.VisitConversion(conversion);
}
}
private void RemapLocalFunction(
SyntaxNode syntax,
MethodSymbol symbol,
out BoundExpression receiver,
out MethodSymbol method,
ref ImmutableArray<BoundExpression> parameters,
ImmutableArray<TypeSymbol> typeArguments = default(ImmutableArray<TypeSymbol>))
{
Debug.Assert(symbol.MethodKind == MethodKind.LocalFunction);
if ((object)symbol != symbol.ConstructedFrom)
{
RemapLocalFunction(syntax,
symbol.ConstructedFrom,
out receiver,
out method,
ref parameters,
TypeMap.SubstituteTypes(symbol.TypeArguments)
.SelectAsArray(t => t.Type));
return;
}
var mappedLocalFunction = _localFunctionMap[(LocalFunctionSymbol)symbol];
var lambda = mappedLocalFunction.Symbol;
var frameCount = lambda.ExtraSynthesizedParameterCount;
if (frameCount != 0)
{
Debug.Assert(!parameters.IsDefault);
var builder = ArrayBuilder<BoundExpression>.GetInstance();
builder.AddRange(parameters);
var start = lambda.ParameterCount - frameCount;
for (int i = start; i < lambda.ParameterCount; i++)
{
// will always be a LambdaFrame, it's always a closure class
var frameType = (NamedTypeSymbol)lambda.Parameters[i].Type.OriginalDefinition;
Debug.Assert(frameType is LambdaFrame);
if (frameType.IsGenericType)
{
var typeParameters = ((LambdaFrame)frameType).ConstructedFromTypeParameters;
var subst = this.TypeMap.SubstituteTypeParameters(typeParameters);
frameType = frameType.Construct(subst);
}
var frame = FrameOfType(syntax, frameType);
builder.Add(frame);
}
parameters = builder.ToImmutableAndFree();
}
method = lambda;
NamedTypeSymbol constructedFrame;
RemapLambdaOrLocalFunction(syntax,
symbol,
typeArguments,
mappedLocalFunction.ClosureKind,
ref method,
out receiver,
out constructedFrame);
}
}
}
......@@ -31,11 +31,18 @@ namespace Microsoft.CodeAnalysis.CSharp
/// in <see cref="_frames"/>. Each frame is given a single field for each captured
/// variable in the corresponding scope. These are maintained in <see cref="MethodToClassRewriter.proxies"/>.
///
/// Finally, we walk and rewrite the input bound tree, keeping track of the following:
/// Next, we walk and rewrite the input bound tree, keeping track of the following:
/// (1) The current set of active frame pointers, in <see cref="_framePointers"/>
/// (2) The current method being processed (this changes within a lambda's body), in <see cref="_currentMethod"/>
/// (3) The "this" symbol for the current method in <see cref="_currentFrameThis"/>, and
/// (4) The symbol that is used to access the innermost frame pointer (it could be a local variable or "this" parameter)
///
/// Lastly, we visit the top-level method and each of the lowered methods
/// to rewrite references (e.g., calls and delegate conversions) to local
/// functions. We visit references to local functions separately from
/// lambdas because we may see the reference before we lower the target
/// local function. Lambdas, on the other hand, are always convertible as
/// they are being lowered.
///
/// There are a few key transformations done in the rewriting.
/// (1) Lambda expressions are turned into delegate creation expressions, and the body of the lambda is
......@@ -43,8 +50,8 @@ namespace Microsoft.CodeAnalysis.CSharp
/// (2) On entry to a scope with captured variables, we create a frame object and store it in a local variable.
/// (3) References to captured variables are transformed into references to fields of a frame class.
///
/// In addition, the rewriting deposits into <see cref="TypeCompilationState.SynthesizedMethods"/> a (<see cref="MethodSymbol"/>, <see cref="BoundStatement"/>)
/// pair for each generated method.
/// In addition, the rewriting deposits into <see cref="TypeCompilationState.SynthesizedMethods"/>
/// a (<see cref="MethodSymbol"/>, <see cref="BoundStatement"/>) pair for each generated method.
///
/// <see cref="Rewrite"/> produces its output in two forms. First, it returns a new bound statement
/// for the caller to use for the body of the original method. Second, it returns a collection of
......@@ -79,6 +86,7 @@ public MappedLocalFunction(SynthesizedLambdaMethod symbol, ClosureKind closureKi
ClosureKind = closureKind;
}
}
private readonly Dictionary<LocalFunctionSymbol, MappedLocalFunction> _localFunctionMap = new Dictionary<LocalFunctionSymbol, MappedLocalFunction>();
// for each block with lifted (captured) variables, the corresponding frame type
......@@ -237,7 +245,18 @@ protected override bool NeedsProxy(Symbol localOrParameter)
analysis.ComputeLambdaScopesAndFrameCaptures();
rewriter.MakeFrames(closureDebugInfoBuilder);
var body = rewriter.AddStatementsIfNeeded((BoundStatement)rewriter.Visit(loweredBody));
// First, lower everything but references (calls, delegate conversions)
// to local functions
var body = rewriter.AddStatementsIfNeeded(
(BoundStatement)rewriter.Visit(loweredBody));
// Now lower the references
if (rewriter._localFunctionMap.Count != 0)
{
body = rewriter.RewriteLocalFunctionReferences(body);
}
CheckLocalsDefined(body);
return body;
......@@ -302,21 +321,21 @@ private void MakeFrames(ArrayBuilder<ClosureDebugInfo> closureDebugInfo)
if (captured.Kind != SymbolKind.Method)
{
var hoistedField = LambdaCapturedVariable.Create(frame, captured, ref _synthesizedFieldNameIdDispenser);
proxies.Add(captured, new CapturedToFrameSymbolReplacement(hoistedField, isReusable: false));
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(frame, hoistedField);
var hoistedField = LambdaCapturedVariable.Create(frame, captured, ref _synthesizedFieldNameIdDispenser);
proxies.Add(captured, new CapturedToFrameSymbolReplacement(hoistedField, isReusable: false));
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(frame, hoistedField);
if (hoistedField.Type.IsRestrictedType())
{
foreach (CSharpSyntaxNode syntax in kvp.Value)
if (hoistedField.Type.IsRestrictedType())
{
// CS4013: Instance of type '{0}' cannot be used inside an anonymous function, query expression, iterator block or async method
this.Diagnostics.Add(ErrorCode.ERR_SpecialByRefInLambda, syntax.Location, hoistedField.Type);
foreach (CSharpSyntaxNode syntax in kvp.Value)
{
// CS4013: Instance of type '{0}' cannot be used inside an anonymous function, query expression, iterator block or async method
this.Diagnostics.Add(ErrorCode.ERR_SpecialByRefInLambda, syntax.Location, hoistedField.Type);
}
}
}
}
}
}
private LambdaFrame GetFrameForScope(BoundNode scope, ArrayBuilder<ClosureDebugInfo> closureDebugInfo)
{
......@@ -590,7 +609,6 @@ private T IntroduceFrame<T>(BoundNode node, LambdaFrame frame, Func<ArrayBuilder
var result = F(prologue, addedLocals);
_framePointers.Remove(frame);
_innermostFramePointer = oldInnermostFramePointer;
if ((object)_innermostFramePointer != null)
......@@ -690,6 +708,70 @@ public override BoundNode VisitBaseReference(BoundBaseReference node)
: FramePointer(node.Syntax, _topLevelMethod.ContainingType); // technically, not the correct static type
}
/// <summary>
/// Visit all references to local functions (calls, delegete
/// conversions, delegate creations) and rewrite them to point
/// to the rewritten local function method instead of the original.
/// </summary>
public BoundStatement RewriteLocalFunctionReferences(BoundStatement loweredBody)
{
var rewriter = new LocalFunctionReferenceRewriter(this);
Debug.Assert(_currentMethod == _topLevelMethod);
// Visit the body first since the state is already set
// for the top-level method
var newBody = (BoundStatement)rewriter.Visit(loweredBody);
// Visit all the rewritten methods as well
var synthesizedMethods = CompilationState.SynthesizedMethods;
if (synthesizedMethods != null)
{
// Dump the existing methods for rewriting
var oldMethods = synthesizedMethods.ToImmutable();
synthesizedMethods.Clear();
foreach (var oldMethod in oldMethods)
{
var synthesizedLambda = oldMethod.Method as SynthesizedLambdaMethod;
if (synthesizedLambda == null)
{
// The only methods synthesized by the rewriter should
// be lowered closures and frame constructors
Debug.Assert(oldMethod.Method.MethodKind == MethodKind.Constructor ||
oldMethod.Method.MethodKind == MethodKind.StaticConstructor);
CompilationState.AddSynthesizedMethod(oldMethod.Method, oldMethod.Body);
continue;
}
_currentMethod = synthesizedLambda;
var closureKind = synthesizedLambda.ClosureKind;
if (closureKind == ClosureKind.Static || closureKind == ClosureKind.Singleton)
{
// no link from a static lambda to its container
_innermostFramePointer = _currentFrameThis = null;
}
else
{
_currentFrameThis = synthesizedLambda.ThisParameter;
_innermostFramePointer = null;
_framePointers.TryGetValue(synthesizedLambda.ContainingType, out _innermostFramePointer);
}
_currentTypeParameters = synthesizedLambda.ContainingType
?.TypeParameters.Concat(synthesizedLambda.TypeParameters)
?? synthesizedLambda.TypeParameters;
_currentLambdaBodyTypeMap = synthesizedLambda.TypeMap;
var rewrittenBody = (BoundStatement)rewriter.Visit(oldMethod.Body);
CompilationState.AddSynthesizedMethod(synthesizedLambda, rewrittenBody);
}
}
return newBody;
}
private void RemapLambdaOrLocalFunction(
SyntaxNode syntax,
MethodSymbol originalMethod,
......@@ -749,62 +831,34 @@ public override BoundNode VisitBaseReference(BoundBaseReference node)
}
}
private void RemapLocalFunction(
SyntaxNode syntax, MethodSymbol symbol,
out BoundExpression receiver, out MethodSymbol method,
ref ImmutableArray<BoundExpression> parameters,
ImmutableArray<TypeSymbol> typeArguments = default(ImmutableArray<TypeSymbol>))
{
Debug.Assert(symbol.MethodKind == MethodKind.LocalFunction);
var constructed = symbol as ConstructedMethodSymbol;
if (constructed != null)
{
RemapLocalFunction(syntax, constructed.ConstructedFrom, out receiver, out method, ref parameters, this.TypeMap.SubstituteTypes(constructed.TypeArguments).SelectAsArray(t => t.Type));
return;
}
var mappedLocalFunction = _localFunctionMap[(LocalFunctionSymbol)symbol];
var lambda = mappedLocalFunction.Symbol;
var frameCount = lambda.ExtraSynthesizedParameterCount;
if (frameCount != 0)
{
Debug.Assert(!parameters.IsDefault);
var builder = ArrayBuilder<BoundExpression>.GetInstance();
builder.AddRange(parameters);
var start = lambda.ParameterCount - frameCount;
for (int i = start; i < lambda.ParameterCount; i++)
{
// will always be a LambdaFrame, it's always a closure class
var frameType = (NamedTypeSymbol)lambda.Parameters[i].Type.OriginalDefinition;
if (frameType.IsGenericType)
{
var typeParameters = ((LambdaFrame)frameType).ConstructedFromTypeParameters;
var subst = this.TypeMap.SubstituteTypeParameters(typeParameters);
frameType = frameType.Construct(subst);
}
var frame = FrameOfType(syntax, frameType);
builder.Add(frame);
}
parameters = builder.ToImmutableAndFree();
}
method = lambda;
NamedTypeSymbol constructedFrame;
RemapLambdaOrLocalFunction(syntax, symbol, typeArguments, mappedLocalFunction.ClosureKind, ref method, out receiver, out constructedFrame);
}
/// <remarks>
/// This pass doesn't rewrite the local function calls themselves
/// because we may encounter a call to a local function that has yet
/// to be lowered. Here we just want to make sure we lower the
/// arguments as they may contain references to captured variables.
/// The final lowering of the call will be in the
/// <see cref="LocalFunctionReferenceRewriter" />
/// </remarks>
public override BoundNode VisitCall(BoundCall node)
{
if (node.Method.MethodKind == MethodKind.LocalFunction)
{
BoundExpression receiver;
MethodSymbol method;
var arguments = node.Arguments;
RemapLocalFunction(node.Syntax, node.Method, out receiver, out method, ref arguments);
node = node.Update(receiver, method, arguments);
var rewrittenArguments = this.VisitList(node.Arguments);
return node.Update(
node.ReceiverOpt,
node.Method,
rewrittenArguments,
node.ArgumentNamesOpt,
node.ArgumentRefKindsOpt,
node.IsDelegateCall,
node.Expanded,
node.InvokedAsExtensionMethod,
node.ArgsToParamsOpt,
node.ResultKind,
node.Type);
}
var visited = base.VisitCall(node);
if (visited.Kind != BoundKind.Call)
{
......@@ -1034,12 +1088,7 @@ public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationE
{
if (node.MethodOpt?.MethodKind == MethodKind.LocalFunction)
{
BoundExpression receiver;
MethodSymbol method;
var arguments = default(ImmutableArray<BoundExpression>);
RemapLocalFunction(node.Syntax, node.MethodOpt, out receiver, out method, ref arguments);
var result = new BoundDelegateCreationExpression(node.Syntax, receiver, method, isExtensionMethod: false, type: node.Type);
return result;
return node;
}
return base.VisitDelegateCreationExpression(node);
}
......@@ -1068,14 +1117,10 @@ public override BoundNode VisitConversion(BoundConversion conversion)
}
else
{
if (conversion.ConversionKind == ConversionKind.MethodGroup && conversion.SymbolOpt?.MethodKind == MethodKind.LocalFunction)
if (conversion.ConversionKind == ConversionKind.MethodGroup &&
conversion.SymbolOpt?.MethodKind == MethodKind.LocalFunction)
{
BoundExpression receiver;
MethodSymbol method;
var arguments = default(ImmutableArray<BoundExpression>);
RemapLocalFunction(conversion.Syntax, conversion.SymbolOpt, out receiver, out method, ref arguments);
var result = new BoundDelegateCreationExpression(conversion.Syntax, receiver, method, isExtensionMethod: false, type: conversion.Type);
return result;
return conversion;
}
return base.VisitConversion(conversion);
}
......@@ -1207,7 +1252,7 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos
{
found = true;
break;
}
}
}
if (found)
{
......@@ -1228,17 +1273,17 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos
closureOrdinal = LambdaDebugInfo.StaticClosureOrdinal;
}
else
{
closureKind = ClosureKind.General;
{
closureKind = ClosureKind.General;
translatedLambdaContainer = containerAsFrame;
closureOrdinal = containerAsFrame.ClosureOrdinal;
}
closureOrdinal = containerAsFrame.ClosureOrdinal;
}
}
else if (_analysis.CapturedVariablesByLambda[node.Symbol].Count == 0)
{
if (_analysis.MethodsConvertedToDelegates.Contains(node.Symbol))
{
translatedLambdaContainer = containerAsFrame = GetStaticFrame(Diagnostics, node);
{
translatedLambdaContainer = containerAsFrame = GetStaticFrame(Diagnostics, node);
closureKind = ClosureKind.Singleton;
closureOrdinal = LambdaDebugInfo.StaticClosureOrdinal;
}
......@@ -1246,9 +1291,9 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos
{
containerAsFrame = null;
translatedLambdaContainer = _topLevelMethod.ContainingType;
closureKind = ClosureKind.Static;
closureOrdinal = LambdaDebugInfo.StaticClosureOrdinal;
}
closureKind = ClosureKind.Static;
closureOrdinal = LambdaDebugInfo.StaticClosureOrdinal;
}
structClosures = default(ImmutableArray<TypeSymbol>);
}
else
......@@ -1321,7 +1366,7 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos
_addedStatements = oldAddedStatements;
return synthesizedMethod;
}
}
private BoundNode RewriteLambdaConversion(BoundLambda node)
{
......
......@@ -37,6 +37,7 @@ internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISy
| (lambdaNode.Symbol.IsAsync ? DeclarationModifiers.Async : 0))
{
_topLevelMethod = topLevelMethod;
ClosureKind = closureKind;
TypeMap typeMap;
ImmutableArray<TypeParameterSymbol> typeParameters;
......@@ -141,5 +142,7 @@ internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree l
// The lambda method body needs to be updated when the containing top-level method body is updated.
bool ISynthesizedMethodBodyImplementationSymbol.HasMethodBodyDependency => true;
public ClosureKind ClosureKind { get; }
}
}
......@@ -97,6 +97,7 @@
<Compile Include="CodeGen\CodeGenIncrementTests.cs" />
<Compile Include="CodeGen\CodeGenInterfaceImplementation.cs" />
<Compile Include="CodeGen\CodeGenIterators.cs" />
<Compile Include="CodeGen\CodeGenLocalFunctionTests.cs" />
<Compile Include="CodeGen\CodeGenMscorlib.cs" />
<Compile Include="CodeGen\CodeGenOperators.cs" />
<Compile Include="CodeGen\CodeGenOptimizedNullableOperators.cs" />
......
......@@ -67,6 +67,7 @@
<Compile Include="FlowAnalysis\FlowTestBase.cs" />
<Compile Include="FlowAnalysis\FlowTests.cs" />
<Compile Include="FlowAnalysis\IterationJumpYieldStatementTests.cs" />
<Compile Include="FlowAnalysis\LocalFunctions.cs" />
<Compile Include="FlowAnalysis\PatternsVsRegions.cs" />
<Compile Include="FlowAnalysis\RegionAnalysisTests.cs" />
<Compile Include="FlowAnalysis\StructTests.cs" />
......
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
[CompilerTrait(CompilerFeature.LocalFunctions)]
public class LocalFunctions : FlowTestBase
{
[Fact]
public void SimpleForwardCall()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
var x = Local();
int Local() => 2;
}
}");
comp.VerifyDiagnostics();
}
[Fact]
public void DefinedWhenCalled()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
bool Local() => x == 0;
x = 0;
Local();
}
}");
comp.VerifyDiagnostics();
}
[Fact]
public void NotDefinedWhenCalled()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
bool Local() => x == 0;
Local();
}
}");
comp.VerifyDiagnostics(
// (8,9): error CS0165: Use of unassigned local variable 'x'
// Local();
Diagnostic(ErrorCode.ERR_UseDefViolation, "Local()").WithArguments("x").WithLocation(8, 9)
);
}
[Fact]
public void ChainedDef()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
bool Local2() => Local1();
bool Local1() => x == 0;
Local2();
}
}");
comp.VerifyDiagnostics(
// (9,9): error CS0165: Use of unassigned local variable 'x'
// Local2();
Diagnostic(ErrorCode.ERR_UseDefViolation, "Local2()").WithArguments("x").WithLocation(9, 9)
);
}
[Fact]
public void SetInLocalFunc()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
void L1()
{
x = 0;
}
bool L2() => x == 0;
L1();
L2();
}
}
");
comp.VerifyDiagnostics();
}
[Fact]
public void SetInLocalFuncMutual()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
bool L1()
{
L2();
return x == 0;
}
void L2()
{
x = 0;
L1();
}
L1();
}
}
");
comp.VerifyDiagnostics();
}
[Fact]
public void LongWriteChain()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public void M()
{
int x;
bool L1()
{
L2();
return x == 0;
}
bool L2()
{
L3();
return x == 0;
}
bool L3()
{
L4();
return x == 0;
}
void L4()
{
x = 0;
}
L1();
}
}");
comp.VerifyDiagnostics();
}
[Fact]
public void ConvertBeforeDefined()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
bool L1() => x == 0;
System.Func<bool> f = L1;
x = 0;
f();
}
}");
comp.VerifyDiagnostics(
// (8,31): error CS0165: Use of unassigned local variable 'x'
// System.Func<bool> f = L1;
Diagnostic(ErrorCode.ERR_UseDefViolation, "L1").WithArguments("x").WithLocation(8, 31));
}
[Fact]
public void NestedCapture()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
void Local1()
{
int x;
bool Local2() => x == 0;
Local2();
}
Local1();
}
}");
comp.VerifyDiagnostics(
// (10,13): error CS0165: Use of unassigned local variable 'x'
// Local2();
Diagnostic(ErrorCode.ERR_UseDefViolation, "Local2()").WithArguments("x").WithLocation(10, 13));
}
[Fact]
public void UnusedLocalFunc()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
bool Local() => x == 0;
}
}");
comp.VerifyDiagnostics(
// (7,14): warning CS0168: The variable 'Local' is declared but never used
// bool Local() => x == 0;
Diagnostic(ErrorCode.WRN_UnreferencedVar, "Local").WithArguments("Local").WithLocation(7, 14));
}
[Fact]
public void UnassignedInStruct()
{
var comp = CreateCompilationWithMscorlib(@"
struct S
{
int _x;
public S(int x)
{
var s = this;
void Local()
{
s._x = _x;
}
Local();
}
}");
comp.VerifyDiagnostics(
// (10,20): error CS1673: Anonymous methods, lambda expressions, and query expressions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression or query expression and using the local instead.
// s._x = _x;
Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "_x").WithLocation(10, 20),
// (7,17): error CS0188: The 'this' object cannot be used before all of its fields are assigned to
// var s = this;
Diagnostic(ErrorCode.ERR_UseDefViolationThis, "this").WithArguments("this").WithLocation(7, 17),
// (12,9): error CS0170: Use of possibly unassigned field '_x'
// Local();
Diagnostic(ErrorCode.ERR_UseDefViolationField, "Local()").WithArguments("_x").WithLocation(12, 9),
// (5,12): error CS0171: Field 'S._x' must be fully assigned before control is returned to the caller
// public S(int x)
Diagnostic(ErrorCode.ERR_UnassignedThis, "S").WithArguments("S._x").WithLocation(5, 12));
}
[Fact]
public void AssignWithStruct()
{
var comp = CreateCompilationWithMscorlib(@"
struct S
{
public int x;
public int y;
}
class C
{
public void M1()
{
S s1;
Local();
S s2 = s1; // unassigned
void Local()
{
s1.x = 0;
}
s1.y = 0;
}
public void M2()
{
S s1;
Local();
S s2 = s1; // success
void Local()
{
s1.x = 0;
s1.y = 0;
}
}
public void M3()
{
S s1;
S s2 = s1; // unassigned
Local();
void Local()
{
s1.x = 0;
s1.y = 0;
}
}
void M4()
{
S s1;
Local();
void Local()
{
s1.x = 0;
s1.x += s1.y;
}
}
}");
comp.VerifyDiagnostics(
// (14,16): error CS0165: Use of unassigned local variable 's1'
// S s2 = s1; // unassigned
Diagnostic(ErrorCode.ERR_UseDefViolation, "s1").WithArguments("s1").WithLocation(14, 16),
// (37,16): error CS0165: Use of unassigned local variable 's1'
// S s2 = s1; // unassigned
Diagnostic(ErrorCode.ERR_UseDefViolation, "s1").WithArguments("s1").WithLocation(37, 16),
// (48,9): error CS0170: Use of possibly unassigned field 'y'
// Local();
Diagnostic(ErrorCode.ERR_UseDefViolationField, "Local()").WithArguments("y").WithLocation(48, 9));
}
[Fact]
public void NestedStructProperty()
{
var comp = CreateCompilationWithMscorlib(@"
struct A
{
public int x;
public int y { get; set; }
}
struct B
{
public A a;
public int z;
}
class C
{
void AssignInLocalFunc()
{
A a1;
Local1(); // unassigned
A a2 = a1;
void Local1()
{
a1.x = 0;
a1.y = 0;
}
B b1;
Local2();
B b2 = b1;
void Local2()
{
b1.a.x = 0;
b1.a.y = 0;
b1.z = 0;
}
}
void SkipNestedField()
{
B b1;
Local();
B b2 = b1; // unassigned
void Local()
{
b1.a.x = 0;
b1.z = 0;
}
}
void SkipNestedStruct()
{
B b1;
Local();
B b2 = b1; // unassigned
void Local()
{
b1.z = 0;
}
}
void SkipField()
{
B b1;
Local();
B b2 = b1; // unassigned
void Local()
{
b1.a.x = 0;
b1.a.y = 0;
}
}
}");
comp.VerifyDiagnostics(
// (19,9): error CS0165: Use of unassigned local variable 'a1'
// Local1();
Diagnostic(ErrorCode.ERR_UseDefViolation, "Local1()").WithArguments("a1").WithLocation(19, 9),
// (28,9): error CS0170: Use of possibly unassigned field 'a'
// Local2();
Diagnostic(ErrorCode.ERR_UseDefViolationField, "Local2()").WithArguments("a").WithLocation(28, 9),
// (41,16): error CS0165: Use of unassigned local variable 'b1'
// B b2 = b1; // unassigned
Diagnostic(ErrorCode.ERR_UseDefViolation, "b1").WithArguments("b1").WithLocation(41, 16),
// (52,16): error CS0165: Use of unassigned local variable 'b1'
// B b2 = b1; // unassigned
Diagnostic(ErrorCode.ERR_UseDefViolation, "b1").WithArguments("b1").WithLocation(52, 16),
// (61,9): error CS0170: Use of possibly unassigned field 'a'
// Local();
Diagnostic(ErrorCode.ERR_UseDefViolationField, "Local()").WithArguments("a").WithLocation(61, 9),
// (62,16): error CS0165: Use of unassigned local variable 'b1'
// B b2 = b1; // unassigned
Diagnostic(ErrorCode.ERR_UseDefViolation, "b1").WithArguments("b1").WithLocation(62, 16));
}
[Fact]
public void WriteAndReadInLocalFunc()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public void M()
{
int x;
bool b;
Local();
void Local()
{
x = x + 1;
x = 0;
}
b = x == 0;
}
}");
comp.VerifyDiagnostics(
// (8,9): error CS0165: Use of unassigned local variable 'x'
// Local();
Diagnostic(ErrorCode.ERR_UseDefViolation, "Local()").WithArguments("x").WithLocation(8, 9));
}
[Fact]
public void EventReadAndWrite()
{
var comp = CreateCompilationWithMscorlib(@"
using System;
struct S
{
public int x;
public event EventHandler Event;
public void Fire() => Event(null, EventArgs.Empty);
}
class C
{
void PartialAssign()
{
S s1;
Local1();
S s2 = s1;
void Local1()
{
s1.x = 0;
}
}
void FullAssign()
{
S s1;
Local1();
S s2 = s1;
void Local1()
{
s1 = new S();
s1.x = 0;
s1.Event += Handler1;
s1.Fire();
void Handler1(object sender, EventArgs args)
{
s1.x++;
}
}
S s3;
void Local2()
{
s3.x = 0;
s3.Event += Handler2;
void Handler2(object sender, EventArgs args)
{
s1.x++;
s3.x++;
}
}
S s4 = s3;
Local2();
}
}");
comp.VerifyDiagnostics(
// (18,16): error CS0165: Use of unassigned local variable 's1'
// S s2 = s1;
Diagnostic(ErrorCode.ERR_UseDefViolation, "s1").WithArguments("s1").WithLocation(18, 16),
// (54,16): error CS0165: Use of unassigned local variable 's3'
// S s4 = s3;
Diagnostic(ErrorCode.ERR_UseDefViolation, "s3").WithArguments("s3").WithLocation(54, 16));
}
[Fact]
public void CaptureForeachVar()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void M()
{
var items = new[] { 0, 1, 2, 3};
foreach (var i in items)
{
void Local()
{
i = 4;
}
Local();
}
}
}");
comp.VerifyDiagnostics(
// (11,17): error CS1656: Cannot assign to 'i' because it is a 'foreach iteration variable'
// i = 4;
Diagnostic(ErrorCode.ERR_AssgReadonlyLocalCause, "i").WithArguments("i", "foreach iteration variable").WithLocation(11, 17));
}
[Fact]
public void CapturePattern()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void M()
{
object o = 2;
if (o is int x1 && Local(x1) == 0)
{
}
if (o is int x2 || Local(x2) == 0)
{
}
if (!(o is int x3))
{
void Local2()
{
x3++;
}
Local2();
}
int Local(int i) => i;
}
}");
comp.VerifyDiagnostics(
// (11,34): error CS0165: Use of unassigned local variable 'x2'
// if (o is int x2 || Local(x2) == 0)
Diagnostic(ErrorCode.ERR_UseDefViolation, "x2").WithArguments("x2").WithLocation(11, 34),
// (21,13): error CS0165: Use of unassigned local variable 'x3'
// Local2();
Diagnostic(ErrorCode.ERR_UseDefViolation, "Local2()").WithArguments("x3").WithLocation(21, 13));
}
[Fact]
public void NotAssignedControlFlow()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void FullyAssigned()
{
int x;
int y = 0;
void Local()
{
if (y == 0)
x = 0;
else
Local2();
}
void Local2()
{
x = 0;
}
Local();
y = x;
}
void PartiallyAssigned()
{
int x;
int y = 0;
void Local()
{
if (y == 0)
x = 0;
else
Local2();
}
void Local2()
{
//x = 0;
}
Local();
y = x; // unassigned
}
}");
comp.VerifyDiagnostics(
// (38,13): error CS0165: Use of unassigned local variable 'x'
// y = x; // unassigned
Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(38, 13));
}
[Fact]
public void UseConsts()
{
var comp = CreateCompilationWithMscorlib(@"
struct S
{
public const int z = 0;
}
class C
{
const int x = 0;
void M()
{
const int y = 0;
Local();
int Local()
{
const int a = 1;
return a + x + y + S.z;
}
}
}");
comp.VerifyDiagnostics();
}
[Fact]
public void NotAssignedAtAllReturns()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void M()
{
int x;
void L1()
{
if ("""".Length == 1)
{
x = 1;
}
else
{
return;
}
}
L1();
var z = x;
}
}");
comp.VerifyDiagnostics(
// (19,17): error CS0165: Use of unassigned local variable 'x'
// var z = x;
Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(19, 17));
}
[Fact]
public void NotAssignedAtThrow()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void M()
{
int x1, x2;
void L1()
{
if ("""".Length == 1)
x1 = x2 = 0;
else
throw new System.Exception();
}
try
{
L1();
var y = x1;
}
catch
{
var z = x1;
}
var zz = x2;
}
}");
comp.VerifyDiagnostics(
// (21,21): error CS0165: Use of unassigned local variable 'x1'
// var z = x1;
Diagnostic(ErrorCode.ERR_UseDefViolation, "x1").WithArguments("x1").WithLocation(21, 21),
// (23,18): error CS0165: Use of unassigned local variable 'x2'
// var zz = x2;
Diagnostic(ErrorCode.ERR_UseDefViolation, "x2").WithArguments("x2").WithLocation(23, 18));
}
[Fact]
public void DeadCode()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void M()
{
int x;
goto live;
void L1() => x = 0;
live:
L1();
var z = x;
}
void M2()
{
int x;
goto live;
void L1()
{
if ("""".Length == 1)
x = 0;
else
return;
}
live:
L1();
var z = x;
}
}");
comp.VerifyDiagnostics(
// (26,17): error CS0165: Use of unassigned local variable 'x'
// var z = x;
Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(26, 17));
}
[Fact]
public void LocalFunctionFromOtherSwitch()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void M()
{
int x;
int y;
switch("""".Length)
{
case 0:
void L1()
{
y = 0;
L2();
}
break;
case 1:
L1();
y = x;
break;
case 2:
void L2()
{
x = y;
}
break;
}
}
}");
comp.VerifyDiagnostics();
}
}
}
......@@ -104,33 +104,50 @@ public void CheckUnion()
{
var r = new Random(seed);
for (int capacity = 0; capacity < maxBits; capacity++)
CheckUnionCore(capacity, r);
{
CheckUnionCore(capacity, capacity, r);
}
for (int i = 0; i < rounds; i++)
{
CheckUnionCore(r.Next(maxBits), r);
CheckUnionCore(r.Next(maxBits), r.Next(maxBits), r);
}
}
private void CheckUnionCore(int capacity, Random r)
private void CheckUnionCore(int capacity1, int capacity2, Random r)
{
BitVector b1 = BitVector.Empty, b2 = BitVector.Empty;
b1.EnsureCapacity(capacity);
b2.EnsureCapacity(capacity);
bool[] a1 = new bool[capacity], a2 = new bool[capacity];
for (int i = 0; i < capacity; i++)
b1.EnsureCapacity(capacity1);
b2.EnsureCapacity(capacity2);
var maxCapacity = Math.Max(capacity1, capacity2);
bool[] a1 = new bool[maxCapacity],
a2 = new bool[maxCapacity];
for (int i = 0; i < capacity1; i++)
{
b1[i] = a1[i] = r.NextBool();
}
for (int i = 0; i < capacity2; i++)
{
b2[i] = a2[i] = r.NextBool();
}
b1.UnionWith(b2);
for (int i = 0; i < capacity; i++)
bool changed = b1.UnionWith(b2);
bool changed2 = false;
for (int i = 0; i < maxCapacity; i++)
{
bool a = a1[i];
a1[i] |= a2[i];
changed2 |= (a != a1[i]);
}
for (int i = 0; i < capacity; i++)
for (int i = 0; i < maxCapacity; i++)
{
Assert.Equal(a1[i], b1[i]);
}
Assert.Equal(changed2, changed);
}
[Fact]
......
......@@ -283,18 +283,34 @@ public bool IntersectWith(BitVector other)
/// <summary>
/// Modify this bit vector by '|'ing each element with the other bit vector.
/// </summary>
/// <param name="other"></param>
public void UnionWith(BitVector other)
/// <returns>
/// True if any bits were set as a result of the union.
/// </returns>
public bool UnionWith(BitVector other)
{
int l = other._bits.Length;
if (l > _bits.Length)
Array.Resize(ref _bits, l + 1);
_bits0 |= other._bits0;
for (int i = 0; i < l; i++)
_bits[i] |= other._bits[i];
bool anyChanged = false;
if (other._capacity > _capacity)
EnsureCapacity(other._capacity);
Word oldbits = _bits0;
_bits0 |= other._bits0;
if (oldbits != _bits0)
anyChanged = true;
for (int i = 0; i < other._bits.Length; i++)
{
oldbits = _bits[i];
_bits[i] |= other._bits[i];
if (_bits[i] != oldbits)
anyChanged = true;
}
Check();
return anyChanged;
}
public bool this[int index]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册