//
// statement.cs: Statement representation for the IL tree.
//
// Authors:
// Miguel de Icaza (miguel@ximian.com)
// Martin Baulig (martin@ximian.com)
// Marek Safar (marek.safar@gmail.com)
//
// Copyright 2001, 2002, 2003 Ximian, Inc.
// Copyright 2003, 2004 Novell, Inc.
// Copyright 2011 Xamarin Inc.
//
using System;
using System.Collections.Generic;
#if STATIC
using IKVM.Reflection.Emit;
#else
using System.Reflection.Emit;
#endif
namespace Mono.CSharp {
public abstract class Statement {
public Location loc;
protected bool reachable;
public bool IsUnreachable {
get {
return !reachable;
}
}
///
/// Resolves the statement, true means that all sub-statements
/// did resolve ok.
///
public virtual bool Resolve (BlockContext bc)
{
return true;
}
///
/// Return value indicates whether all code paths emitted return.
///
protected abstract void DoEmit (EmitContext ec);
public virtual void Emit (EmitContext ec)
{
ec.Mark (loc);
DoEmit (ec);
if (ec.StatementEpilogue != null) {
ec.EmitEpilogue ();
}
}
//
// This routine must be overrided in derived classes and make copies
// of all the data that might be modified if resolved
//
protected abstract void CloneTo (CloneContext clonectx, Statement target);
public Statement Clone (CloneContext clonectx)
{
Statement s = (Statement) this.MemberwiseClone ();
CloneTo (clonectx, s);
return s;
}
public virtual Expression CreateExpressionTree (ResolveContext ec)
{
ec.Report.Error (834, loc, "A lambda expression with statement body cannot be converted to an expresion tree");
return null;
}
public virtual object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
//
// Return value indicates whether statement has unreachable end
//
protected abstract bool DoFlowAnalysis (FlowAnalysisContext fc);
public bool FlowAnalysis (FlowAnalysisContext fc)
{
if (reachable) {
fc.UnreachableReported = false;
var res = DoFlowAnalysis (fc);
return res;
}
//
// Special handling cases
//
if (this is Block) {
return DoFlowAnalysis (fc);
}
if (this is EmptyStatement || loc.IsNull)
return true;
if (fc.UnreachableReported)
return true;
fc.Report.Warning (162, 2, loc, "Unreachable code detected");
fc.UnreachableReported = true;
return true;
}
public virtual Reachability MarkReachable (Reachability rc)
{
if (!rc.IsUnreachable)
reachable = true;
return rc;
}
protected void CheckExitBoundaries (BlockContext bc, Block scope)
{
if (bc.CurrentBlock.ParametersBlock.Original != scope.ParametersBlock.Original) {
bc.Report.Error (1632, loc, "Control cannot leave the body of an anonymous method");
return;
}
for (var b = bc.CurrentBlock; b != null && b != scope; b = b.Parent) {
if (b.IsFinallyBlock) {
Error_FinallyClauseExit (bc);
break;
}
}
}
protected void Error_FinallyClauseExit (BlockContext bc)
{
bc.Report.Error (157, loc, "Control cannot leave the body of a finally clause");
}
}
public sealed class EmptyStatement : Statement
{
public EmptyStatement (Location loc)
{
this.loc = loc;
}
public override bool Resolve (BlockContext ec)
{
return true;
}
public override void Emit (EmitContext ec)
{
}
protected override void DoEmit (EmitContext ec)
{
throw new NotSupportedException ();
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
return false;
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
// nothing needed.
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public class If : Statement {
Expression expr;
public Statement TrueStatement;
public Statement FalseStatement;
bool true_returns, false_returns;
public If (Expression bool_expr, Statement true_statement, Location l)
: this (bool_expr, true_statement, null, l)
{
}
public If (Expression bool_expr,
Statement true_statement,
Statement false_statement,
Location l)
{
this.expr = bool_expr;
TrueStatement = true_statement;
FalseStatement = false_statement;
loc = l;
}
public Expression Expr {
get {
return this.expr;
}
}
public override bool Resolve (BlockContext ec)
{
expr = expr.Resolve (ec);
var ok = TrueStatement.Resolve (ec);
if (FalseStatement != null) {
ok &= FalseStatement.Resolve (ec);
}
return ok;
}
protected override void DoEmit (EmitContext ec)
{
Label false_target = ec.DefineLabel ();
Label end;
//
// If we're a boolean constant, Resolve() already
// eliminated dead code for us.
//
Constant c = expr as Constant;
if (c != null){
c.EmitSideEffect (ec);
if (!c.IsDefaultValue)
TrueStatement.Emit (ec);
else if (FalseStatement != null)
FalseStatement.Emit (ec);
return;
}
expr.EmitBranchable (ec, false_target, false);
TrueStatement.Emit (ec);
if (FalseStatement != null){
bool branch_emitted = false;
end = ec.DefineLabel ();
if (!true_returns){
ec.Emit (OpCodes.Br, end);
branch_emitted = true;
}
ec.MarkLabel (false_target);
FalseStatement.Emit (ec);
if (branch_emitted)
ec.MarkLabel (end);
} else {
ec.MarkLabel (false_target);
}
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
expr.FlowAnalysisConditional (fc);
var da_false = new DefiniteAssignmentBitSet (fc.DefiniteAssignmentOnFalse);
fc.DefiniteAssignment = fc.DefiniteAssignmentOnTrue;
var res = TrueStatement.FlowAnalysis (fc);
if (FalseStatement == null) {
var c = expr as Constant;
if (c != null && !c.IsDefaultValue)
return true_returns;
if (true_returns)
fc.DefiniteAssignment = da_false;
else
fc.DefiniteAssignment &= da_false;
return false;
}
if (true_returns) {
fc.DefiniteAssignment = da_false;
return FalseStatement.FlowAnalysis (fc);
}
var da_true = fc.DefiniteAssignment;
fc.DefiniteAssignment = da_false;
res &= FalseStatement.FlowAnalysis (fc);
if (!TrueStatement.IsUnreachable) {
if (false_returns || FalseStatement.IsUnreachable)
fc.DefiniteAssignment = da_true;
else
fc.DefiniteAssignment &= da_true;
}
return res;
}
public override Reachability MarkReachable (Reachability rc)
{
if (rc.IsUnreachable)
return rc;
base.MarkReachable (rc);
var c = expr as Constant;
if (c != null) {
bool take = !c.IsDefaultValue;
if (take) {
rc = TrueStatement.MarkReachable (rc);
} else {
if (FalseStatement != null)
rc = FalseStatement.MarkReachable (rc);
}
return rc;
}
var true_rc = TrueStatement.MarkReachable (rc);
true_returns = true_rc.IsUnreachable;
if (FalseStatement == null)
return rc;
var false_rc = FalseStatement.MarkReachable (rc);
false_returns = false_rc.IsUnreachable;
return true_rc & false_rc;
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
If target = (If) t;
target.expr = expr.Clone (clonectx);
target.TrueStatement = TrueStatement.Clone (clonectx);
if (FalseStatement != null)
target.FalseStatement = FalseStatement.Clone (clonectx);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public class Do : LoopStatement
{
public Expression expr;
bool iterator_reachable, end_reachable;
public Do (Statement statement, BooleanExpression bool_expr, Location doLocation, Location whileLocation)
: base (statement)
{
expr = bool_expr;
loc = doLocation;
WhileLocation = whileLocation;
}
public Location WhileLocation {
get; private set;
}
public override bool Resolve (BlockContext bc)
{
var ok = base.Resolve (bc);
expr = expr.Resolve (bc);
return ok;
}
protected override void DoEmit (EmitContext ec)
{
Label loop = ec.DefineLabel ();
Label old_begin = ec.LoopBegin;
Label old_end = ec.LoopEnd;
ec.LoopBegin = ec.DefineLabel ();
ec.LoopEnd = ec.DefineLabel ();
ec.MarkLabel (loop);
Statement.Emit (ec);
ec.MarkLabel (ec.LoopBegin);
// Mark start of while condition
ec.Mark (WhileLocation);
//
// Dead code elimination
//
if (expr is Constant) {
bool res = !((Constant) expr).IsDefaultValue;
expr.EmitSideEffect (ec);
if (res)
ec.Emit (OpCodes.Br, loop);
} else {
expr.EmitBranchable (ec, loop, true);
}
ec.MarkLabel (ec.LoopEnd);
ec.LoopBegin = old_begin;
ec.LoopEnd = old_end;
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
var res = Statement.FlowAnalysis (fc);
expr.FlowAnalysisConditional (fc);
fc.DefiniteAssignment = fc.DefiniteAssignmentOnFalse;
if (res && !iterator_reachable)
return !end_reachable;
if (!end_reachable) {
var c = expr as Constant;
if (c != null && !c.IsDefaultValue)
return true;
}
return false;
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
var body_rc = Statement.MarkReachable (rc);
if (body_rc.IsUnreachable && !iterator_reachable) {
expr = new UnreachableExpression (expr);
return end_reachable ? rc : Reachability.CreateUnreachable ();
}
if (!end_reachable) {
var c = expr as Constant;
if (c != null && !c.IsDefaultValue)
return Reachability.CreateUnreachable ();
}
return rc;
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Do target = (Do) t;
target.Statement = Statement.Clone (clonectx);
target.expr = expr.Clone (clonectx);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
public override void SetEndReachable ()
{
end_reachable = true;
}
public override void SetIteratorReachable ()
{
iterator_reachable = true;
}
}
public class While : LoopStatement
{
public Expression expr;
bool empty, infinite, end_reachable;
List end_reachable_das;
public While (BooleanExpression bool_expr, Statement statement, Location l)
: base (statement)
{
this.expr = bool_expr;
loc = l;
}
public override bool Resolve (BlockContext bc)
{
bool ok = true;
expr = expr.Resolve (bc);
if (expr == null)
ok = false;
var c = expr as Constant;
if (c != null) {
empty = c.IsDefaultValue;
infinite = !empty;
}
ok &= base.Resolve (bc);
return ok;
}
protected override void DoEmit (EmitContext ec)
{
if (empty) {
expr.EmitSideEffect (ec);
return;
}
Label old_begin = ec.LoopBegin;
Label old_end = ec.LoopEnd;
ec.LoopBegin = ec.DefineLabel ();
ec.LoopEnd = ec.DefineLabel ();
//
// Inform whether we are infinite or not
//
if (expr is Constant) {
// expr is 'true', since the 'empty' case above handles the 'false' case
ec.MarkLabel (ec.LoopBegin);
if (ec.EmitAccurateDebugInfo)
ec.Emit (OpCodes.Nop);
expr.EmitSideEffect (ec);
Statement.Emit (ec);
ec.Emit (OpCodes.Br, ec.LoopBegin);
//
// Inform that we are infinite (ie, `we return'), only
// if we do not `break' inside the code.
//
ec.MarkLabel (ec.LoopEnd);
} else {
Label while_loop = ec.DefineLabel ();
ec.Emit (OpCodes.Br, ec.LoopBegin);
ec.MarkLabel (while_loop);
Statement.Emit (ec);
ec.MarkLabel (ec.LoopBegin);
ec.Mark (loc);
expr.EmitBranchable (ec, while_loop, true);
ec.MarkLabel (ec.LoopEnd);
}
ec.LoopBegin = old_begin;
ec.LoopEnd = old_end;
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
expr.FlowAnalysisConditional (fc);
fc.DefiniteAssignment = fc.DefiniteAssignmentOnTrue;
var da_false = new DefiniteAssignmentBitSet (fc.DefiniteAssignmentOnFalse);
Statement.FlowAnalysis (fc);
//
// Special case infinite while with breaks
//
if (end_reachable_das != null) {
da_false = DefiniteAssignmentBitSet.And (end_reachable_das);
end_reachable_das = null;
}
fc.DefiniteAssignment = da_false;
if (infinite && !end_reachable)
return true;
return false;
}
public override Reachability MarkReachable (Reachability rc)
{
if (rc.IsUnreachable)
return rc;
base.MarkReachable (rc);
//
// Special case unreachable while body
//
if (empty) {
Statement.MarkReachable (Reachability.CreateUnreachable ());
return rc;
}
Statement.MarkReachable (rc);
//
// When infinite while end is unreachable via break anything what follows is unreachable too
//
if (infinite && !end_reachable)
return Reachability.CreateUnreachable ();
return rc;
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
While target = (While) t;
target.expr = expr.Clone (clonectx);
target.Statement = Statement.Clone (clonectx);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
public override void AddEndDefiniteAssignment (FlowAnalysisContext fc)
{
if (!infinite)
return;
if (end_reachable_das == null)
end_reachable_das = new List ();
end_reachable_das.Add (fc.DefiniteAssignment);
}
public override void SetEndReachable ()
{
end_reachable = true;
}
}
public class For : LoopStatement
{
bool infinite, empty, iterator_reachable, end_reachable;
List end_reachable_das;
public For (Location l)
: base (null)
{
loc = l;
}
public Statement Initializer {
get; set;
}
public Expression Condition {
get; set;
}
public Statement Iterator {
get; set;
}
public override bool Resolve (BlockContext bc)
{
Initializer.Resolve (bc);
if (Condition != null) {
Condition = Condition.Resolve (bc);
var condition_constant = Condition as Constant;
if (condition_constant != null) {
if (condition_constant.IsDefaultValue) {
empty = true;
} else {
infinite = true;
}
}
} else {
infinite = true;
}
return base.Resolve (bc) && Iterator.Resolve (bc);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
Initializer.FlowAnalysis (fc);
DefiniteAssignmentBitSet da_false;
if (Condition != null) {
Condition.FlowAnalysisConditional (fc);
fc.DefiniteAssignment = fc.DefiniteAssignmentOnTrue;
da_false = new DefiniteAssignmentBitSet (fc.DefiniteAssignmentOnFalse);
} else {
da_false = fc.BranchDefiniteAssignment ();
}
Statement.FlowAnalysis (fc);
Iterator.FlowAnalysis (fc);
//
// Special case infinite for with breaks
//
if (end_reachable_das != null) {
da_false = DefiniteAssignmentBitSet.And (end_reachable_das);
end_reachable_das = null;
}
fc.DefiniteAssignment = da_false;
if (infinite && !end_reachable)
return true;
return false;
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
Initializer.MarkReachable (rc);
var body_rc = Statement.MarkReachable (rc);
if (!body_rc.IsUnreachable || iterator_reachable) {
Iterator.MarkReachable (rc);
}
//
// When infinite for end is unreachable via break anything what follows is unreachable too
//
if (infinite && !end_reachable) {
return Reachability.CreateUnreachable ();
}
return rc;
}
protected override void DoEmit (EmitContext ec)
{
if (Initializer != null)
Initializer.Emit (ec);
if (empty) {
Condition.EmitSideEffect (ec);
return;
}
Label old_begin = ec.LoopBegin;
Label old_end = ec.LoopEnd;
Label loop = ec.DefineLabel ();
Label test = ec.DefineLabel ();
ec.LoopBegin = ec.DefineLabel ();
ec.LoopEnd = ec.DefineLabel ();
ec.Emit (OpCodes.Br, test);
ec.MarkLabel (loop);
Statement.Emit (ec);
ec.MarkLabel (ec.LoopBegin);
Iterator.Emit (ec);
ec.MarkLabel (test);
//
// If test is null, there is no test, and we are just
// an infinite loop
//
if (Condition != null) {
ec.Mark (Condition.Location);
//
// The Resolve code already catches the case for
// Test == Constant (false) so we know that
// this is true
//
if (Condition is Constant) {
Condition.EmitSideEffect (ec);
ec.Emit (OpCodes.Br, loop);
} else {
Condition.EmitBranchable (ec, loop, true);
}
} else
ec.Emit (OpCodes.Br, loop);
ec.MarkLabel (ec.LoopEnd);
ec.LoopBegin = old_begin;
ec.LoopEnd = old_end;
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
For target = (For) t;
if (Initializer != null)
target.Initializer = Initializer.Clone (clonectx);
if (Condition != null)
target.Condition = Condition.Clone (clonectx);
if (Iterator != null)
target.Iterator = Iterator.Clone (clonectx);
target.Statement = Statement.Clone (clonectx);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
public override void AddEndDefiniteAssignment (FlowAnalysisContext fc)
{
if (!infinite)
return;
if (end_reachable_das == null)
end_reachable_das = new List ();
end_reachable_das.Add (fc.DefiniteAssignment);
}
public override void SetEndReachable ()
{
end_reachable = true;
}
public override void SetIteratorReachable ()
{
iterator_reachable = true;
}
}
public abstract class LoopStatement : Statement
{
protected LoopStatement (Statement statement)
{
Statement = statement;
}
public Statement Statement { get; set; }
public override bool Resolve (BlockContext bc)
{
var prev_loop = bc.EnclosingLoop;
var prev_los = bc.EnclosingLoopOrSwitch;
bc.EnclosingLoopOrSwitch = bc.EnclosingLoop = this;
var ok = Statement.Resolve (bc);
bc.EnclosingLoopOrSwitch = prev_los;
bc.EnclosingLoop = prev_loop;
return ok;
}
//
// Needed by possibly infinite loops statements (for, while) and switch statment
//
public virtual void AddEndDefiniteAssignment (FlowAnalysisContext fc)
{
}
public virtual void SetEndReachable ()
{
}
public virtual void SetIteratorReachable ()
{
}
}
public class StatementExpression : Statement
{
ExpressionStatement expr;
public StatementExpression (ExpressionStatement expr)
{
this.expr = expr;
loc = expr.StartLocation;
}
public StatementExpression (ExpressionStatement expr, Location loc)
{
this.expr = expr;
this.loc = loc;
}
public ExpressionStatement Expr {
get {
return this.expr;
}
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
StatementExpression target = (StatementExpression) t;
target.expr = (ExpressionStatement) expr.Clone (clonectx);
}
protected override void DoEmit (EmitContext ec)
{
expr.EmitStatement (ec);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
expr.FlowAnalysis (fc);
return false;
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
expr.MarkReachable (rc);
return rc;
}
public override bool Resolve (BlockContext ec)
{
expr = expr.ResolveStatement (ec);
return expr != null;
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public class StatementErrorExpression : Statement
{
Expression expr;
public StatementErrorExpression (Expression expr)
{
this.expr = expr;
this.loc = expr.StartLocation;
}
public Expression Expr {
get {
return expr;
}
}
public override bool Resolve (BlockContext bc)
{
expr.Error_InvalidExpressionStatement (bc);
return true;
}
protected override void DoEmit (EmitContext ec)
{
throw new NotSupportedException ();
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
return false;
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
var t = (StatementErrorExpression) target;
t.expr = expr.Clone (clonectx);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
//
// Simple version of statement list not requiring a block
//
public class StatementList : Statement
{
List statements;
public StatementList (Statement first, Statement second)
{
statements = new List { first, second };
}
#region Properties
public IList Statements {
get {
return statements;
}
}
#endregion
public void Add (Statement statement)
{
statements.Add (statement);
}
public override bool Resolve (BlockContext ec)
{
foreach (var s in statements)
s.Resolve (ec);
return true;
}
protected override void DoEmit (EmitContext ec)
{
foreach (var s in statements)
s.Emit (ec);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
foreach (var s in statements)
s.FlowAnalysis (fc);
return false;
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
Reachability res = rc;
foreach (var s in statements)
res = s.MarkReachable (rc);
return res;
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
StatementList t = (StatementList) target;
t.statements = new List (statements.Count);
foreach (Statement s in statements)
t.statements.Add (s.Clone (clonectx));
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
//
// For statements which require special handling when inside try or catch block
//
public abstract class ExitStatement : Statement
{
protected bool unwind_protect;
protected abstract bool DoResolve (BlockContext bc);
protected abstract bool IsLocalExit { get; }
public override bool Resolve (BlockContext bc)
{
var res = DoResolve (bc);
if (!IsLocalExit) {
//
// We are inside finally scope but is it the scope we are exiting
//
if (bc.HasSet (ResolveContext.Options.FinallyScope)) {
for (var b = bc.CurrentBlock; b != null; b = b.Parent) {
if (b.IsFinallyBlock) {
Error_FinallyClauseExit (bc);
break;
}
if (b is ParametersBlock)
break;
}
}
}
unwind_protect = bc.HasAny (ResolveContext.Options.TryScope | ResolveContext.Options.CatchScope);
return res;
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
if (IsLocalExit)
return true;
if (fc.TryFinally != null) {
fc.TryFinally.RegisterForControlExitCheck (new DefiniteAssignmentBitSet (fc.DefiniteAssignment));
} else {
fc.ParametersBlock.CheckControlExit (fc);
}
return true;
}
}
///
/// Implements the return statement
///
public class Return : ExitStatement
{
Expression expr;
public Return (Expression expr, Location l)
{
this.expr = expr;
loc = l;
}
#region Properties
public Expression Expr {
get {
return expr;
}
protected set {
expr = value;
}
}
protected override bool IsLocalExit {
get {
return false;
}
}
#endregion
protected override bool DoResolve (BlockContext ec)
{
var block_return_type = ec.ReturnType;
if (expr == null) {
if (block_return_type.Kind == MemberKind.Void || block_return_type == InternalType.ErrorType)
return true;
//
// Return must not be followed by an expression when
// the method return type is Task
//
if (ec.CurrentAnonymousMethod is AsyncInitializer) {
var storey = (AsyncTaskStorey) ec.CurrentAnonymousMethod.Storey;
if (storey.ReturnType == ec.Module.PredefinedTypes.Task.TypeSpec) {
//
// Extra trick not to emit ret/leave inside awaiter body
//
expr = EmptyExpression.Null;
return true;
}
if (storey.ReturnType.IsGenericTask)
block_return_type = storey.ReturnType.TypeArguments[0];
}
if (ec.CurrentIterator != null) {
Error_ReturnFromIterator (ec);
} else if (block_return_type != InternalType.ErrorType) {
ec.Report.Error (126, loc,
"An object of a type convertible to `{0}' is required for the return statement",
block_return_type.GetSignatureForError ());
}
return false;
}
expr = expr.Resolve (ec);
AnonymousExpression am = ec.CurrentAnonymousMethod;
if (am == null) {
if (block_return_type.Kind == MemberKind.Void) {
ec.Report.Error (127, loc,
"`{0}': A return keyword must not be followed by any expression when method returns void",
ec.GetSignatureForError ());
return false;
}
} else {
if (am.IsIterator) {
Error_ReturnFromIterator (ec);
return false;
}
var async_block = am as AsyncInitializer;
if (async_block != null) {
if (expr != null) {
var storey = (AsyncTaskStorey) am.Storey;
var async_type = storey.ReturnType;
if (async_type == null && async_block.ReturnTypeInference != null) {
if (expr.Type.Kind == MemberKind.Void && !(this is ContextualReturn))
ec.Report.Error (4029, loc, "Cannot return an expression of type `void'");
else
async_block.ReturnTypeInference.AddCommonTypeBoundAsync (expr.Type);
return true;
}
if (async_type.Kind == MemberKind.Void) {
ec.Report.Error (8030, loc,
"Anonymous function or lambda expression converted to a void returning delegate cannot return a value");
return false;
}
if (!async_type.IsGenericTask) {
if (this is ContextualReturn)
return true;
if (async_block.DelegateType != null) {
ec.Report.Error (8031, loc,
"Async lambda expression or anonymous method converted to a `Task' cannot return a value. Consider returning `Task'");
} else {
ec.Report.Error (1997, loc,
"`{0}': A return keyword must not be followed by an expression when async method returns `Task'. Consider using `Task' return type",
ec.GetSignatureForError ());
}
return false;
}
//
// The return type is actually Task type argument
//
if (expr.Type == async_type && async_type.TypeArguments [0] != ec.Module.PredefinedTypes.Task.TypeSpec) {
ec.Report.Error (4016, loc,
"`{0}': The return expression type of async method must be `{1}' rather than `Task<{1}>'",
ec.GetSignatureForError (), async_type.TypeArguments[0].GetSignatureForError ());
} else {
block_return_type = async_type.TypeArguments[0];
}
}
} else {
if (block_return_type.Kind == MemberKind.Void) {
ec.Report.Error (8030, loc,
"Anonymous function or lambda expression converted to a void returning delegate cannot return a value");
return false;
}
var l = am as AnonymousMethodBody;
if (l != null && expr != null) {
if (l.ReturnTypeInference != null) {
l.ReturnTypeInference.AddCommonTypeBound (expr.Type);
return true;
}
//
// Try to optimize simple lambda. Only when optimizations are enabled not to cause
// unexpected debugging experience
//
if (this is ContextualReturn && !ec.IsInProbingMode && ec.Module.Compiler.Settings.Optimize) {
l.DirectMethodGroupConversion = expr.CanReduceLambda (l);
}
}
}
}
if (expr == null)
return false;
if (expr.Type != block_return_type && expr.Type != InternalType.ErrorType) {
expr = Convert.ImplicitConversionRequired (ec, expr, block_return_type, loc);
if (expr == null) {
if (am != null && block_return_type == ec.ReturnType) {
ec.Report.Error (1662, loc,
"Cannot convert `{0}' to delegate type `{1}' because some of the return types in the block are not implicitly convertible to the delegate return type",
am.ContainerType, am.GetSignatureForError ());
}
return false;
}
}
return true;
}
protected override void DoEmit (EmitContext ec)
{
if (expr != null) {
var async_body = ec.CurrentAnonymousMethod as AsyncInitializer;
if (async_body != null) {
var storey = (AsyncTaskStorey)async_body.Storey;
Label exit_label = async_body.BodyEnd;
//
// It's null for await without async
//
if (storey.HoistedReturnValue != null) {
//
// Special case hoisted return value (happens in try/finally scenario)
//
if (ec.TryFinallyUnwind != null) {
exit_label = TryFinally.EmitRedirectedReturn (ec, async_body);
}
var async_return = (IAssignMethod)storey.HoistedReturnValue;
async_return.EmitAssign (ec, expr, false, false);
ec.EmitEpilogue ();
} else {
expr.Emit (ec);
if (ec.TryFinallyUnwind != null)
exit_label = TryFinally.EmitRedirectedReturn (ec, async_body);
}
ec.Emit (OpCodes.Leave, exit_label);
return;
}
expr.Emit (ec);
ec.EmitEpilogue ();
if (unwind_protect || ec.EmitAccurateDebugInfo)
ec.Emit (OpCodes.Stloc, ec.TemporaryReturn ());
}
if (unwind_protect) {
ec.Emit (OpCodes.Leave, ec.CreateReturnLabel ());
} else if (ec.EmitAccurateDebugInfo) {
ec.Emit (OpCodes.Br, ec.CreateReturnLabel ());
} else {
ec.Emit (OpCodes.Ret);
}
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
if (expr != null)
expr.FlowAnalysis (fc);
base.DoFlowAnalysis (fc);
return true;
}
void Error_ReturnFromIterator (ResolveContext rc)
{
rc.Report.Error (1622, loc,
"Cannot return a value from iterators. Use the yield return statement to return a value, or yield break to end the iteration");
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
return Reachability.CreateUnreachable ();
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Return target = (Return) t;
// It's null for simple return;
if (expr != null)
target.expr = expr.Clone (clonectx);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public class Goto : ExitStatement
{
string target;
LabeledStatement label;
TryFinally try_finally;
public Goto (string label, Location l)
{
loc = l;
target = label;
}
public string Target {
get { return target; }
}
protected override bool IsLocalExit {
get {
return true;
}
}
protected override bool DoResolve (BlockContext bc)
{
label = bc.CurrentBlock.LookupLabel (target);
if (label == null) {
Error_UnknownLabel (bc, target, loc);
return false;
}
try_finally = bc.CurrentTryBlock as TryFinally;
CheckExitBoundaries (bc, label.Block);
return true;
}
public static void Error_UnknownLabel (BlockContext bc, string label, Location loc)
{
bc.Report.Error (159, loc, "The label `{0}:' could not be found within the scope of the goto statement",
label);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
// Goto to unreachable label
if (label == null)
return true;
if (fc.AddReachedLabel (label))
return true;
label.Block.ScanGotoJump (label, fc);
return true;
}
public override Reachability MarkReachable (Reachability rc)
{
if (rc.IsUnreachable)
return rc;
base.MarkReachable (rc);
if (try_finally != null) {
if (try_finally.FinallyBlock.HasReachableClosingBrace) {
label.AddGotoReference (rc);
} else {
label = null;
}
} else {
label.AddGotoReference (rc);
}
return Reachability.CreateUnreachable ();
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
// Nothing to clone
}
protected override void DoEmit (EmitContext ec)
{
// This should only happen for goto from try block to unrechable label
if (label == null)
return;
Label l = label.LabelTarget (ec);
if (ec.TryFinallyUnwind != null && IsLeavingFinally (label.Block)) {
var async_body = (AsyncInitializer) ec.CurrentAnonymousMethod;
l = TryFinally.EmitRedirectedJump (ec, async_body, l, label.Block);
}
ec.Emit (unwind_protect ? OpCodes.Leave : OpCodes.Br, l);
}
bool IsLeavingFinally (Block labelBlock)
{
var b = try_finally.Statement as Block;
while (b != null) {
if (b == labelBlock)
return true;
b = b.Parent;
}
return false;
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public class LabeledStatement : Statement {
string name;
bool defined;
bool referenced;
Label label;
Block block;
public LabeledStatement (string name, Block block, Location l)
{
this.name = name;
this.block = block;
this.loc = l;
}
public Label LabelTarget (EmitContext ec)
{
if (defined)
return label;
label = ec.DefineLabel ();
defined = true;
return label;
}
public Block Block {
get {
return block;
}
}
public string Name {
get { return name; }
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
var t = (LabeledStatement) target;
t.block = clonectx.RemapBlockCopy (block);
}
public override bool Resolve (BlockContext bc)
{
return true;
}
protected override void DoEmit (EmitContext ec)
{
LabelTarget (ec);
ec.MarkLabel (label);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
if (!referenced) {
fc.Report.Warning (164, 2, loc, "This label has not been referenced");
}
return false;
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
if (referenced)
rc = new Reachability ();
return rc;
}
public void AddGotoReference (Reachability rc)
{
if (referenced)
return;
referenced = true;
MarkReachable (rc);
block.ScanGotoJump (this);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
///
/// `goto default' statement
///
public class GotoDefault : SwitchGoto
{
public GotoDefault (Location l)
: base (l)
{
}
public override bool Resolve (BlockContext bc)
{
if (bc.Switch == null) {
Error_GotoCaseRequiresSwitchBlock (bc);
return false;
}
bc.Switch.RegisterGotoCase (null, null);
base.Resolve (bc);
return true;
}
protected override void DoEmit (EmitContext ec)
{
ec.Emit (unwind_protect ? OpCodes.Leave : OpCodes.Br, ec.Switch.DefaultLabel.GetILLabel (ec));
}
public override Reachability MarkReachable (Reachability rc)
{
if (!rc.IsUnreachable) {
var label = switch_statement.DefaultLabel;
if (label.IsUnreachable) {
label.MarkReachable (rc);
switch_statement.Block.ScanGotoJump (label);
}
}
return base.MarkReachable (rc);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
///
/// `goto case' statement
///
public class GotoCase : SwitchGoto
{
Expression expr;
public GotoCase (Expression e, Location l)
: base (l)
{
expr = e;
}
public Expression Expr {
get {
return expr;
}
}
public SwitchLabel Label { get; set; }
public override bool Resolve (BlockContext ec)
{
if (ec.Switch == null) {
Error_GotoCaseRequiresSwitchBlock (ec);
return false;
}
Constant c = expr.ResolveLabelConstant (ec);
if (c == null) {
return false;
}
Constant res;
if (ec.Switch.IsNullable && c is NullLiteral) {
res = c;
} else {
TypeSpec type = ec.Switch.SwitchType;
res = c.Reduce (ec, type);
if (res == null) {
c.Error_ValueCannotBeConverted (ec, type, true);
return false;
}
if (!Convert.ImplicitStandardConversionExists (c, type))
ec.Report.Warning (469, 2, loc,
"The `goto case' value is not implicitly convertible to type `{0}'",
type.GetSignatureForError ());
}
ec.Switch.RegisterGotoCase (this, res);
base.Resolve (ec);
expr = res;
return true;
}
protected override void DoEmit (EmitContext ec)
{
ec.Emit (unwind_protect ? OpCodes.Leave : OpCodes.Br, Label.GetILLabel (ec));
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
GotoCase target = (GotoCase) t;
target.expr = expr.Clone (clonectx);
}
public override Reachability MarkReachable (Reachability rc)
{
if (!rc.IsUnreachable) {
var label = switch_statement.FindLabel ((Constant) expr);
if (label.IsUnreachable) {
label.MarkReachable (rc);
switch_statement.Block.ScanGotoJump (label);
}
}
return base.MarkReachable (rc);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public abstract class SwitchGoto : Statement
{
protected bool unwind_protect;
protected Switch switch_statement;
protected SwitchGoto (Location loc)
{
this.loc = loc;
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
// Nothing to clone
}
public override bool Resolve (BlockContext bc)
{
CheckExitBoundaries (bc, bc.Switch.Block);
unwind_protect = bc.HasAny (ResolveContext.Options.TryScope | ResolveContext.Options.CatchScope);
switch_statement = bc.Switch;
return true;
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
return true;
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
return Reachability.CreateUnreachable ();
}
protected void Error_GotoCaseRequiresSwitchBlock (BlockContext bc)
{
bc.Report.Error (153, loc, "A goto case is only valid inside a switch statement");
}
}
public class Throw : Statement {
Expression expr;
public Throw (Expression expr, Location l)
{
this.expr = expr;
loc = l;
}
public Expression Expr {
get {
return this.expr;
}
}
public override bool Resolve (BlockContext ec)
{
if (expr == null) {
if (!ec.HasSet (ResolveContext.Options.CatchScope)) {
ec.Report.Error (156, loc, "A throw statement with no arguments is not allowed outside of a catch clause");
} else if (ec.HasSet (ResolveContext.Options.FinallyScope)) {
for (var b = ec.CurrentBlock; b != null && !b.IsCatchBlock; b = b.Parent) {
if (b.IsFinallyBlock) {
ec.Report.Error (724, loc,
"A throw statement with no arguments is not allowed inside of a finally clause nested inside of the innermost catch clause");
break;
}
}
}
return true;
}
expr = expr.Resolve (ec, ResolveFlags.Type | ResolveFlags.VariableOrValue);
if (expr == null)
return false;
var et = ec.BuiltinTypes.Exception;
if (Convert.ImplicitConversionExists (ec, expr, et))
expr = Convert.ImplicitConversion (ec, expr, et, loc);
else
ec.Report.Error (155, expr.Location, "The type caught or thrown must be derived from System.Exception");
return true;
}
protected override void DoEmit (EmitContext ec)
{
if (expr == null) {
var atv = ec.AsyncThrowVariable;
if (atv != null) {
if (atv.HoistedVariant != null) {
atv.HoistedVariant.Emit (ec);
} else {
atv.Emit (ec);
}
ec.Emit (OpCodes.Throw);
} else {
ec.Emit (OpCodes.Rethrow);
}
} else {
expr.Emit (ec);
ec.Emit (OpCodes.Throw);
}
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
if (expr != null)
expr.FlowAnalysis (fc);
return true;
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
return Reachability.CreateUnreachable ();
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Throw target = (Throw) t;
if (expr != null)
target.expr = expr.Clone (clonectx);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public class Break : LocalExitStatement
{
public Break (Location l)
: base (l)
{
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
protected override void DoEmit (EmitContext ec)
{
var l = ec.LoopEnd;
if (ec.TryFinallyUnwind != null) {
var async_body = (AsyncInitializer) ec.CurrentAnonymousMethod;
l = TryFinally.EmitRedirectedJump (ec, async_body, l, enclosing_loop.Statement as Block);
}
ec.Emit (unwind_protect ? OpCodes.Leave : OpCodes.Br, l);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
enclosing_loop.AddEndDefiniteAssignment (fc);
return true;
}
protected override bool DoResolve (BlockContext bc)
{
enclosing_loop = bc.EnclosingLoopOrSwitch;
return base.DoResolve (bc);
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
if (!rc.IsUnreachable)
enclosing_loop.SetEndReachable ();
return Reachability.CreateUnreachable ();
}
}
public class Continue : LocalExitStatement
{
public Continue (Location l)
: base (l)
{
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
protected override void DoEmit (EmitContext ec)
{
var l = ec.LoopBegin;
if (ec.TryFinallyUnwind != null) {
var async_body = (AsyncInitializer) ec.CurrentAnonymousMethod;
l = TryFinally.EmitRedirectedJump (ec, async_body, l, enclosing_loop.Statement as Block);
}
ec.Emit (unwind_protect ? OpCodes.Leave : OpCodes.Br, l);
}
protected override bool DoResolve (BlockContext bc)
{
enclosing_loop = bc.EnclosingLoop;
return base.DoResolve (bc);
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
if (!rc.IsUnreachable)
enclosing_loop.SetIteratorReachable ();
return Reachability.CreateUnreachable ();
}
}
public abstract class LocalExitStatement : ExitStatement
{
protected LoopStatement enclosing_loop;
protected LocalExitStatement (Location loc)
{
this.loc = loc;
}
protected override bool IsLocalExit {
get {
return true;
}
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
// nothing needed.
}
protected override bool DoResolve (BlockContext bc)
{
if (enclosing_loop == null) {
bc.Report.Error (139, loc, "No enclosing loop out of which to break or continue");
return false;
}
var block = enclosing_loop.Statement as Block;
// Don't need to do extra checks for simple statements loops
if (block != null) {
CheckExitBoundaries (bc, block);
}
return true;
}
}
public interface ILocalVariable
{
void Emit (EmitContext ec);
void EmitAssign (EmitContext ec);
void EmitAddressOf (EmitContext ec);
}
public interface INamedBlockVariable
{
Block Block { get; }
Expression CreateReferenceExpression (ResolveContext rc, Location loc);
bool IsDeclared { get; }
bool IsParameter { get; }
Location Location { get; }
}
public class BlockVariableDeclarator
{
LocalVariable li;
Expression initializer;
public BlockVariableDeclarator (LocalVariable li, Expression initializer)
{
if (li.Type != null)
throw new ArgumentException ("Expected null variable type");
this.li = li;
this.initializer = initializer;
}
#region Properties
public LocalVariable Variable {
get {
return li;
}
}
public Expression Initializer {
get {
return initializer;
}
set {
initializer = value;
}
}
#endregion
public virtual BlockVariableDeclarator Clone (CloneContext cloneCtx)
{
var t = (BlockVariableDeclarator) MemberwiseClone ();
if (initializer != null)
t.initializer = initializer.Clone (cloneCtx);
return t;
}
}
public class BlockVariable : Statement
{
Expression initializer;
protected FullNamedExpression type_expr;
protected LocalVariable li;
protected List declarators;
TypeSpec type;
public BlockVariable (FullNamedExpression type, LocalVariable li)
{
this.type_expr = type;
this.li = li;
this.loc = type_expr.Location;
}
protected BlockVariable (LocalVariable li)
{
this.li = li;
}
#region Properties
public List Declarators {
get {
return declarators;
}
}
public Expression Initializer {
get {
return initializer;
}
set {
initializer = value;
}
}
public FullNamedExpression TypeExpression {
get {
return type_expr;
}
}
public LocalVariable Variable {
get {
return li;
}
}
#endregion
public void AddDeclarator (BlockVariableDeclarator decl)
{
if (declarators == null)
declarators = new List ();
declarators.Add (decl);
}
static void CreateEvaluatorVariable (BlockContext bc, LocalVariable li)
{
if (bc.Report.Errors != 0)
return;
var container = bc.CurrentMemberDefinition.Parent.PartialContainer;
Field f = new Field (container, new TypeExpression (li.Type, li.Location), Modifiers.PUBLIC | Modifiers.STATIC,
new MemberName (li.Name, li.Location), null);
container.AddField (f);
f.Define ();
li.HoistedVariant = new HoistedEvaluatorVariable (f);
li.SetIsUsed ();
}
public override bool Resolve (BlockContext bc)
{
return Resolve (bc, true);
}
public bool Resolve (BlockContext bc, bool resolveDeclaratorInitializers)
{
if (type == null && !li.IsCompilerGenerated) {
var vexpr = type_expr as VarExpr;
//
// C# 3.0 introduced contextual keywords (var) which behaves like a type if type with
// same name exists or as a keyword when no type was found
//
if (vexpr != null && !vexpr.IsPossibleType (bc)) {
if (bc.Module.Compiler.Settings.Version < LanguageVersion.V_3)
bc.Report.FeatureIsNotAvailable (bc.Module.Compiler, loc, "implicitly typed local variable");
if (li.IsFixed) {
bc.Report.Error (821, loc, "A fixed statement cannot use an implicitly typed local variable");
return false;
}
if (li.IsConstant) {
bc.Report.Error (822, loc, "An implicitly typed local variable cannot be a constant");
return false;
}
if (Initializer == null) {
bc.Report.Error (818, loc, "An implicitly typed local variable declarator must include an initializer");
return false;
}
if (declarators != null) {
bc.Report.Error (819, loc, "An implicitly typed local variable declaration cannot include multiple declarators");
declarators = null;
}
Initializer = Initializer.Resolve (bc);
if (Initializer != null) {
((VarExpr) type_expr).InferType (bc, Initializer);
type = type_expr.Type;
} else {
// Set error type to indicate the var was placed correctly but could
// not be infered
//
// var a = missing ();
//
type = InternalType.ErrorType;
}
}
if (type == null) {
type = type_expr.ResolveAsType (bc);
if (type == null)
return false;
if (li.IsConstant && !type.IsConstantCompatible) {
Const.Error_InvalidConstantType (type, loc, bc.Report);
}
}
if (type.IsStatic)
FieldBase.Error_VariableOfStaticClass (loc, li.Name, type, bc.Report);
li.Type = type;
}
bool eval_global = bc.Module.Compiler.Settings.StatementMode && bc.CurrentBlock is ToplevelBlock;
if (eval_global) {
CreateEvaluatorVariable (bc, li);
} else if (type != InternalType.ErrorType) {
li.PrepareAssignmentAnalysis (bc);
}
if (initializer != null) {
initializer = ResolveInitializer (bc, li, initializer);
// li.Variable.DefinitelyAssigned
}
if (declarators != null) {
foreach (var d in declarators) {
d.Variable.Type = li.Type;
if (eval_global) {
CreateEvaluatorVariable (bc, d.Variable);
} else if (type != InternalType.ErrorType) {
d.Variable.PrepareAssignmentAnalysis (bc);
}
if (d.Initializer != null && resolveDeclaratorInitializers) {
d.Initializer = ResolveInitializer (bc, d.Variable, d.Initializer);
// d.Variable.DefinitelyAssigned
}
}
}
return true;
}
protected virtual Expression ResolveInitializer (BlockContext bc, LocalVariable li, Expression initializer)
{
var a = new SimpleAssign (li.CreateReferenceExpression (bc, li.Location), initializer, li.Location);
return a.ResolveStatement (bc);
}
protected override void DoEmit (EmitContext ec)
{
li.CreateBuilder (ec);
if (Initializer != null && !IsUnreachable)
((ExpressionStatement) Initializer).EmitStatement (ec);
if (declarators != null) {
foreach (var d in declarators) {
d.Variable.CreateBuilder (ec);
if (d.Initializer != null && !IsUnreachable) {
ec.Mark (d.Variable.Location);
((ExpressionStatement) d.Initializer).EmitStatement (ec);
}
}
}
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
if (Initializer != null)
Initializer.FlowAnalysis (fc);
if (declarators != null) {
foreach (var d in declarators) {
if (d.Initializer != null)
d.Initializer.FlowAnalysis (fc);
}
}
return false;
}
public override Reachability MarkReachable (Reachability rc)
{
var init = initializer as ExpressionStatement;
if (init != null)
init.MarkReachable (rc);
return base.MarkReachable (rc);
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
BlockVariable t = (BlockVariable) target;
if (type_expr != null)
t.type_expr = (FullNamedExpression) type_expr.Clone (clonectx);
if (initializer != null)
t.initializer = initializer.Clone (clonectx);
if (declarators != null) {
t.declarators = null;
foreach (var d in declarators)
t.AddDeclarator (d.Clone (clonectx));
}
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public class BlockConstant : BlockVariable
{
public BlockConstant (FullNamedExpression type, LocalVariable li)
: base (type, li)
{
}
public override void Emit (EmitContext ec)
{
// Nothing to emit, not even sequence point
}
protected override Expression ResolveInitializer (BlockContext bc, LocalVariable li, Expression initializer)
{
initializer = initializer.Resolve (bc);
if (initializer == null)
return null;
var c = initializer as Constant;
if (c == null) {
initializer.Error_ExpressionMustBeConstant (bc, initializer.Location, li.Name);
return null;
}
c = c.ConvertImplicitly (li.Type);
if (c == null) {
if (TypeSpec.IsReferenceType (li.Type))
initializer.Error_ConstantCanBeInitializedWithNullOnly (bc, li.Type, initializer.Location, li.Name);
else
initializer.Error_ValueCannotBeConverted (bc, li.Type, false);
return null;
}
li.ConstantValue = c;
return initializer;
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
//
// The information about a user-perceived local variable
//
public sealed class LocalVariable : INamedBlockVariable, ILocalVariable
{
[Flags]
public enum Flags
{
Used = 1,
IsThis = 1 << 1,
AddressTaken = 1 << 2,
CompilerGenerated = 1 << 3,
Constant = 1 << 4,
ForeachVariable = 1 << 5,
FixedVariable = 1 << 6,
UsingVariable = 1 << 7,
IsLocked = 1 << 8,
SymbolFileHidden = 1 << 9,
ReadonlyMask = ForeachVariable | FixedVariable | UsingVariable
}
TypeSpec type;
readonly string name;
readonly Location loc;
readonly Block block;
Flags flags;
Constant const_value;
public VariableInfo VariableInfo;
HoistedVariable hoisted_variant;
LocalBuilder builder;
public LocalVariable (Block block, string name, Location loc)
{
this.block = block;
this.name = name;
this.loc = loc;
}
public LocalVariable (Block block, string name, Flags flags, Location loc)
: this (block, name, loc)
{
this.flags = flags;
}
//
// Used by variable declarators
//
public LocalVariable (LocalVariable li, string name, Location loc)
: this (li.block, name, li.flags, loc)
{
}
#region Properties
public bool AddressTaken {
get {
return (flags & Flags.AddressTaken) != 0;
}
}
public Block Block {
get {
return block;
}
}
public Constant ConstantValue {
get {
return const_value;
}
set {
const_value = value;
}
}
//
// Hoisted local variable variant
//
public HoistedVariable HoistedVariant {
get {
return hoisted_variant;
}
set {
hoisted_variant = value;
}
}
public bool IsDeclared {
get {
return type != null;
}
}
public bool IsCompilerGenerated {
get {
return (flags & Flags.CompilerGenerated) != 0;
}
}
public bool IsConstant {
get {
return (flags & Flags.Constant) != 0;
}
}
public bool IsLocked {
get {
return (flags & Flags.IsLocked) != 0;
}
set {
flags = value ? flags | Flags.IsLocked : flags & ~Flags.IsLocked;
}
}
public bool IsThis {
get {
return (flags & Flags.IsThis) != 0;
}
}
public bool IsFixed {
get {
return (flags & Flags.FixedVariable) != 0;
}
set {
flags = value ? flags | Flags.FixedVariable : flags & ~Flags.FixedVariable;
}
}
bool INamedBlockVariable.IsParameter {
get {
return false;
}
}
public bool IsReadonly {
get {
return (flags & Flags.ReadonlyMask) != 0;
}
}
public Location Location {
get {
return loc;
}
}
public string Name {
get {
return name;
}
}
public TypeSpec Type {
get {
return type;
}
set {
type = value;
}
}
#endregion
public void CreateBuilder (EmitContext ec)
{
if ((flags & Flags.Used) == 0) {
if (VariableInfo == null) {
// Missing flow analysis or wrong variable flags
throw new InternalErrorException ("VariableInfo is null and the variable `{0}' is not used", name);
}
if (VariableInfo.IsEverAssigned)
ec.Report.Warning (219, 3, Location, "The variable `{0}' is assigned but its value is never used", Name);
else
ec.Report.Warning (168, 3, Location, "The variable `{0}' is declared but never used", Name);
}
if (HoistedVariant != null)
return;
if (builder != null) {
if ((flags & Flags.CompilerGenerated) != 0)
return;
// To avoid Used warning duplicates
throw new InternalErrorException ("Already created variable `{0}'", name);
}
//
// All fixed variabled are pinned, a slot has to be alocated
//
builder = ec.DeclareLocal (Type, IsFixed);
if ((flags & Flags.SymbolFileHidden) == 0)
ec.DefineLocalVariable (name, builder);
}
public static LocalVariable CreateCompilerGenerated (TypeSpec type, Block block, Location loc, bool writeToSymbolFile = false)
{
LocalVariable li = new LocalVariable (block, GetCompilerGeneratedName (block), Flags.CompilerGenerated | Flags.Used, loc);
if (!writeToSymbolFile)
li.flags |= Flags.SymbolFileHidden;
li.Type = type;
return li;
}
public Expression CreateReferenceExpression (ResolveContext rc, Location loc)
{
if (IsConstant && const_value != null)
return Constant.CreateConstantFromValue (Type, const_value.GetValue (), loc);
return new LocalVariableReference (this, loc);
}
public void Emit (EmitContext ec)
{
// TODO: Need something better for temporary variables
if ((flags & Flags.CompilerGenerated) != 0)
CreateBuilder (ec);
ec.Emit (OpCodes.Ldloc, builder);
}
public void EmitAssign (EmitContext ec)
{
// TODO: Need something better for temporary variables
if ((flags & Flags.CompilerGenerated) != 0)
CreateBuilder (ec);
ec.Emit (OpCodes.Stloc, builder);
}
public void EmitAddressOf (EmitContext ec)
{
// TODO: Need something better for temporary variables
if ((flags & Flags.CompilerGenerated) != 0)
CreateBuilder (ec);
ec.Emit (OpCodes.Ldloca, builder);
}
public static string GetCompilerGeneratedName (Block block)
{
// HACK: Debugger depends on the name semantics
return "$locvar" + block.ParametersBlock.TemporaryLocalsCount++.ToString ("X");
}
public string GetReadOnlyContext ()
{
switch (flags & Flags.ReadonlyMask) {
case Flags.FixedVariable:
return "fixed variable";
case Flags.ForeachVariable:
return "foreach iteration variable";
case Flags.UsingVariable:
return "using variable";
}
throw new InternalErrorException ("Variable is not readonly");
}
public bool IsThisAssigned (FlowAnalysisContext fc, Block block)
{
if (VariableInfo == null)
throw new Exception ();
if (IsAssigned (fc))
return true;
return VariableInfo.IsFullyInitialized (fc, block.StartLocation);
}
public bool IsAssigned (FlowAnalysisContext fc)
{
return fc.IsDefinitelyAssigned (VariableInfo);
}
public void PrepareAssignmentAnalysis (BlockContext bc)
{
//
// No need to run assignment analysis for these guys
//
if ((flags & (Flags.Constant | Flags.ReadonlyMask | Flags.CompilerGenerated)) != 0)
return;
VariableInfo = VariableInfo.Create (bc, this);
}
//
// Mark the variables as referenced in the user code
//
public void SetIsUsed ()
{
flags |= Flags.Used;
}
public void SetHasAddressTaken ()
{
flags |= (Flags.AddressTaken | Flags.Used);
}
public override string ToString ()
{
return string.Format ("LocalInfo ({0},{1},{2},{3})", name, type, VariableInfo, Location);
}
}
///
/// Block represents a C# block.
///
///
///
/// This class is used in a number of places: either to represent
/// explicit blocks that the programmer places or implicit blocks.
///
/// Implicit blocks are used as labels or to introduce variable
/// declarations.
///
/// Top-level blocks derive from Block, and they are called ToplevelBlock
/// they contain extra information that is not necessary on normal blocks.
///
public class Block : Statement {
[Flags]
public enum Flags
{
Unchecked = 1,
ReachableEnd = 8,
Unsafe = 16,
HasCapturedVariable = 64,
HasCapturedThis = 1 << 7,
IsExpressionTree = 1 << 8,
CompilerGenerated = 1 << 9,
HasAsyncModifier = 1 << 10,
Resolved = 1 << 11,
YieldBlock = 1 << 12,
AwaitBlock = 1 << 13,
FinallyBlock = 1 << 14,
CatchBlock = 1 << 15,
Iterator = 1 << 20,
NoFlowAnalysis = 1 << 21,
InitializationEmitted = 1 << 22
}
public Block Parent;
public Location StartLocation;
public Location EndLocation;
public ExplicitBlock Explicit;
public ParametersBlock ParametersBlock;
protected Flags flags;
//
// The statements in this block
//
protected List statements;
protected List scope_initializers;
int? resolving_init_idx;
Block original;
#if DEBUG
static int id;
public int ID = id++;
static int clone_id_counter;
int clone_id;
#endif
// int assignable_slots;
public Block (Block parent, Location start, Location end)
: this (parent, 0, start, end)
{
}
public Block (Block parent, Flags flags, Location start, Location end)
{
if (parent != null) {
// the appropriate constructors will fixup these fields
ParametersBlock = parent.ParametersBlock;
Explicit = parent.Explicit;
}
this.Parent = parent;
this.flags = flags;
this.StartLocation = start;
this.EndLocation = end;
this.loc = start;
statements = new List (4);
this.original = this;
}
#region Properties
public Block Original {
get {
return original;
}
protected set {
original = value;
}
}
public bool IsCompilerGenerated {
get { return (flags & Flags.CompilerGenerated) != 0; }
set { flags = value ? flags | Flags.CompilerGenerated : flags & ~Flags.CompilerGenerated; }
}
public bool IsCatchBlock {
get {
return (flags & Flags.CatchBlock) != 0;
}
}
public bool IsFinallyBlock {
get {
return (flags & Flags.FinallyBlock) != 0;
}
}
public bool Unchecked {
get { return (flags & Flags.Unchecked) != 0; }
set { flags = value ? flags | Flags.Unchecked : flags & ~Flags.Unchecked; }
}
public bool Unsafe {
get { return (flags & Flags.Unsafe) != 0; }
set { flags |= Flags.Unsafe; }
}
public List Statements {
get { return statements; }
}
#endregion
public void SetEndLocation (Location loc)
{
EndLocation = loc;
}
public void AddLabel (LabeledStatement target)
{
ParametersBlock.TopBlock.AddLabel (target.Name, target);
}
public void AddLocalName (LocalVariable li)
{
AddLocalName (li.Name, li);
}
public void AddLocalName (string name, INamedBlockVariable li)
{
ParametersBlock.TopBlock.AddLocalName (name, li, false);
}
public virtual void Error_AlreadyDeclared (string name, INamedBlockVariable variable, string reason)
{
if (reason == null) {
Error_AlreadyDeclared (name, variable);
return;
}
ParametersBlock.TopBlock.Report.Error (136, variable.Location,
"A local variable named `{0}' cannot be declared in this scope because it would give a different meaning " +
"to `{0}', which is already used in a `{1}' scope to denote something else",
name, reason);
}
public virtual void Error_AlreadyDeclared (string name, INamedBlockVariable variable)
{
var pi = variable as ParametersBlock.ParameterInfo;
if (pi != null) {
pi.Parameter.Error_DuplicateName (ParametersBlock.TopBlock.Report);
} else {
ParametersBlock.TopBlock.Report.Error (128, variable.Location,
"A local variable named `{0}' is already defined in this scope", name);
}
}
public virtual void Error_AlreadyDeclaredTypeParameter (string name, Location loc)
{
ParametersBlock.TopBlock.Report.Error (412, loc,
"The type parameter name `{0}' is the same as local variable or parameter name",
name);
}
//
// It should be used by expressions which require to
// register a statement during resolve process.
//
public void AddScopeStatement (Statement s)
{
if (scope_initializers == null)
scope_initializers = new List ();
//
// Simple recursive helper, when resolve scope initializer another
// new scope initializer can be added, this ensures it's initialized
// before existing one. For now this can happen with expression trees
// in base ctor initializer only
//
if (resolving_init_idx.HasValue) {
scope_initializers.Insert (resolving_init_idx.Value, s);
++resolving_init_idx;
} else {
scope_initializers.Add (s);
}
}
public void InsertStatement (int index, Statement s)
{
statements.Insert (index, s);
}
public void AddStatement (Statement s)
{
statements.Add (s);
}
public LabeledStatement LookupLabel (string name)
{
return ParametersBlock.GetLabel (name, this);
}
public override Reachability MarkReachable (Reachability rc)
{
if (rc.IsUnreachable)
return rc;
MarkReachableScope (rc);
foreach (var s in statements) {
rc = s.MarkReachable (rc);
if (rc.IsUnreachable) {
if ((flags & Flags.ReachableEnd) != 0)
return new Reachability ();
return rc;
}
}
flags |= Flags.ReachableEnd;
return rc;
}
public void MarkReachableScope (Reachability rc)
{
base.MarkReachable (rc);
if (scope_initializers != null) {
foreach (var si in scope_initializers)
si.MarkReachable (rc);
}
}
public override bool Resolve (BlockContext bc)
{
if ((flags & Flags.Resolved) != 0)
return true;
Block prev_block = bc.CurrentBlock;
bc.CurrentBlock = this;
//
// Compiler generated scope statements
//
if (scope_initializers != null) {
for (resolving_init_idx = 0; resolving_init_idx < scope_initializers.Count; ++resolving_init_idx) {
scope_initializers[resolving_init_idx.Value].Resolve (bc);
}
resolving_init_idx = null;
}
bool ok = true;
int statement_count = statements.Count;
for (int ix = 0; ix < statement_count; ix++){
Statement s = statements [ix];
if (!s.Resolve (bc)) {
ok = false;
statements [ix] = new EmptyStatement (s.loc);
continue;
}
}
bc.CurrentBlock = prev_block;
flags |= Flags.Resolved;
return ok;
}
protected override void DoEmit (EmitContext ec)
{
for (int ix = 0; ix < statements.Count; ix++){
statements [ix].Emit (ec);
}
}
public override void Emit (EmitContext ec)
{
if (scope_initializers != null)
EmitScopeInitializers (ec);
DoEmit (ec);
}
protected void EmitScopeInitializers (EmitContext ec)
{
foreach (Statement s in scope_initializers)
s.Emit (ec);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
if (scope_initializers != null) {
foreach (var si in scope_initializers)
si.FlowAnalysis (fc);
}
return DoFlowAnalysis (fc, 0);
}
bool DoFlowAnalysis (FlowAnalysisContext fc, int startIndex)
{
bool end_unreachable = !reachable;
bool goto_flow_analysis = startIndex != 0;
for (; startIndex < statements.Count; ++startIndex) {
var s = statements[startIndex];
end_unreachable = s.FlowAnalysis (fc);
if (s.IsUnreachable) {
statements [startIndex] = RewriteUnreachableStatement (s);
continue;
}
//
// Statement end reachability is needed mostly due to goto support. Consider
//
// if (cond) {
// goto X;
// } else {
// goto Y;
// }
// X:
//
// X label is reachable only via goto not as another statement after if. We need
// this for flow-analysis only to carry variable info correctly.
//
if (end_unreachable) {
bool after_goto_case = goto_flow_analysis && s is GotoCase;
var f = s as TryFinally;
if (f != null && !f.FinallyBlock.HasReachableClosingBrace) {
//
// Special case for try-finally with unreachable code after
// finally block. Try block has to include leave opcode but there is
// no label to leave to after unreachable finally block closing
// brace. This sentinel ensures there is always IL instruction to
// leave to even if we know it'll never be reached.
//
statements.Insert (startIndex + 1, new SentinelStatement ());
} else {
for (++startIndex; startIndex < statements.Count; ++startIndex) {
s = statements [startIndex];
if (s is SwitchLabel) {
if (!after_goto_case)
s.FlowAnalysis (fc);
break;
}
if (s.IsUnreachable) {
s.FlowAnalysis (fc);
statements [startIndex] = RewriteUnreachableStatement (s);
}
}
}
//
// Idea is to stop after goto case because goto case will always have at least same
// variable assigned as switch case label. This saves a lot for complex goto case tests
//
if (after_goto_case)
break;
continue;
}
var lb = s as LabeledStatement;
if (lb != null && fc.AddReachedLabel (lb))
break;
}
//
// The condition should be true unless there is forward jumping goto
//
// if (this is ExplicitBlock && end_unreachable != Explicit.HasReachableClosingBrace)
// Debug.Fail ();
return !Explicit.HasReachableClosingBrace;
}
static Statement RewriteUnreachableStatement (Statement s)
{
// LAMESPEC: It's not clear whether declararion statement should be part of reachability
// analysis. Even csc report unreachable warning for it but it's actually used hence
// we try to emulate this behaviour
//
// Consider:
// goto L;
// int v;
// L:
// v = 1;
if (s is BlockVariable || s is EmptyStatement || s is SentinelStatement)
return s;
return new EmptyStatement (s.loc);
}
public void ScanGotoJump (Statement label)
{
int i;
for (i = 0; i < statements.Count; ++i) {
if (statements[i] == label)
break;
}
var rc = new Reachability ();
for (++i; i < statements.Count; ++i) {
var s = statements[i];
rc = s.MarkReachable (rc);
if (rc.IsUnreachable)
return;
}
flags |= Flags.ReachableEnd;
}
public void ScanGotoJump (Statement label, FlowAnalysisContext fc)
{
int i;
for (i = 0; i < statements.Count; ++i) {
if (statements[i] == label)
break;
}
DoFlowAnalysis (fc, ++i);
}
#if DEBUG
public override string ToString ()
{
return String.Format ("{0}: ID={1} Clone={2} Location={3}", GetType (), ID, clone_id != 0, StartLocation);
}
#endif
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Block target = (Block) t;
#if DEBUG
target.clone_id = ++clone_id_counter;
#endif
clonectx.AddBlockMap (this, target);
if (original != this)
clonectx.AddBlockMap (original, target);
target.ParametersBlock = (ParametersBlock) (ParametersBlock == this ? target : clonectx.RemapBlockCopy (ParametersBlock));
target.Explicit = (ExplicitBlock) (Explicit == this ? target : clonectx.LookupBlock (Explicit));
if (Parent != null)
target.Parent = clonectx.RemapBlockCopy (Parent);
target.statements = new List (statements.Count);
foreach (Statement s in statements)
target.statements.Add (s.Clone (clonectx));
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public class ExplicitBlock : Block
{
protected AnonymousMethodStorey am_storey;
int debug_scope_index;
public ExplicitBlock (Block parent, Location start, Location end)
: this (parent, (Flags) 0, start, end)
{
}
public ExplicitBlock (Block parent, Flags flags, Location start, Location end)
: base (parent, flags, start, end)
{
this.Explicit = this;
}
#region Properties
public AnonymousMethodStorey AnonymousMethodStorey {
get {
return am_storey;
}
}
public bool HasAwait {
get {
return (flags & Flags.AwaitBlock) != 0;
}
}
public bool HasCapturedThis {
set {
flags = value ? flags | Flags.HasCapturedThis : flags & ~Flags.HasCapturedThis;
}
get {
return (flags & Flags.HasCapturedThis) != 0;
}
}
//
// Used to indicate that the block has reference to parent
// block and cannot be made static when defining anonymous method
//
public bool HasCapturedVariable {
set {
flags = value ? flags | Flags.HasCapturedVariable : flags & ~Flags.HasCapturedVariable;
}
get {
return (flags & Flags.HasCapturedVariable) != 0;
}
}
public bool HasReachableClosingBrace {
get {
return (flags & Flags.ReachableEnd) != 0;
}
set {
flags = value ? flags | Flags.ReachableEnd : flags & ~Flags.ReachableEnd;
}
}
public bool HasYield {
get {
return (flags & Flags.YieldBlock) != 0;
}
}
#endregion
//
// Creates anonymous method storey in current block
//
public AnonymousMethodStorey CreateAnonymousMethodStorey (ResolveContext ec)
{
//
// Return same story for iterator and async blocks unless we are
// in nested anonymous method
//
if (ec.CurrentAnonymousMethod is StateMachineInitializer && ParametersBlock.Original == ec.CurrentAnonymousMethod.Block.Original)
return ec.CurrentAnonymousMethod.Storey;
if (am_storey == null) {
MemberBase mc = ec.MemberContext as MemberBase;
//
// Creates anonymous method storey for this block
//
am_storey = new AnonymousMethodStorey (this, ec.CurrentMemberDefinition.Parent.PartialContainer, mc, ec.CurrentTypeParameters, "AnonStorey", MemberKind.Class);
}
return am_storey;
}
public void EmitScopeInitialization (EmitContext ec)
{
if ((flags & Flags.InitializationEmitted) != 0)
return;
if (am_storey != null) {
DefineStoreyContainer (ec, am_storey);
am_storey.EmitStoreyInstantiation (ec, this);
}
if (scope_initializers != null)
EmitScopeInitializers (ec);
flags |= Flags.InitializationEmitted;
}
public override void Emit (EmitContext ec)
{
if (Parent != null) {
// TODO: It's needed only when scope has variable (normal or lifted)
ec.BeginScope (GetDebugSymbolScopeIndex ());
}
EmitScopeInitialization (ec);
if (ec.EmitAccurateDebugInfo && !IsCompilerGenerated && ec.Mark (StartLocation)) {
ec.Emit (OpCodes.Nop);
}
DoEmit (ec);
if (Parent != null)
ec.EndScope ();
if (ec.EmitAccurateDebugInfo && HasReachableClosingBrace && !(this is ParametersBlock) &&
!IsCompilerGenerated && ec.Mark (EndLocation)) {
ec.Emit (OpCodes.Nop);
}
}
protected void DefineStoreyContainer (EmitContext ec, AnonymousMethodStorey storey)
{
if (ec.CurrentAnonymousMethod != null && ec.CurrentAnonymousMethod.Storey != null) {
storey.SetNestedStoryParent (ec.CurrentAnonymousMethod.Storey);
storey.Mutator = ec.CurrentAnonymousMethod.Storey.Mutator;
}
//
// Creates anonymous method storey
//
storey.CreateContainer ();
storey.DefineContainer ();
if (Original.Explicit.HasCapturedThis && Original.ParametersBlock.TopBlock.ThisReferencesFromChildrenBlock != null) {
//
// Only first storey in path will hold this reference. All children blocks will
// reference it indirectly using $ref field
//
for (Block b = Original.Explicit; b != null; b = b.Parent) {
if (b.Parent != null) {
var s = b.Parent.Explicit.AnonymousMethodStorey;
if (s != null) {
storey.HoistedThis = s.HoistedThis;
break;
}
}
if (b.Explicit == b.Explicit.ParametersBlock && b.Explicit.ParametersBlock.StateMachine != null) {
if (storey.HoistedThis == null)
storey.HoistedThis = b.Explicit.ParametersBlock.StateMachine.HoistedThis;
if (storey.HoistedThis != null)
break;
}
}
//
// We are the first storey on path and 'this' has to be hoisted
//
if (storey.HoistedThis == null || !(storey.Parent is HoistedStoreyClass)) {
foreach (ExplicitBlock ref_block in Original.ParametersBlock.TopBlock.ThisReferencesFromChildrenBlock) {
//
// ThisReferencesFromChildrenBlock holds all reference even if they
// are not on this path. It saves some memory otherwise it'd have to
// be in every explicit block. We run this check to see if the reference
// is valid for this storey
//
Block block_on_path = ref_block;
for (; block_on_path != null && block_on_path != Original; block_on_path = block_on_path.Parent);
if (block_on_path == null)
continue;
if (storey.HoistedThis == null) {
storey.AddCapturedThisField (ec, null);
}
for (ExplicitBlock b = ref_block; b.AnonymousMethodStorey != storey; b = b.Parent.Explicit) {
ParametersBlock pb;
AnonymousMethodStorey b_storey = b.AnonymousMethodStorey;
if (b_storey != null) {
//
// Don't add storey cross reference for `this' when the storey ends up not
// beeing attached to any parent
//
if (b.ParametersBlock.StateMachine == null) {
AnonymousMethodStorey s = null;
for (Block ab = b.AnonymousMethodStorey.OriginalSourceBlock.Parent; ab != null; ab = ab.Parent) {
s = ab.Explicit.AnonymousMethodStorey;
if (s != null)
break;
}
// Needs to be in sync with AnonymousMethodBody::DoCreateMethodHost
if (s == null) {
var parent = storey == null || storey.Kind == MemberKind.Struct ? null : storey;
b.AnonymousMethodStorey.AddCapturedThisField (ec, parent);
break;
}
}
//
// Stop propagation inside same top block
//
if (b.ParametersBlock == ParametersBlock.Original) {
b_storey.AddParentStoreyReference (ec, storey);
// b_storey.HoistedThis = storey.HoistedThis;
break;
}
b = pb = b.ParametersBlock;
} else {
pb = b as ParametersBlock;
}
if (pb != null && pb.StateMachine != null) {
if (pb.StateMachine == storey)
break;
//
// If we are state machine with no parent. We can hook into parent without additional
// reference and capture this directly
//
ExplicitBlock parent_storey_block = pb;
while (parent_storey_block.Parent != null) {
parent_storey_block = parent_storey_block.Parent.Explicit;
if (parent_storey_block.AnonymousMethodStorey != null) {
break;
}
}
if (parent_storey_block.AnonymousMethodStorey == null) {
if (pb.StateMachine.HoistedThis == null) {
pb.StateMachine.AddCapturedThisField (ec, null);
b.HasCapturedThis = true;
}
continue;
}
var parent_this_block = pb;
while (parent_this_block.Parent != null) {
parent_this_block = parent_this_block.Parent.ParametersBlock;
if (parent_this_block.StateMachine != null && parent_this_block.StateMachine.HoistedThis != null) {
break;
}
}
//
// Add reference to closest storey which holds captured this
//
pb.StateMachine.AddParentStoreyReference (ec, parent_this_block.StateMachine ?? storey);
}
//
// Add parent storey reference only when this is not captured directly
//
if (b_storey != null) {
b_storey.AddParentStoreyReference (ec, storey);
b_storey.HoistedThis = storey.HoistedThis;
}
}
}
}
}
var ref_blocks = storey.ReferencesFromChildrenBlock;
if (ref_blocks != null) {
foreach (ExplicitBlock ref_block in ref_blocks) {
for (ExplicitBlock b = ref_block; b.AnonymousMethodStorey != storey; b = b.Parent.Explicit) {
if (b.AnonymousMethodStorey != null) {
b.AnonymousMethodStorey.AddParentStoreyReference (ec, storey);
//
// Stop propagation inside same top block
//
if (b.ParametersBlock == ParametersBlock.Original)
break;
b = b.ParametersBlock;
}
var pb = b as ParametersBlock;
if (pb != null && pb.StateMachine != null) {
if (pb.StateMachine == storey)
break;
pb.StateMachine.AddParentStoreyReference (ec, storey);
}
b.HasCapturedVariable = true;
}
}
}
storey.Define ();
storey.PrepareEmit ();
storey.Parent.PartialContainer.AddCompilerGeneratedClass (storey);
}
public int GetDebugSymbolScopeIndex ()
{
if (debug_scope_index == 0)
debug_scope_index = ++ParametersBlock.debug_scope_index;
return debug_scope_index;
}
public void RegisterAsyncAwait ()
{
var block = this;
while ((block.flags & Flags.AwaitBlock) == 0) {
block.flags |= Flags.AwaitBlock;
if (block is ParametersBlock)
return;
block = block.Parent.Explicit;
}
}
public void RegisterIteratorYield ()
{
ParametersBlock.TopBlock.IsIterator = true;
var block = this;
while ((block.flags & Flags.YieldBlock) == 0) {
block.flags |= Flags.YieldBlock;
if (block.Parent == null)
return;
block = block.Parent.Explicit;
}
}
public void SetCatchBlock ()
{
flags |= Flags.CatchBlock;
}
public void SetFinallyBlock ()
{
flags |= Flags.FinallyBlock;
}
public void WrapIntoDestructor (TryFinally tf, ExplicitBlock tryBlock)
{
tryBlock.statements = statements;
statements = new List (1);
statements.Add (tf);
}
}
//
// ParametersBlock was introduced to support anonymous methods
// and lambda expressions
//
public class ParametersBlock : ExplicitBlock
{
public class ParameterInfo : INamedBlockVariable
{
readonly ParametersBlock block;
readonly int index;
public VariableInfo VariableInfo;
bool is_locked;
public ParameterInfo (ParametersBlock block, int index)
{
this.block = block;
this.index = index;
}
#region Properties
public ParametersBlock Block {
get {
return block;
}
}
Block INamedBlockVariable.Block {
get {
return block;
}
}
public bool IsDeclared {
get {
return true;
}
}
public bool IsParameter {
get {
return true;
}
}
public bool IsLocked {
get {
return is_locked;
}
set {
is_locked = value;
}
}
public Location Location {
get {
return Parameter.Location;
}
}
public Parameter Parameter {
get {
return block.Parameters [index];
}
}
public TypeSpec ParameterType {
get {
return Parameter.Type;
}
}
#endregion
public Expression CreateReferenceExpression (ResolveContext rc, Location loc)
{
return new ParameterReference (this, loc);
}
}
//
// Block is converted into an expression
//
sealed class BlockScopeExpression : Expression
{
Expression child;
readonly ParametersBlock block;
public BlockScopeExpression (Expression child, ParametersBlock block)
{
this.child = child;
this.block = block;
}
public override bool ContainsEmitWithAwait ()
{
return child.ContainsEmitWithAwait ();
}
public override Expression CreateExpressionTree (ResolveContext ec)
{
throw new NotSupportedException ();
}
protected override Expression DoResolve (ResolveContext ec)
{
if (child == null)
return null;
child = child.Resolve (ec);
if (child == null)
return null;
eclass = child.eclass;
type = child.Type;
return this;
}
public override void Emit (EmitContext ec)
{
block.EmitScopeInitializers (ec);
child.Emit (ec);
}
}
protected ParametersCompiled parameters;
protected ParameterInfo[] parameter_info;
protected bool resolved;
protected ToplevelBlock top_block;
protected StateMachine state_machine;
protected Dictionary labels;
public ParametersBlock (Block parent, ParametersCompiled parameters, Location start, Flags flags = 0)
: base (parent, 0, start, start)
{
if (parameters == null)
throw new ArgumentNullException ("parameters");
this.parameters = parameters;
ParametersBlock = this;
this.flags |= flags | (parent.ParametersBlock.flags & (Flags.YieldBlock | Flags.AwaitBlock));
this.top_block = parent.ParametersBlock.top_block;
ProcessParameters ();
}
protected ParametersBlock (ParametersCompiled parameters, Location start)
: base (null, 0, start, start)
{
if (parameters == null)
throw new ArgumentNullException ("parameters");
this.parameters = parameters;
ParametersBlock = this;
}
//
// It's supposed to be used by method body implementation of anonymous methods
//
protected ParametersBlock (ParametersBlock source, ParametersCompiled parameters)
: base (null, 0, source.StartLocation, source.EndLocation)
{
this.parameters = parameters;
this.statements = source.statements;
this.scope_initializers = source.scope_initializers;
this.resolved = true;
this.reachable = source.reachable;
this.am_storey = source.am_storey;
this.state_machine = source.state_machine;
this.flags = source.flags & Flags.ReachableEnd;
ParametersBlock = this;
//
// Overwrite original for comparison purposes when linking cross references
// between anonymous methods
//
Original = source.Original;
}
#region Properties
public bool IsAsync {
get {
return (flags & Flags.HasAsyncModifier) != 0;
}
set {
flags = value ? flags | Flags.HasAsyncModifier : flags & ~Flags.HasAsyncModifier;
}
}
//
// Block has been converted to expression tree
//
public bool IsExpressionTree {
get {
return (flags & Flags.IsExpressionTree) != 0;
}
}
//
// The parameters for the block.
//
public ParametersCompiled Parameters {
get {
return parameters;
}
}
public StateMachine StateMachine {
get {
return state_machine;
}
}
public ToplevelBlock TopBlock {
get {
return top_block;
}
set {
top_block = value;
}
}
public bool Resolved {
get {
return (flags & Flags.Resolved) != 0;
}
}
public int TemporaryLocalsCount { get; set; }
#endregion
//
// Checks whether all `out' parameters have been assigned.
//
public void CheckControlExit (FlowAnalysisContext fc)
{
CheckControlExit (fc, fc.DefiniteAssignment);
}
public virtual void CheckControlExit (FlowAnalysisContext fc, DefiniteAssignmentBitSet dat)
{
if (parameter_info == null)
return;
foreach (var p in parameter_info) {
if (p.VariableInfo == null)
continue;
if (p.VariableInfo.IsAssigned (dat))
continue;
fc.Report.Error (177, p.Location,
"The out parameter `{0}' must be assigned to before control leaves the current method",
p.Parameter.Name);
}
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
base.CloneTo (clonectx, t);
var target = (ParametersBlock) t;
//
// Clone label statements as well as they contain block reference
//
var pb = this;
while (true) {
if (pb.labels != null) {
target.labels = new Dictionary ();
foreach (var entry in pb.labels) {
var list = entry.Value as List;
if (list != null) {
var list_clone = new List ();
foreach (var lentry in list) {
list_clone.Add (RemapLabeledStatement (lentry, clonectx.RemapBlockCopy (lentry.Block)));
}
target.labels.Add (entry.Key, list_clone);
} else {
var labeled = (LabeledStatement) entry.Value;
target.labels.Add (entry.Key, RemapLabeledStatement (labeled, clonectx.RemapBlockCopy (labeled.Block)));
}
}
break;
}
if (pb.Parent == null)
break;
pb = pb.Parent.ParametersBlock;
}
}
public override Expression CreateExpressionTree (ResolveContext ec)
{
if (statements.Count == 1) {
Expression expr = statements[0].CreateExpressionTree (ec);
if (scope_initializers != null)
expr = new BlockScopeExpression (expr, this);
return expr;
}
return base.CreateExpressionTree (ec);
}
public override void Emit (EmitContext ec)
{
if (state_machine != null && state_machine.OriginalSourceBlock != this) {
DefineStoreyContainer (ec, state_machine);
state_machine.EmitStoreyInstantiation (ec, this);
}
base.Emit (ec);
}
public void EmitEmbedded (EmitContext ec)
{
if (state_machine != null && state_machine.OriginalSourceBlock != this) {
DefineStoreyContainer (ec, state_machine);
state_machine.EmitStoreyInstantiation (ec, this);
}
base.Emit (ec);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
var res = base.DoFlowAnalysis (fc);
if (HasReachableClosingBrace)
CheckControlExit (fc);
return res;
}
public LabeledStatement GetLabel (string name, Block block)
{
//
// Cloned parameters blocks can have their own cloned version of top-level labels
//
if (labels == null) {
if (Parent != null)
return Parent.ParametersBlock.GetLabel (name, block);
return null;
}
object value;
if (!labels.TryGetValue (name, out value)) {
return null;
}
var label = value as LabeledStatement;
Block b = block;
if (label != null) {
if (IsLabelVisible (label, b))
return label;
} else {
List list = (List) value;
for (int i = 0; i < list.Count; ++i) {
label = list[i];
if (IsLabelVisible (label, b))
return label;
}
}
return null;
}
static bool IsLabelVisible (LabeledStatement label, Block b)
{
do {
if (label.Block == b)
return true;
b = b.Parent;
} while (b != null);
return false;
}
public ParameterInfo GetParameterInfo (Parameter p)
{
for (int i = 0; i < parameters.Count; ++i) {
if (parameters[i] == p)
return parameter_info[i];
}
throw new ArgumentException ("Invalid parameter");
}
public ParameterReference GetParameterReference (int index, Location loc)
{
return new ParameterReference (parameter_info[index], loc);
}
public Statement PerformClone (ref HashSet undeclaredVariables)
{
undeclaredVariables = TopBlock.GetUndeclaredVariables ();
CloneContext clonectx = new CloneContext ();
return Clone (clonectx);
}
protected void ProcessParameters ()
{
if (parameters.Count == 0)
return;
parameter_info = new ParameterInfo[parameters.Count];
for (int i = 0; i < parameter_info.Length; ++i) {
var p = parameters.FixedParameters[i];
if (p == null)
continue;
// TODO: Should use Parameter only and more block there
parameter_info[i] = new ParameterInfo (this, i);
if (p.Name != null)
AddLocalName (p.Name, parameter_info[i]);
}
}
LabeledStatement RemapLabeledStatement (LabeledStatement stmt, Block dst)
{
var src = stmt.Block;
//
// Cannot remap label block if the label was not yet cloned which
// can happen in case of anonymous method inside anoynymous method
// with a label. But in this case we don't care because goto cannot
// jump of out anonymous method
//
if (src.ParametersBlock != this)
return stmt;
var src_stmts = src.Statements;
for (int i = 0; i < src_stmts.Count; ++i) {
if (src_stmts[i] == stmt)
return (LabeledStatement) dst.Statements[i];
}
throw new InternalErrorException ("Should never be reached");
}
public override bool Resolve (BlockContext bc)
{
// TODO: if ((flags & Flags.Resolved) != 0)
if (resolved)
return true;
resolved = true;
if (bc.HasSet (ResolveContext.Options.ExpressionTreeConversion))
flags |= Flags.IsExpressionTree;
try {
PrepareAssignmentAnalysis (bc);
if (!base.Resolve (bc))
return false;
} catch (Exception e) {
if (e is CompletionResult || bc.Report.IsDisabled || e is FatalException || bc.Report.Printer is NullReportPrinter || bc.Module.Compiler.Settings.BreakOnInternalError)
throw;
if (bc.CurrentBlock != null) {
bc.Report.Error (584, bc.CurrentBlock.StartLocation, "Internal compiler error: {0}", e.Message);
} else {
bc.Report.Error (587, "Internal compiler error: {0}", e.Message);
}
}
//
// If an asynchronous body of F is either an expression classified as nothing, or a
// statement block where no return statements have expressions, the inferred return type is Task
//
if (IsAsync) {
var am = bc.CurrentAnonymousMethod as AnonymousMethodBody;
if (am != null && am.ReturnTypeInference != null && !am.ReturnTypeInference.HasBounds (0)) {
am.ReturnTypeInference = null;
am.ReturnType = bc.Module.PredefinedTypes.Task.TypeSpec;
return true;
}
}
return true;
}
void PrepareAssignmentAnalysis (BlockContext bc)
{
for (int i = 0; i < parameters.Count; ++i) {
var par = parameters.FixedParameters[i];
if ((par.ModFlags & Parameter.Modifier.OUT) == 0)
continue;
parameter_info [i].VariableInfo = VariableInfo.Create (bc, (Parameter) par);
}
}
public ToplevelBlock ConvertToIterator (IMethodData method, TypeDefinition host, TypeSpec iterator_type, bool is_enumerable)
{
var iterator = new Iterator (this, method, host, iterator_type, is_enumerable);
var stateMachine = new IteratorStorey (iterator);
state_machine = stateMachine;
iterator.SetStateMachine (stateMachine);
var tlb = new ToplevelBlock (host.Compiler, Parameters, Location.Null, Flags.CompilerGenerated);
tlb.Original = this;
tlb.state_machine = stateMachine;
tlb.AddStatement (new Return (iterator, iterator.Location));
return tlb;
}
public ParametersBlock ConvertToAsyncTask (IMemberContext context, TypeDefinition host, ParametersCompiled parameters, TypeSpec returnType, TypeSpec delegateType, Location loc)
{
for (int i = 0; i < parameters.Count; i++) {
Parameter p = parameters[i];
Parameter.Modifier mod = p.ModFlags;
if ((mod & Parameter.Modifier.RefOutMask) != 0) {
host.Compiler.Report.Error (1988, p.Location,
"Async methods cannot have ref or out parameters");
return this;
}
if (p is ArglistParameter) {
host.Compiler.Report.Error (4006, p.Location,
"__arglist is not allowed in parameter list of async methods");
return this;
}
if (parameters.Types[i].IsPointer) {
host.Compiler.Report.Error (4005, p.Location,
"Async methods cannot have unsafe parameters");
return this;
}
}
if (!HasAwait) {
host.Compiler.Report.Warning (1998, 1, loc,
"Async block lacks `await' operator and will run synchronously");
}
var block_type = host.Module.Compiler.BuiltinTypes.Void;
var initializer = new AsyncInitializer (this, host, block_type);
initializer.Type = block_type;
initializer.DelegateType = delegateType;
var stateMachine = new AsyncTaskStorey (this, context, initializer, returnType);
state_machine = stateMachine;
initializer.SetStateMachine (stateMachine);
const Flags flags = Flags.CompilerGenerated;
var b = this is ToplevelBlock ?
new ToplevelBlock (host.Compiler, Parameters, Location.Null, flags) :
new ParametersBlock (Parent, parameters, Location.Null, flags | Flags.HasAsyncModifier);
b.Original = this;
b.state_machine = stateMachine;
b.AddStatement (new AsyncInitializerStatement (initializer));
return b;
}
}
//
//
//
public class ToplevelBlock : ParametersBlock
{
LocalVariable this_variable;
CompilerContext compiler;
Dictionary names;
List this_references;
public ToplevelBlock (CompilerContext ctx, Location loc)
: this (ctx, ParametersCompiled.EmptyReadOnlyParameters, loc)
{
}
public ToplevelBlock (CompilerContext ctx, ParametersCompiled parameters, Location start, Flags flags = 0)
: base (parameters, start)
{
this.compiler = ctx;
this.flags = flags;
top_block = this;
ProcessParameters ();
}
//
// Recreates a top level block from parameters block. Used for
// compiler generated methods where the original block comes from
// explicit child block. This works for already resolved blocks
// only to ensure we resolve them in the correct flow order
//
public ToplevelBlock (ParametersBlock source, ParametersCompiled parameters)
: base (source, parameters)
{
this.compiler = source.TopBlock.compiler;
top_block = this;
}
public bool IsIterator {
get {
return (flags & Flags.Iterator) != 0;
}
set {
flags = value ? flags | Flags.Iterator : flags & ~Flags.Iterator;
}
}
public Report Report {
get {
return compiler.Report;
}
}
//
// Used by anonymous blocks to track references of `this' variable
//
public List ThisReferencesFromChildrenBlock {
get {
return this_references;
}
}
//
// Returns the "this" instance variable of this block.
// See AddThisVariable() for more information.
//
public LocalVariable ThisVariable {
get {
return this_variable;
}
}
public void AddLocalName (string name, INamedBlockVariable li, bool ignoreChildrenBlocks)
{
if (names == null)
names = new Dictionary ();
object value;
if (!names.TryGetValue (name, out value)) {
names.Add (name, li);
return;
}
INamedBlockVariable existing = value as INamedBlockVariable;
List existing_list;
if (existing != null) {
existing_list = new List ();
existing_list.Add (existing);
names[name] = existing_list;
} else {
existing_list = (List) value;
}
//
// A collision checking between local names
//
var variable_block = li.Block.Explicit;
for (int i = 0; i < existing_list.Count; ++i) {
existing = existing_list[i];
Block b = existing.Block.Explicit;
// Collision at same level
if (variable_block == b) {
li.Block.Error_AlreadyDeclared (name, li);
break;
}
// Collision with parent
Block parent = variable_block;
while ((parent = parent.Parent) != null) {
if (parent == b) {
li.Block.Error_AlreadyDeclared (name, li, "parent or current");
i = existing_list.Count;
break;
}
}
if (!ignoreChildrenBlocks && variable_block.Parent != b.Parent) {
// Collision with children
while ((b = b.Parent) != null) {
if (variable_block == b) {
li.Block.Error_AlreadyDeclared (name, li, "child");
i = existing_list.Count;
break;
}
}
}
}
existing_list.Add (li);
}
public void AddLabel (string name, LabeledStatement label)
{
if (labels == null)
labels = new Dictionary ();
object value;
if (!labels.TryGetValue (name, out value)) {
labels.Add (name, label);
return;
}
LabeledStatement existing = value as LabeledStatement;
List existing_list;
if (existing != null) {
existing_list = new List ();
existing_list.Add (existing);
labels[name] = existing_list;
} else {
existing_list = (List) value;
}
//
// A collision checking between labels
//
for (int i = 0; i < existing_list.Count; ++i) {
existing = existing_list[i];
Block b = existing.Block;
// Collision at same level
if (label.Block == b) {
Report.SymbolRelatedToPreviousError (existing.loc, name);
Report.Error (140, label.loc, "The label `{0}' is a duplicate", name);
break;
}
// Collision with parent
b = label.Block;
while ((b = b.Parent) != null) {
if (existing.Block == b) {
Report.Error (158, label.loc,
"The label `{0}' shadows another label by the same name in a contained scope", name);
i = existing_list.Count;
break;
}
}
// Collision with with children
b = existing.Block;
while ((b = b.Parent) != null) {
if (label.Block == b) {
Report.Error (158, label.loc,
"The label `{0}' shadows another label by the same name in a contained scope", name);
i = existing_list.Count;
break;
}
}
}
existing_list.Add (label);
}
public void AddThisReferenceFromChildrenBlock (ExplicitBlock block)
{
if (this_references == null)
this_references = new List ();
if (!this_references.Contains (block))
this_references.Add (block);
}
public void RemoveThisReferenceFromChildrenBlock (ExplicitBlock block)
{
this_references.Remove (block);
}
//
// Creates an arguments set from all parameters, useful for method proxy calls
//
public Arguments GetAllParametersArguments ()
{
int count = parameters.Count;
Arguments args = new Arguments (count);
for (int i = 0; i < count; ++i) {
var pi = parameter_info[i];
var arg_expr = GetParameterReference (i, pi.Location);
Argument.AType atype_modifier;
switch (pi.Parameter.ParameterModifier & Parameter.Modifier.RefOutMask) {
case Parameter.Modifier.REF:
atype_modifier = Argument.AType.Ref;
break;
case Parameter.Modifier.OUT:
atype_modifier = Argument.AType.Out;
break;
default:
atype_modifier = 0;
break;
}
args.Add (new Argument (arg_expr, atype_modifier));
}
return args;
}
//
// Lookup inside a block, the returned value can represent 3 states
//
// true+variable: A local name was found and it's valid
// false+variable: A local name was found in a child block only
// false+null: No local name was found
//
public bool GetLocalName (string name, Block block, ref INamedBlockVariable variable)
{
if (names == null)
return false;
object value;
if (!names.TryGetValue (name, out value))
return false;
variable = value as INamedBlockVariable;
Block b = block;
if (variable != null) {
do {
if (variable.Block == b.Original)
return true;
b = b.Parent;
} while (b != null);
b = variable.Block;
do {
if (block == b)
return false;
b = b.Parent;
} while (b != null);
} else {
List list = (List) value;
for (int i = 0; i < list.Count; ++i) {
variable = list[i];
do {
if (variable.Block == b.Original)
return true;
b = b.Parent;
} while (b != null);
b = variable.Block;
do {
if (block == b)
return false;
b = b.Parent;
} while (b != null);
b = block;
}
}
variable = null;
return false;
}
public void IncludeBlock (ParametersBlock pb, ToplevelBlock block)
{
if (block.names != null) {
foreach (var n in block.names) {
var variable = n.Value as INamedBlockVariable;
if (variable != null) {
if (variable.Block.ParametersBlock == pb)
AddLocalName (n.Key, variable, false);
continue;
}
foreach (var v in (List) n.Value)
if (v.Block.ParametersBlock == pb)
AddLocalName (n.Key, v, false);
}
}
}
//
// This is used by non-static `struct' constructors which do not have an
// initializer - in this case, the constructor must initialize all of the
// struct's fields. To do this, we add a "this" variable and use the flow
// analysis code to ensure that it's been fully initialized before control
// leaves the constructor.
//
public void AddThisVariable (BlockContext bc)
{
if (this_variable != null)
throw new InternalErrorException (StartLocation.ToString ());
this_variable = new LocalVariable (this, "this", LocalVariable.Flags.IsThis | LocalVariable.Flags.Used, StartLocation);
this_variable.Type = bc.CurrentType;
this_variable.PrepareAssignmentAnalysis (bc);
}
public override void CheckControlExit (FlowAnalysisContext fc, DefiniteAssignmentBitSet dat)
{
//
// If we're a non-static struct constructor which doesn't have an
// initializer, then we must initialize all of the struct's fields.
//
if (this_variable != null)
this_variable.IsThisAssigned (fc, this);
base.CheckControlExit (fc, dat);
}
public HashSet GetUndeclaredVariables ()
{
if (names == null)
return null;
HashSet variables = null;
foreach (var entry in names) {
var complex = entry.Value as List;
if (complex != null) {
foreach (var centry in complex) {
if (IsUndeclaredVariable (centry)) {
if (variables == null)
variables = new HashSet ();
variables.Add ((LocalVariable) centry);
}
}
} else if (IsUndeclaredVariable ((INamedBlockVariable)entry.Value)) {
if (variables == null)
variables = new HashSet ();
variables.Add ((LocalVariable)entry.Value);
}
}
return variables;
}
static bool IsUndeclaredVariable (INamedBlockVariable namedBlockVariable)
{
var lv = namedBlockVariable as LocalVariable;
return lv != null && !lv.IsDeclared;
}
public void SetUndeclaredVariables (HashSet undeclaredVariables)
{
if (names == null)
return;
foreach (var entry in names) {
var complex = entry.Value as List;
if (complex != null) {
foreach (var centry in complex) {
var lv = centry as LocalVariable;
if (lv != null && undeclaredVariables.Contains (lv)) {
lv.Type = null;
}
}
} else {
var lv = entry.Value as LocalVariable;
if (lv != null && undeclaredVariables.Contains (lv))
lv.Type = null;
}
}
}
public override void Emit (EmitContext ec)
{
if (Report.Errors > 0)
return;
try {
if (IsCompilerGenerated) {
using (ec.With (BuilderContext.Options.OmitDebugInfo, true)) {
base.Emit (ec);
}
} else {
base.Emit (ec);
}
//
// If `HasReturnLabel' is set, then we already emitted a
// jump to the end of the method, so we must emit a `ret'
// there.
//
// Unfortunately, System.Reflection.Emit automatically emits
// a leave to the end of a finally block. This is a problem
// if no code is following the try/finally block since we may
// jump to a point after the end of the method.
// As a workaround, we're always creating a return label in
// this case.
//
if (ec.HasReturnLabel || HasReachableClosingBrace) {
if (ec.HasReturnLabel)
ec.MarkLabel (ec.ReturnLabel);
if (ec.EmitAccurateDebugInfo && !IsCompilerGenerated)
ec.Mark (EndLocation);
if (ec.ReturnType.Kind != MemberKind.Void)
ec.Emit (OpCodes.Ldloc, ec.TemporaryReturn ());
ec.Emit (OpCodes.Ret);
}
} catch (Exception e) {
throw new InternalErrorException (e, StartLocation);
}
}
public bool Resolve (BlockContext bc, IMethodData md)
{
if (resolved)
return true;
var errors = bc.Report.Errors;
base.Resolve (bc);
if (bc.Report.Errors > errors)
return false;
MarkReachable (new Reachability ());
if (HasReachableClosingBrace && bc.ReturnType.Kind != MemberKind.Void) {
// TODO: var md = bc.CurrentMemberDefinition;
bc.Report.Error (161, md.Location, "`{0}': not all code paths return a value", md.GetSignatureForError ());
}
if ((flags & Flags.NoFlowAnalysis) != 0)
return true;
var fc = new FlowAnalysisContext (bc.Module.Compiler, this, bc.AssignmentInfoOffset);
try {
FlowAnalysis (fc);
} catch (Exception e) {
throw new InternalErrorException (e, StartLocation);
}
return true;
}
}
public class SwitchLabel : Statement
{
Constant converted;
Expression label;
Label? il_label;
//
// if expr == null, then it is the default case.
//
public SwitchLabel (Expression expr, Location l)
{
label = expr;
loc = l;
}
public bool IsDefault {
get {
return label == null;
}
}
public Expression Label {
get {
return label;
}
}
public Location Location {
get {
return loc;
}
}
public Constant Converted {
get {
return converted;
}
set {
converted = value;
}
}
public bool PatternMatching { get; set; }
public bool SectionStart { get; set; }
public Label GetILLabel (EmitContext ec)
{
if (il_label == null){
il_label = ec.DefineLabel ();
}
return il_label.Value;
}
protected override void DoEmit (EmitContext ec)
{
ec.MarkLabel (GetILLabel (ec));
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
if (!SectionStart)
return false;
fc.BranchDefiniteAssignment (fc.SwitchInitialDefinitiveAssignment);
return false;
}
public override bool Resolve (BlockContext bc)
{
if (ResolveAndReduce (bc))
bc.Switch.RegisterLabel (bc, this);
return true;
}
//
// Resolves the expression, reduces it to a literal if possible
// and then converts it to the requested type.
//
bool ResolveAndReduce (BlockContext bc)
{
if (IsDefault)
return true;
var switch_statement = bc.Switch;
if (PatternMatching) {
label = new Is (switch_statement.ExpressionValue, label, loc).Resolve (bc);
return label != null;
}
var c = label.ResolveLabelConstant (bc);
if (c == null)
return false;
if (switch_statement.IsNullable && c is NullLiteral) {
converted = c;
return true;
}
if (switch_statement.IsPatternMatching) {
label = new Is (switch_statement.ExpressionValue, label, loc).Resolve (bc);
return true;
}
converted = c.ImplicitConversionRequired (bc, switch_statement.SwitchType);
return converted != null;
}
public void Error_AlreadyOccurs (ResolveContext ec, SwitchLabel collision_with)
{
ec.Report.SymbolRelatedToPreviousError (collision_with.loc, null);
ec.Report.Error (152, loc, "The label `{0}' already occurs in this switch statement", GetSignatureForError ());
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
var t = (SwitchLabel) target;
if (label != null)
t.label = label.Clone (clonectx);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
public string GetSignatureForError ()
{
string label;
if (converted == null)
label = "default";
else
label = converted.GetValueAsLiteral ();
return string.Format ("case {0}:", label);
}
}
public class Switch : LoopStatement
{
// structure used to hold blocks of keys while calculating table switch
sealed class LabelsRange : IComparable
{
public readonly long min;
public long max;
public readonly List label_values;
public LabelsRange (long value)
{
min = max = value;
label_values = new List ();
label_values.Add (value);
}
public LabelsRange (long min, long max, ICollection values)
{
this.min = min;
this.max = max;
this.label_values = new List (values);
}
public long Range {
get {
return max - min + 1;
}
}
public bool AddValue (long value)
{
var gap = value - min + 1;
// Ensure the range has > 50% occupancy
if (gap > 2 * (label_values.Count + 1) || gap <= 0)
return false;
max = value;
label_values.Add (value);
return true;
}
public int CompareTo (LabelsRange other)
{
int nLength = label_values.Count;
int nLengthOther = other.label_values.Count;
if (nLengthOther == nLength)
return (int) (other.min - min);
return nLength - nLengthOther;
}
}
sealed class DispatchStatement : Statement
{
readonly Switch body;
public DispatchStatement (Switch body)
{
this.body = body;
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
throw new NotImplementedException ();
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
return false;
}
protected override void DoEmit (EmitContext ec)
{
body.EmitDispatch (ec);
}
}
class MissingBreak : Statement
{
readonly SwitchLabel label;
public MissingBreak (SwitchLabel sl)
{
this.label = sl;
this.loc = sl.loc;
}
public bool FallOut { get; set; }
protected override void DoEmit (EmitContext ec)
{
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
if (FallOut) {
fc.Report.Error (8070, loc, "Control cannot fall out of switch statement through final case label `{0}'",
label.GetSignatureForError ());
} else {
fc.Report.Error (163, loc, "Control cannot fall through from one case label `{0}' to another",
label.GetSignatureForError ());
}
return true;
}
}
public Expression Expr;
//
// Mapping of all labels to their SwitchLabels
//
Dictionary labels;
Dictionary string_labels;
List case_labels;
List> goto_cases;
List end_reachable_das;
///
/// The governing switch type
///
public TypeSpec SwitchType;
Expression new_expr;
SwitchLabel case_null;
SwitchLabel case_default;
Label defaultLabel, nullLabel;
VariableReference value;
ExpressionStatement string_dictionary;
FieldExpr switch_cache_field;
ExplicitBlock block;
bool end_reachable;
//
// Nullable Types support
//
Nullable.Unwrap unwrap;
public Switch (Expression e, ExplicitBlock block, Location l)
: base (block)
{
Expr = e;
this.block = block;
loc = l;
}
public SwitchLabel ActiveLabel { get; set; }
public ExplicitBlock Block {
get {
return block;
}
}
public SwitchLabel DefaultLabel {
get {
return case_default;
}
}
public bool IsNullable {
get {
return unwrap != null;
}
}
public bool IsPatternMatching {
get {
return new_expr == null && SwitchType != null;
}
}
public List RegisteredLabels {
get {
return case_labels;
}
}
public VariableReference ExpressionValue {
get {
return value;
}
}
//
// Determines the governing type for a switch. The returned
// expression might be the expression from the switch, or an
// expression that includes any potential conversions to
//
static Expression SwitchGoverningType (ResolveContext rc, Expression expr, bool unwrapExpr)
{
switch (expr.Type.BuiltinType) {
case BuiltinTypeSpec.Type.Byte:
case BuiltinTypeSpec.Type.SByte:
case BuiltinTypeSpec.Type.UShort:
case BuiltinTypeSpec.Type.Short:
case BuiltinTypeSpec.Type.UInt:
case BuiltinTypeSpec.Type.Int:
case BuiltinTypeSpec.Type.ULong:
case BuiltinTypeSpec.Type.Long:
case BuiltinTypeSpec.Type.Char:
case BuiltinTypeSpec.Type.String:
case BuiltinTypeSpec.Type.Bool:
return expr;
}
if (expr.Type.IsEnum)
return expr;
//
// Try to find a *user* defined implicit conversion.
//
// If there is no implicit conversion, or if there are multiple
// conversions, we have to report an error
//
Expression converted = null;
foreach (TypeSpec tt in rc.Module.PredefinedTypes.SwitchUserTypes) {
if (!unwrapExpr && tt.IsNullableType && expr.Type.IsNullableType)
break;
var restr = Convert.UserConversionRestriction.ImplicitOnly |
Convert.UserConversionRestriction.ProbingOnly;
if (unwrapExpr)
restr |= Convert.UserConversionRestriction.NullableSourceOnly;
var e = Convert.UserDefinedConversion (rc, expr, tt, restr, Location.Null);
if (e == null)
continue;
//
// Ignore over-worked ImplicitUserConversions that do
// an implicit conversion in addition to the user conversion.
//
var uc = e as UserCast;
if (uc == null)
continue;
if (converted != null){
// rc.Report.ExtraInformation (loc, "(Ambiguous implicit user defined conversion in previous ");
return null;
}
converted = e;
}
return converted;
}
public static TypeSpec[] CreateSwitchUserTypes (ModuleContainer module, TypeSpec nullable)
{
var types = module.Compiler.BuiltinTypes;
// LAMESPEC: For some reason it does not contain bool which looks like csc bug
TypeSpec[] stypes = new[] {
types.SByte,
types.Byte,
types.Short,
types.UShort,
types.Int,
types.UInt,
types.Long,
types.ULong,
types.Char,
types.String
};
if (nullable != null) {
Array.Resize (ref stypes, stypes.Length + 9);
for (int i = 0; i < 9; ++i) {
stypes [10 + i] = nullable.MakeGenericType (module, new [] { stypes [i] });
}
}
return stypes;
}
public void RegisterLabel (BlockContext rc, SwitchLabel sl)
{
case_labels.Add (sl);
if (sl.IsDefault) {
if (case_default != null) {
sl.Error_AlreadyOccurs (rc, case_default);
} else {
case_default = sl;
}
return;
}
if (sl.Converted == null)
return;
try {
if (string_labels != null) {
string string_value = sl.Converted.GetValue () as string;
if (string_value == null)
case_null = sl;
else
string_labels.Add (string_value, sl);
} else {
if (sl.Converted.IsNull) {
case_null = sl;
} else {
labels.Add (sl.Converted.GetValueAsLong (), sl);
}
}
} catch (ArgumentException) {
if (string_labels != null)
sl.Error_AlreadyOccurs (rc, string_labels[(string) sl.Converted.GetValue ()]);
else
sl.Error_AlreadyOccurs (rc, labels[sl.Converted.GetValueAsLong ()]);
}
}
//
// This method emits code for a lookup-based switch statement (non-string)
// Basically it groups the cases into blocks that are at least half full,
// and then spits out individual lookup opcodes for each block.
// It emits the longest blocks first, and short blocks are just
// handled with direct compares.
//
void EmitTableSwitch (EmitContext ec, Expression val)
{
if (labels != null && labels.Count > 0) {
List ranges;
if (string_labels != null) {
// We have done all hard work for string already
// setup single range only
ranges = new List (1);
ranges.Add (new LabelsRange (0, labels.Count - 1, labels.Keys));
} else {
var element_keys = new long[labels.Count];
labels.Keys.CopyTo (element_keys, 0);
Array.Sort (element_keys);
//
// Build possible ranges of switch labes to reduce number
// of comparisons
//
ranges = new List (element_keys.Length);
var range = new LabelsRange (element_keys[0]);
ranges.Add (range);
for (int i = 1; i < element_keys.Length; ++i) {
var l = element_keys[i];
if (range.AddValue (l))
continue;
range = new LabelsRange (l);
ranges.Add (range);
}
// sort the blocks so we can tackle the largest ones first
ranges.Sort ();
}
Label lbl_default = defaultLabel;
TypeSpec compare_type = SwitchType.IsEnum ? EnumSpec.GetUnderlyingType (SwitchType) : SwitchType;
for (int range_index = ranges.Count - 1; range_index >= 0; --range_index) {
LabelsRange kb = ranges[range_index];
lbl_default = (range_index == 0) ? defaultLabel : ec.DefineLabel ();
// Optimize small ranges using simple equality check
if (kb.Range <= 2) {
foreach (var key in kb.label_values) {
SwitchLabel sl = labels[key];
if (sl == case_default || sl == case_null)
continue;
if (sl.Converted.IsZeroInteger) {
val.EmitBranchable (ec, sl.GetILLabel (ec), false);
} else {
val.Emit (ec);
sl.Converted.Emit (ec);
ec.Emit (OpCodes.Beq, sl.GetILLabel (ec));
}
}
} else {
// TODO: if all the keys in the block are the same and there are
// no gaps/defaults then just use a range-check.
if (compare_type.BuiltinType == BuiltinTypeSpec.Type.Long || compare_type.BuiltinType == BuiltinTypeSpec.Type.ULong) {
// TODO: optimize constant/I4 cases
// check block range (could be > 2^31)
val.Emit (ec);
ec.EmitLong (kb.min);
ec.Emit (OpCodes.Blt, lbl_default);
val.Emit (ec);
ec.EmitLong (kb.max);
ec.Emit (OpCodes.Bgt, lbl_default);
// normalize range
val.Emit (ec);
if (kb.min != 0) {
ec.EmitLong (kb.min);
ec.Emit (OpCodes.Sub);
}
ec.Emit (OpCodes.Conv_I4); // assumes < 2^31 labels!
} else {
// normalize range
val.Emit (ec);
int first = (int) kb.min;
if (first > 0) {
ec.EmitInt (first);
ec.Emit (OpCodes.Sub);
} else if (first < 0) {
ec.EmitInt (-first);
ec.Emit (OpCodes.Add);
}
}
// first, build the list of labels for the switch
int iKey = 0;
long cJumps = kb.Range;
Label[] switch_labels = new Label[cJumps];
for (int iJump = 0; iJump < cJumps; iJump++) {
var key = kb.label_values[iKey];
if (key == kb.min + iJump) {
switch_labels[iJump] = labels[key].GetILLabel (ec);
iKey++;
} else {
switch_labels[iJump] = lbl_default;
}
}
// emit the switch opcode
ec.Emit (OpCodes.Switch, switch_labels);
}
// mark the default for this block
if (range_index != 0)
ec.MarkLabel (lbl_default);
}
// the last default just goes to the end
if (ranges.Count > 0)
ec.Emit (OpCodes.Br, lbl_default);
}
}
public SwitchLabel FindLabel (Constant value)
{
SwitchLabel sl = null;
if (string_labels != null) {
string s = value.GetValue () as string;
if (s == null) {
if (case_null != null)
sl = case_null;
else if (case_default != null)
sl = case_default;
} else {
string_labels.TryGetValue (s, out sl);
}
} else {
if (value is NullLiteral) {
sl = case_null;
} else {
labels.TryGetValue (value.GetValueAsLong (), out sl);
}
}
if (sl == null || sl.SectionStart)
return sl;
//
// Always return section start, it simplifies handling of switch labels
//
for (int idx = case_labels.IndexOf (sl); ; --idx) {
var cs = case_labels [idx];
if (cs.SectionStart)
return cs;
}
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
Expr.FlowAnalysis (fc);
var prev_switch = fc.SwitchInitialDefinitiveAssignment;
var InitialDefinitiveAssignment = fc.DefiniteAssignment;
fc.SwitchInitialDefinitiveAssignment = InitialDefinitiveAssignment;
block.FlowAnalysis (fc);
fc.SwitchInitialDefinitiveAssignment = prev_switch;
if (end_reachable_das != null) {
var sections_das = DefiniteAssignmentBitSet.And (end_reachable_das);
InitialDefinitiveAssignment |= sections_das;
end_reachable_das = null;
}
fc.DefiniteAssignment = InitialDefinitiveAssignment;
return case_default != null && !end_reachable;
}
public override bool Resolve (BlockContext ec)
{
Expr = Expr.Resolve (ec);
if (Expr == null)
return false;
//
// LAMESPEC: User conversion from non-nullable governing type has a priority
//
new_expr = SwitchGoverningType (ec, Expr, false);
if (new_expr == null) {
if (Expr.Type.IsNullableType) {
unwrap = Nullable.Unwrap.Create (Expr, false);
if (unwrap == null)
return false;
//
// Unwrap + user conversion using non-nullable type is not allowed but user operator
// involving nullable Expr and nullable governing type is
//
new_expr = SwitchGoverningType (ec, unwrap, true);
}
}
Expression switch_expr;
if (new_expr == null) {
if (ec.Module.Compiler.Settings.Version != LanguageVersion.Experimental) {
if (Expr.Type != InternalType.ErrorType) {
ec.Report.Error (151, loc,
"A switch expression of type `{0}' cannot be converted to an integral type, bool, char, string, enum or nullable type",
Expr.Type.GetSignatureForError ());
}
return false;
}
switch_expr = Expr;
SwitchType = Expr.Type;
} else {
switch_expr = new_expr;
SwitchType = new_expr.Type;
if (SwitchType.IsNullableType) {
new_expr = unwrap = Nullable.Unwrap.Create (new_expr, true);
SwitchType = Nullable.NullableInfo.GetUnderlyingType (SwitchType);
}
if (SwitchType.BuiltinType == BuiltinTypeSpec.Type.Bool && ec.Module.Compiler.Settings.Version == LanguageVersion.ISO_1) {
ec.Report.FeatureIsNotAvailable (ec.Module.Compiler, loc, "switch expression of boolean type");
return false;
}
if (block.Statements.Count == 0)
return true;
if (SwitchType.BuiltinType == BuiltinTypeSpec.Type.String) {
string_labels = new Dictionary ();
} else {
labels = new Dictionary ();
}
}
var constant = switch_expr as Constant;
//
// Don't need extra variable for constant switch or switch with
// only default case
//
if (constant == null) {
//
// Store switch expression for comparison purposes
//
value = switch_expr as VariableReference;
if (value == null && !HasOnlyDefaultSection ()) {
var current_block = ec.CurrentBlock;
ec.CurrentBlock = Block;
// Create temporary variable inside switch scope
value = TemporaryVariableReference.Create (SwitchType, ec.CurrentBlock, loc);
value.Resolve (ec);
ec.CurrentBlock = current_block;
}
}
case_labels = new List ();
Switch old_switch = ec.Switch;
ec.Switch = this;
var parent_los = ec.EnclosingLoopOrSwitch;
ec.EnclosingLoopOrSwitch = this;
var ok = Statement.Resolve (ec);
ec.EnclosingLoopOrSwitch = parent_los;
ec.Switch = old_switch;
//
// Check if all goto cases are valid. Needs to be done after switch
// is resolved because goto can jump forward in the scope.
//
if (goto_cases != null) {
foreach (var gc in goto_cases) {
if (gc.Item1 == null) {
if (DefaultLabel == null) {
Goto.Error_UnknownLabel (ec, "default", loc);
}
continue;
}
var sl = FindLabel (gc.Item2);
if (sl == null) {
Goto.Error_UnknownLabel (ec, "case " + gc.Item2.GetValueAsLiteral (), loc);
} else {
gc.Item1.Label = sl;
}
}
}
if (!ok)
return false;
if (constant == null && SwitchType.BuiltinType == BuiltinTypeSpec.Type.String && string_labels.Count > 6) {
ResolveStringSwitchMap (ec);
}
//
// Anonymous storey initialization has to happen before
// any generated switch dispatch
//
block.InsertStatement (0, new DispatchStatement (this));
return true;
}
bool HasOnlyDefaultSection ()
{
for (int i = 0; i < block.Statements.Count; ++i) {
var s = block.Statements[i] as SwitchLabel;
if (s == null || s.IsDefault)
continue;
return false;
}
return true;
}
public override Reachability MarkReachable (Reachability rc)
{
if (rc.IsUnreachable)
return rc;
base.MarkReachable (rc);
block.MarkReachableScope (rc);
if (block.Statements.Count == 0)
return rc;
SwitchLabel constant_label = null;
var constant = new_expr as Constant;
if (constant != null) {
constant_label = FindLabel (constant) ?? case_default;
if (constant_label == null) {
block.Statements.RemoveAt (0);
return rc;
}
}
var section_rc = new Reachability ();
SwitchLabel prev_label = null;
for (int i = 0; i < block.Statements.Count; ++i) {
var s = block.Statements[i];
var sl = s as SwitchLabel;
if (sl != null && sl.SectionStart) {
//
// Section is marked already via goto case
//
if (!sl.IsUnreachable) {
section_rc = new Reachability ();
continue;
}
if (section_rc.IsUnreachable) {
//
// Common case. Previous label section end is unreachable as
// it ends with break, return, etc. For next section revert
// to reachable again unless we have constant switch block
//
section_rc = constant_label != null && constant_label != sl ?
Reachability.CreateUnreachable () :
new Reachability ();
} else if (prev_label != null) {
//
// Error case as control cannot fall through from one case label
//
sl.SectionStart = false;
s = new MissingBreak (prev_label);
s.MarkReachable (rc);
block.Statements.Insert (i - 1, s);
++i;
} else if (constant_label != null && constant_label != sl) {
//
// Special case for the first unreachable label in constant
// switch block
//
section_rc = Reachability.CreateUnreachable ();
}
prev_label = sl;
}
section_rc = s.MarkReachable (section_rc);
}
if (!section_rc.IsUnreachable && prev_label != null) {
prev_label.SectionStart = false;
var s = new MissingBreak (prev_label) {
FallOut = true
};
s.MarkReachable (rc);
block.Statements.Add (s);
}
//
// Reachability can affect parent only when all possible paths are handled but
// we still need to run reachability check on switch body to check for fall-through
//
if (case_default == null && constant_label == null)
return rc;
//
// We have at least one local exit from the switch
//
if (end_reachable)
return rc;
return Reachability.CreateUnreachable ();
}
public void RegisterGotoCase (GotoCase gotoCase, Constant value)
{
if (goto_cases == null)
goto_cases = new List> ();
goto_cases.Add (Tuple.Create (gotoCase, value));
}
//
// Converts string switch into string hashtable
//
void ResolveStringSwitchMap (ResolveContext ec)
{
FullNamedExpression string_dictionary_type;
if (ec.Module.PredefinedTypes.Dictionary.Define ()) {
string_dictionary_type = new TypeExpression (
ec.Module.PredefinedTypes.Dictionary.TypeSpec.MakeGenericType (ec,
new [] { ec.BuiltinTypes.String, ec.BuiltinTypes.Int }),
loc);
} else if (ec.Module.PredefinedTypes.Hashtable.Define ()) {
string_dictionary_type = new TypeExpression (ec.Module.PredefinedTypes.Hashtable.TypeSpec, loc);
} else {
ec.Module.PredefinedTypes.Dictionary.Resolve ();
return;
}
var ctype = ec.CurrentMemberDefinition.Parent.PartialContainer;
Field field = new Field (ctype, string_dictionary_type,
Modifiers.STATIC | Modifiers.PRIVATE | Modifiers.COMPILER_GENERATED,
new MemberName (CompilerGeneratedContainer.MakeName (null, "f", "switch$map", ec.Module.CounterSwitchTypes++), loc), null);
if (!field.Define ())
return;
ctype.AddField (field);
var init = new List ();
int counter = -1;
labels = new Dictionary (string_labels.Count);
string value = null;
foreach (SwitchLabel sl in case_labels) {
if (sl.SectionStart)
labels.Add (++counter, sl);
if (sl == case_default || sl == case_null)
continue;
value = (string) sl.Converted.GetValue ();
var init_args = new List (2);
init_args.Add (new StringLiteral (ec.BuiltinTypes, value, sl.Location));
sl.Converted = new IntConstant (ec.BuiltinTypes, counter, loc);
init_args.Add (sl.Converted);
init.Add (new CollectionElementInitializer (init_args, loc));
}
Arguments args = new Arguments (1);
args.Add (new Argument (new IntConstant (ec.BuiltinTypes, init.Count, loc)));
Expression initializer = new NewInitialize (string_dictionary_type, args,
new CollectionOrObjectInitializers (init, loc), loc);
switch_cache_field = new FieldExpr (field, loc);
string_dictionary = new SimpleAssign (switch_cache_field, initializer.Resolve (ec));
}
void DoEmitStringSwitch (EmitContext ec)
{
Label l_initialized = ec.DefineLabel ();
//
// Skip initialization when value is null
//
value.EmitBranchable (ec, nullLabel, false);
//
// Check if string dictionary is initialized and initialize
//
switch_cache_field.EmitBranchable (ec, l_initialized, true);
using (ec.With (BuilderContext.Options.OmitDebugInfo, true)) {
string_dictionary.EmitStatement (ec);
}
ec.MarkLabel (l_initialized);
LocalTemporary string_switch_variable = new LocalTemporary (ec.BuiltinTypes.Int);
ResolveContext rc = new ResolveContext (ec.MemberContext);
if (switch_cache_field.Type.IsGeneric) {
Arguments get_value_args = new Arguments (2);
get_value_args.Add (new Argument (value));
get_value_args.Add (new Argument (string_switch_variable, Argument.AType.Out));
Expression get_item = new Invocation (new MemberAccess (switch_cache_field, "TryGetValue", loc), get_value_args).Resolve (rc);
if (get_item == null)
return;
//
// A value was not found, go to default case
//
get_item.EmitBranchable (ec, defaultLabel, false);
} else {
Arguments get_value_args = new Arguments (1);
get_value_args.Add (new Argument (value));
Expression get_item = new ElementAccess (switch_cache_field, get_value_args, loc).Resolve (rc);
if (get_item == null)
return;
LocalTemporary get_item_object = new LocalTemporary (ec.BuiltinTypes.Object);
get_item_object.EmitAssign (ec, get_item, true, false);
ec.Emit (OpCodes.Brfalse, defaultLabel);
ExpressionStatement get_item_int = (ExpressionStatement) new SimpleAssign (string_switch_variable,
new Cast (new TypeExpression (ec.BuiltinTypes.Int, loc), get_item_object, loc)).Resolve (rc);
get_item_int.EmitStatement (ec);
get_item_object.Release (ec);
}
EmitTableSwitch (ec, string_switch_variable);
string_switch_variable.Release (ec);
}
//
// Emits switch using simple if/else comparison for small label count (4 + optional default)
//
void EmitShortSwitch (EmitContext ec)
{
MethodSpec equal_method = null;
if (SwitchType.BuiltinType == BuiltinTypeSpec.Type.String) {
equal_method = ec.Module.PredefinedMembers.StringEqual.Resolve (loc);
}
if (equal_method != null) {
value.EmitBranchable (ec, nullLabel, false);
}
for (int i = 0; i < case_labels.Count; ++i) {
var label = case_labels [i];
if (label == case_default || label == case_null)
continue;
var constant = label.Converted;
if (constant == null) {
label.Label.EmitBranchable (ec, label.GetILLabel (ec), true);
continue;
}
if (equal_method != null) {
value.Emit (ec);
constant.Emit (ec);
var call = new CallEmitter ();
call.EmitPredefined (ec, equal_method, new Arguments (0));
ec.Emit (OpCodes.Brtrue, label.GetILLabel (ec));
continue;
}
if (constant.IsZeroInteger && constant.Type.BuiltinType != BuiltinTypeSpec.Type.Long && constant.Type.BuiltinType != BuiltinTypeSpec.Type.ULong) {
value.EmitBranchable (ec, label.GetILLabel (ec), false);
continue;
}
value.Emit (ec);
constant.Emit (ec);
ec.Emit (OpCodes.Beq, label.GetILLabel (ec));
}
ec.Emit (OpCodes.Br, defaultLabel);
}
void EmitDispatch (EmitContext ec)
{
if (IsPatternMatching) {
EmitShortSwitch (ec);
return;
}
if (value == null) {
//
// Constant switch, we've already done the work if there is only 1 label
// referenced
//
int reachable = 0;
foreach (var sl in case_labels) {
if (sl.IsUnreachable)
continue;
if (reachable++ > 0) {
var constant = (Constant) new_expr;
var constant_label = FindLabel (constant) ?? case_default;
ec.Emit (OpCodes.Br, constant_label.GetILLabel (ec));
break;
}
}
return;
}
if (string_dictionary != null) {
DoEmitStringSwitch (ec);
} else if (case_labels.Count < 4 || string_labels != null) {
EmitShortSwitch (ec);
} else {
EmitTableSwitch (ec, value);
}
}
protected override void DoEmit (EmitContext ec)
{
//
// Setup the codegen context
//
Label old_end = ec.LoopEnd;
Switch old_switch = ec.Switch;
ec.LoopEnd = ec.DefineLabel ();
ec.Switch = this;
defaultLabel = case_default == null ? ec.LoopEnd : case_default.GetILLabel (ec);
nullLabel = case_null == null ? defaultLabel : case_null.GetILLabel (ec);
if (value != null) {
ec.Mark (loc);
var switch_expr = new_expr ?? Expr;
if (IsNullable) {
unwrap.EmitCheck (ec);
ec.Emit (OpCodes.Brfalse, nullLabel);
value.EmitAssign (ec, switch_expr, false, false);
} else if (switch_expr != value) {
value.EmitAssign (ec, switch_expr, false, false);
}
//
// Next statement is compiler generated we don't need extra
// nop when we can use the statement for sequence point
//
ec.Mark (block.StartLocation);
block.IsCompilerGenerated = true;
} else {
new_expr.EmitSideEffect (ec);
}
block.Emit (ec);
// Restore context state.
ec.MarkLabel (ec.LoopEnd);
//
// Restore the previous context
//
ec.LoopEnd = old_end;
ec.Switch = old_switch;
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Switch target = (Switch) t;
target.Expr = Expr.Clone (clonectx);
target.Statement = target.block = (ExplicitBlock) block.Clone (clonectx);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
public override void AddEndDefiniteAssignment (FlowAnalysisContext fc)
{
if (case_default == null && !(new_expr is Constant))
return;
if (end_reachable_das == null)
end_reachable_das = new List ();
end_reachable_das.Add (fc.DefiniteAssignment);
}
public override void SetEndReachable ()
{
end_reachable = true;
}
}
// A place where execution can restart in a state machine
public abstract class ResumableStatement : Statement
{
bool prepared;
protected Label resume_point;
public Label PrepareForEmit (EmitContext ec)
{
if (!prepared) {
prepared = true;
resume_point = ec.DefineLabel ();
}
return resume_point;
}
public virtual Label PrepareForDispose (EmitContext ec, Label end)
{
return end;
}
public virtual void EmitForDispose (EmitContext ec, LocalBuilder pc, Label end, bool have_dispatcher)
{
}
}
public abstract class TryFinallyBlock : ExceptionStatement
{
protected Statement stmt;
Label dispose_try_block;
bool prepared_for_dispose, emitted_dispose;
Method finally_host;
protected TryFinallyBlock (Statement stmt, Location loc)
: base (loc)
{
this.stmt = stmt;
}
#region Properties
public Statement Statement {
get {
return stmt;
}
}
#endregion
protected abstract void EmitTryBody (EmitContext ec);
public abstract void EmitFinallyBody (EmitContext ec);
public override Label PrepareForDispose (EmitContext ec, Label end)
{
if (!prepared_for_dispose) {
prepared_for_dispose = true;
dispose_try_block = ec.DefineLabel ();
}
return dispose_try_block;
}
protected sealed override void DoEmit (EmitContext ec)
{
EmitTryBodyPrepare (ec);
EmitTryBody (ec);
bool beginFinally = EmitBeginFinallyBlock (ec);
Label start_finally = ec.DefineLabel ();
if (resume_points != null && beginFinally) {
var state_machine = (StateMachineInitializer) ec.CurrentAnonymousMethod;
ec.Emit (OpCodes.Ldloc, state_machine.SkipFinally);
ec.Emit (OpCodes.Brfalse_S, start_finally);
ec.Emit (OpCodes.Endfinally);
}
ec.MarkLabel (start_finally);
if (finally_host != null) {
finally_host.Define ();
finally_host.PrepareEmit ();
finally_host.Emit ();
// Now it's safe to add, to close it properly and emit sequence points
finally_host.Parent.AddMember (finally_host);
var ce = new CallEmitter ();
ce.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
ce.EmitPredefined (ec, finally_host.Spec, new Arguments (0), true);
} else {
EmitFinallyBody (ec);
}
if (beginFinally)
ec.EndExceptionBlock ();
}
public override void EmitForDispose (EmitContext ec, LocalBuilder pc, Label end, bool have_dispatcher)
{
if (emitted_dispose)
return;
emitted_dispose = true;
Label end_of_try = ec.DefineLabel ();
// Ensure that the only way we can get into this code is through a dispatcher
if (have_dispatcher)
ec.Emit (OpCodes.Br, end);
ec.BeginExceptionBlock ();
ec.MarkLabel (dispose_try_block);
Label[] labels = null;
for (int i = 0; i < resume_points.Count; ++i) {
ResumableStatement s = resume_points[i];
Label ret = s.PrepareForDispose (ec, end_of_try);
if (ret.Equals (end_of_try) && labels == null)
continue;
if (labels == null) {
labels = new Label[resume_points.Count];
for (int j = 0; j < i; ++j)
labels[j] = end_of_try;
}
labels[i] = ret;
}
if (labels != null) {
int j;
for (j = 1; j < labels.Length; ++j)
if (!labels[0].Equals (labels[j]))
break;
bool emit_dispatcher = j < labels.Length;
if (emit_dispatcher) {
ec.Emit (OpCodes.Ldloc, pc);
ec.EmitInt (first_resume_pc);
ec.Emit (OpCodes.Sub);
ec.Emit (OpCodes.Switch, labels);
}
foreach (ResumableStatement s in resume_points)
s.EmitForDispose (ec, pc, end_of_try, emit_dispatcher);
}
ec.MarkLabel (end_of_try);
ec.BeginFinallyBlock ();
if (finally_host != null) {
var ce = new CallEmitter ();
ce.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
ce.EmitPredefined (ec, finally_host.Spec, new Arguments (0), true);
} else {
EmitFinallyBody (ec);
}
ec.EndExceptionBlock ();
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
var res = stmt.FlowAnalysis (fc);
parent_try_block = null;
return res;
}
protected virtual bool EmitBeginFinallyBlock (EmitContext ec)
{
ec.BeginFinallyBlock ();
return true;
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
return Statement.MarkReachable (rc);
}
public override bool Resolve (BlockContext bc)
{
bool ok;
parent_try_block = bc.CurrentTryBlock;
bc.CurrentTryBlock = this;
if (stmt is TryCatch) {
ok = stmt.Resolve (bc);
} else {
using (bc.Set (ResolveContext.Options.TryScope)) {
ok = stmt.Resolve (bc);
}
}
bc.CurrentTryBlock = parent_try_block;
//
// Finally block inside iterator is called from MoveNext and
// Dispose methods that means we need to lift the block into
// newly created host method to emit the body only once. The
// original block then simply calls the newly generated method.
//
if (bc.CurrentIterator != null && !bc.IsInProbingMode) {
var b = stmt as Block;
if (b != null && b.Explicit.HasYield) {
finally_host = bc.CurrentIterator.CreateFinallyHost (this);
}
}
return base.Resolve (bc) && ok;
}
}
//
// Base class for blocks using exception handling
//
public abstract class ExceptionStatement : ResumableStatement
{
protected List resume_points;
protected int first_resume_pc;
protected ExceptionStatement parent_try_block;
protected int first_catch_resume_pc = -1;
protected ExceptionStatement (Location loc)
{
this.loc = loc;
}
protected virtual void EmitTryBodyPrepare (EmitContext ec)
{
StateMachineInitializer state_machine = null;
if (resume_points != null) {
state_machine = (StateMachineInitializer) ec.CurrentAnonymousMethod;
ec.EmitInt ((int) IteratorStorey.State.Running);
ec.Emit (OpCodes.Stloc, state_machine.CurrentPC);
}
//
// The resume points in catch section when this is try-catch-finally
//
if (IsRewrittenTryCatchFinally ()) {
ec.BeginExceptionBlock ();
if (first_catch_resume_pc >= 0) {
ec.MarkLabel (resume_point);
// For normal control flow, we want to fall-through the Switch
// So, we use CurrentPC rather than the $PC field, and initialize it to an outside value above
ec.Emit (OpCodes.Ldloc, state_machine.CurrentPC);
ec.EmitInt (first_resume_pc + first_catch_resume_pc);
ec.Emit (OpCodes.Sub);
var labels = new Label [resume_points.Count - first_catch_resume_pc];
for (int i = 0; i < labels.Length; ++i)
labels [i] = resume_points [i + first_catch_resume_pc].PrepareForEmit (ec);
ec.Emit (OpCodes.Switch, labels);
}
}
ec.BeginExceptionBlock ();
//
// The resume points for try section
//
if (resume_points != null && first_catch_resume_pc != 0) {
if (first_catch_resume_pc < 0)
ec.MarkLabel (resume_point);
// For normal control flow, we want to fall-through the Switch
// So, we use CurrentPC rather than the $PC field, and initialize it to an outside value above
ec.Emit (OpCodes.Ldloc, state_machine.CurrentPC);
ec.EmitInt (first_resume_pc);
ec.Emit (OpCodes.Sub);
var labels = new Label[resume_points.Count - System.Math.Max (first_catch_resume_pc, 0)];
for (int i = 0; i < labels.Length; ++i)
labels[i] = resume_points[i].PrepareForEmit (ec);
ec.Emit (OpCodes.Switch, labels);
}
}
bool IsRewrittenTryCatchFinally ()
{
var tf = this as TryFinally;
if (tf == null)
return false;
var tc = tf.Statement as TryCatch;
if (tc == null)
return false;
return tf.FinallyBlock.HasAwait || tc.HasClauseWithAwait;
}
public int AddResumePoint (ResumableStatement stmt, int pc, StateMachineInitializer stateMachine, TryCatch catchBlock)
{
if (parent_try_block != null) {
pc = parent_try_block.AddResumePoint (this, pc, stateMachine, catchBlock);
} else {
pc = stateMachine.AddResumePoint (this);
}
if (resume_points == null) {
resume_points = new List ();
first_resume_pc = pc;
}
if (pc != first_resume_pc + resume_points.Count)
throw new InternalErrorException ("missed an intervening AddResumePoint?");
var tf = this as TryFinally;
if (tf != null && tf.Statement == catchBlock && first_catch_resume_pc < 0) {
first_catch_resume_pc = resume_points.Count;
}
resume_points.Add (stmt);
return pc;
}
}
public class Lock : TryFinallyBlock
{
Expression expr;
TemporaryVariableReference expr_copy;
TemporaryVariableReference lock_taken;
public Lock (Expression expr, Statement stmt, Location loc)
: base (stmt, loc)
{
this.expr = expr;
}
public Expression Expr {
get {
return this.expr;
}
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
expr.FlowAnalysis (fc);
return base.DoFlowAnalysis (fc);
}
public override bool Resolve (BlockContext ec)
{
expr = expr.Resolve (ec);
if (expr == null)
return false;
if (!TypeSpec.IsReferenceType (expr.Type) && expr.Type != InternalType.ErrorType) {
ec.Report.Error (185, loc,
"`{0}' is not a reference type as required by the lock statement",
expr.Type.GetSignatureForError ());
}
if (expr.Type.IsGenericParameter) {
expr = Convert.ImplicitTypeParameterConversion (expr, (TypeParameterSpec)expr.Type, ec.BuiltinTypes.Object);
}
VariableReference lv = expr as VariableReference;
bool locked;
if (lv != null) {
locked = lv.IsLockedByStatement;
lv.IsLockedByStatement = true;
} else {
lv = null;
locked = false;
}
//
// Have to keep original lock value around to unlock same location
// in the case of original value has changed or is null
//
expr_copy = TemporaryVariableReference.Create (ec.BuiltinTypes.Object, ec.CurrentBlock, loc);
expr_copy.Resolve (ec);
//
// Ensure Monitor methods are available
//
if (ResolvePredefinedMethods (ec) > 1) {
lock_taken = TemporaryVariableReference.Create (ec.BuiltinTypes.Bool, ec.CurrentBlock, loc);
lock_taken.Resolve (ec);
}
using (ec.Set (ResolveContext.Options.LockScope)) {
base.Resolve (ec);
}
if (lv != null) {
lv.IsLockedByStatement = locked;
}
return true;
}
protected override void EmitTryBodyPrepare (EmitContext ec)
{
expr_copy.EmitAssign (ec, expr);
if (lock_taken != null) {
//
// Initialize ref variable
//
lock_taken.EmitAssign (ec, new BoolLiteral (ec.BuiltinTypes, false, loc));
} else {
//
// Monitor.Enter (expr_copy)
//
expr_copy.Emit (ec);
ec.Emit (OpCodes.Call, ec.Module.PredefinedMembers.MonitorEnter.Get ());
}
base.EmitTryBodyPrepare (ec);
}
protected override void EmitTryBody (EmitContext ec)
{
//
// Monitor.Enter (expr_copy, ref lock_taken)
//
if (lock_taken != null) {
expr_copy.Emit (ec);
lock_taken.LocalInfo.CreateBuilder (ec);
lock_taken.AddressOf (ec, AddressOp.Load);
ec.Emit (OpCodes.Call, ec.Module.PredefinedMembers.MonitorEnter_v4.Get ());
}
Statement.Emit (ec);
}
public override void EmitFinallyBody (EmitContext ec)
{
//
// if (lock_taken) Monitor.Exit (expr_copy)
//
Label skip = ec.DefineLabel ();
if (lock_taken != null) {
lock_taken.Emit (ec);
ec.Emit (OpCodes.Brfalse_S, skip);
}
expr_copy.Emit (ec);
var m = ec.Module.PredefinedMembers.MonitorExit.Resolve (loc);
if (m != null)
ec.Emit (OpCodes.Call, m);
ec.MarkLabel (skip);
}
int ResolvePredefinedMethods (ResolveContext rc)
{
// Try 4.0 Monitor.Enter (object, ref bool) overload first
var m = rc.Module.PredefinedMembers.MonitorEnter_v4.Get ();
if (m != null)
return 4;
m = rc.Module.PredefinedMembers.MonitorEnter.Get ();
if (m != null)
return 1;
rc.Module.PredefinedMembers.MonitorEnter_v4.Resolve (loc);
return 0;
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Lock target = (Lock) t;
target.expr = expr.Clone (clonectx);
target.stmt = Statement.Clone (clonectx);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public class Unchecked : Statement {
public Block Block;
public Unchecked (Block b, Location loc)
{
Block = b;
b.Unchecked = true;
this.loc = loc;
}
public override bool Resolve (BlockContext ec)
{
using (ec.With (ResolveContext.Options.AllCheckStateFlags, false))
return Block.Resolve (ec);
}
protected override void DoEmit (EmitContext ec)
{
using (ec.With (EmitContext.Options.CheckedScope, false))
Block.Emit (ec);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
return Block.FlowAnalysis (fc);
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
return Block.MarkReachable (rc);
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Unchecked target = (Unchecked) t;
target.Block = clonectx.LookupBlock (Block);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public class Checked : Statement {
public Block Block;
public Checked (Block b, Location loc)
{
Block = b;
b.Unchecked = false;
this.loc = loc;
}
public override bool Resolve (BlockContext ec)
{
using (ec.With (ResolveContext.Options.AllCheckStateFlags, true))
return Block.Resolve (ec);
}
protected override void DoEmit (EmitContext ec)
{
using (ec.With (EmitContext.Options.CheckedScope, true))
Block.Emit (ec);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
return Block.FlowAnalysis (fc);
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
return Block.MarkReachable (rc);
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Checked target = (Checked) t;
target.Block = clonectx.LookupBlock (Block);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public class Unsafe : Statement {
public Block Block;
public Unsafe (Block b, Location loc)
{
Block = b;
Block.Unsafe = true;
this.loc = loc;
}
public override bool Resolve (BlockContext ec)
{
if (ec.CurrentIterator != null)
ec.Report.Error (1629, loc, "Unsafe code may not appear in iterators");
using (ec.Set (ResolveContext.Options.UnsafeScope))
return Block.Resolve (ec);
}
protected override void DoEmit (EmitContext ec)
{
Block.Emit (ec);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
return Block.FlowAnalysis (fc);
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
return Block.MarkReachable (rc);
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Unsafe target = (Unsafe) t;
target.Block = clonectx.LookupBlock (Block);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
//
// Fixed statement
//
public class Fixed : Statement
{
abstract class Emitter : ShimExpression
{
protected LocalVariable vi;
protected Emitter (Expression expr, LocalVariable li)
: base (expr)
{
vi = li;
}
public abstract void EmitExit (EmitContext ec);
public override void FlowAnalysis (FlowAnalysisContext fc)
{
expr.FlowAnalysis (fc);
}
}
sealed class ExpressionEmitter : Emitter {
public ExpressionEmitter (Expression converted, LocalVariable li)
: base (converted, li)
{
}
protected override Expression DoResolve (ResolveContext rc)
{
throw new NotImplementedException ();
}
public override void Emit (EmitContext ec) {
//
// Store pointer in pinned location
//
expr.Emit (ec);
vi.EmitAssign (ec);
}
public override void EmitExit (EmitContext ec)
{
ec.EmitInt (0);
ec.Emit (OpCodes.Conv_U);
vi.EmitAssign (ec);
}
}
class StringEmitter : Emitter
{
LocalVariable pinned_string;
public StringEmitter (Expression expr, LocalVariable li)
: base (expr, li)
{
}
protected override Expression DoResolve (ResolveContext rc)
{
pinned_string = new LocalVariable (vi.Block, "$pinned",
LocalVariable.Flags.FixedVariable | LocalVariable.Flags.CompilerGenerated | LocalVariable.Flags.Used,
vi.Location);
pinned_string.Type = rc.BuiltinTypes.String;
vi.IsFixed = false;
eclass = ExprClass.Variable;
type = rc.BuiltinTypes.Int;
return this;
}
public override void Emit (EmitContext ec)
{
pinned_string.CreateBuilder (ec);
expr.Emit (ec);
pinned_string.EmitAssign (ec);
// TODO: Should use Binary::Add
pinned_string.Emit (ec);
ec.Emit (OpCodes.Conv_I);
var m = ec.Module.PredefinedMembers.RuntimeHelpersOffsetToStringData.Resolve (loc);
if (m == null)
return;
PropertyExpr pe = new PropertyExpr (m, pinned_string.Location);
//pe.InstanceExpression = pinned_string;
pe.Resolve (new ResolveContext (ec.MemberContext)).Emit (ec);
ec.Emit (OpCodes.Add);
vi.EmitAssign (ec);
}
public override void EmitExit (EmitContext ec)
{
ec.EmitNull ();
pinned_string.EmitAssign (ec);
}
}
public class VariableDeclaration : BlockVariable
{
public VariableDeclaration (FullNamedExpression type, LocalVariable li)
: base (type, li)
{
}
protected override Expression ResolveInitializer (BlockContext bc, LocalVariable li, Expression initializer)
{
if (!Variable.Type.IsPointer && li == Variable) {
bc.Report.Error (209, TypeExpression.Location,
"The type of locals declared in a fixed statement must be a pointer type");
return null;
}
var res = initializer.Resolve (bc);
if (res == null)
return null;
//
// Case 1: Array
//
var ac = res.Type as ArrayContainer;
if (ac != null) {
TypeSpec array_type = ac.Element;
//
// Provided that array_type is unmanaged,
//
if (!TypeManager.VerifyUnmanaged (bc.Module, array_type, loc))
return null;
Expression res_init;
if (ExpressionAnalyzer.IsInexpensiveLoad (res)) {
res_init = res;
} else {
var expr_variable = LocalVariable.CreateCompilerGenerated (ac, bc.CurrentBlock, loc);
res_init = new CompilerAssign (expr_variable.CreateReferenceExpression (bc, loc), res, loc);
res = expr_variable.CreateReferenceExpression (bc, loc);
}
//
// and T* is implicitly convertible to the
// pointer type given in the fixed statement.
//
ArrayPtr array_ptr = new ArrayPtr (res, array_type, loc);
Expression converted = Convert.ImplicitConversionRequired (bc, array_ptr.Resolve (bc), li.Type, loc);
if (converted == null)
return null;
//
// fixed (T* e_ptr = (e == null || e.Length == 0) ? null : converted [0])
//
converted = new Conditional (new BooleanExpression (new Binary (Binary.Operator.LogicalOr,
new Binary (Binary.Operator.Equality, res_init, new NullLiteral (loc)),
new Binary (Binary.Operator.Equality, new MemberAccess (res, "Length"), new IntConstant (bc.BuiltinTypes, 0, loc)))),
new NullLiteral (loc),
converted, loc);
converted = converted.Resolve (bc);
return new ExpressionEmitter (converted, li);
}
//
// Case 2: string
//
if (res.Type.BuiltinType == BuiltinTypeSpec.Type.String) {
return new StringEmitter (res, li).Resolve (bc);
}
// Case 3: fixed buffer
if (res is FixedBufferPtr) {
return new ExpressionEmitter (res, li);
}
bool already_fixed = true;
//
// Case 4: & object.
//
Unary u = res as Unary;
if (u != null) {
if (u.Oper == Unary.Operator.AddressOf) {
IVariableReference vr = u.Expr as IVariableReference;
if (vr == null || !vr.IsFixed) {
already_fixed = false;
}
}
} else if (initializer is Cast) {
bc.Report.Error (254, initializer.Location, "The right hand side of a fixed statement assignment may not be a cast expression");
return null;
}
if (already_fixed) {
bc.Report.Error (213, loc, "You cannot use the fixed statement to take the address of an already fixed expression");
}
res = Convert.ImplicitConversionRequired (bc, res, li.Type, loc);
return new ExpressionEmitter (res, li);
}
}
VariableDeclaration decl;
Statement statement;
bool has_ret;
public Fixed (VariableDeclaration decl, Statement stmt, Location l)
{
this.decl = decl;
statement = stmt;
loc = l;
}
#region Properties
public Statement Statement {
get {
return statement;
}
}
public BlockVariable Variables {
get {
return decl;
}
}
#endregion
public override bool Resolve (BlockContext bc)
{
using (bc.Set (ResolveContext.Options.FixedInitializerScope)) {
if (!decl.Resolve (bc))
return false;
}
return statement.Resolve (bc);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
decl.FlowAnalysis (fc);
return statement.FlowAnalysis (fc);
}
protected override void DoEmit (EmitContext ec)
{
decl.Variable.CreateBuilder (ec);
decl.Initializer.Emit (ec);
if (decl.Declarators != null) {
foreach (var d in decl.Declarators) {
d.Variable.CreateBuilder (ec);
d.Initializer.Emit (ec);
}
}
statement.Emit (ec);
if (has_ret)
return;
//
// Clear the pinned variable
//
((Emitter) decl.Initializer).EmitExit (ec);
if (decl.Declarators != null) {
foreach (var d in decl.Declarators) {
((Emitter)d.Initializer).EmitExit (ec);
}
}
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
decl.MarkReachable (rc);
rc = statement.MarkReachable (rc);
// TODO: What if there is local exit?
has_ret = rc.IsUnreachable;
return rc;
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Fixed target = (Fixed) t;
target.decl = (VariableDeclaration) decl.Clone (clonectx);
target.statement = statement.Clone (clonectx);
}
public override object Accept (StructuralVisitor visitor)
{
return visitor.Visit (this);
}
}
public class Catch : Statement
{
class CatchVariableStore : Statement
{
readonly Catch ctch;
public CatchVariableStore (Catch ctch)
{
this.ctch = ctch;
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
}
protected override void DoEmit (EmitContext ec)
{
// Emits catch variable debug information inside correct block
ctch.EmitCatchVariableStore (ec);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
return true;
}
}
class FilterStatement : Statement
{
readonly Catch ctch;
public FilterStatement (Catch ctch)
{
this.ctch = ctch;
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
}
protected override void DoEmit (EmitContext ec)
{
if (ctch.li != null) {
if (ctch.hoisted_temp != null)
ctch.hoisted_temp.Emit (ec);
else
ctch.li.Emit (ec);
if (!ctch.IsGeneral && ctch.type.Kind == MemberKind.TypeParameter)
ec.Emit (OpCodes.Box, ctch.type);
}
var expr_start = ec.DefineLabel ();
var end = ec.DefineLabel ();
ec.Emit (OpCodes.Brtrue_S, expr_start);
ec.EmitInt (0);
ec.Emit (OpCodes.Br, end);
ec.MarkLabel (expr_start);
ctch.Filter.Emit (ec);
ec.MarkLabel (end);
ec.Emit (OpCodes.Endfilter);
ec.BeginFilterHandler ();
ec.Emit (OpCodes.Pop);
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
ctch.Filter.FlowAnalysis (fc);
return true;
}
public override bool Resolve (BlockContext bc)
{
ctch.Filter = ctch.Filter.Resolve (bc);
if (ctch.Filter != null) {
if (ctch.Filter.ContainsEmitWithAwait ()) {
bc.Report.Error (7094, ctch.Filter.Location, "The `await' operator cannot be used in the filter expression of a catch clause");
}
var c = ctch.Filter as Constant;
if (c != null && !c.IsDefaultValue) {
bc.Report.Warning (7095, 1, ctch.Filter.Location, "Exception filter expression is a constant");
}
}
return true;
}
}
ExplicitBlock block;
LocalVariable li;
FullNamedExpression type_expr;
CompilerAssign assign;
TypeSpec type;
LocalTemporary hoisted_temp;
public Catch (ExplicitBlock block, Location loc)
{
this.block = block;
this.loc = loc;
}
#region Properties
public ExplicitBlock Block {
get {
return block;
}
}
public TypeSpec CatchType {
get {
return type;
}
}
public Expression Filter {
get; set;
}
public bool IsGeneral {
get {
return type_expr == null;
}
}
public FullNamedExpression TypeExpression {
get {
return type_expr;
}
set {
type_expr = value;
}
}
public LocalVariable Variable {
get {
return li;
}
set {
li = value;
}
}
#endregion
protected override void DoEmit (EmitContext ec)
{
if (Filter != null) {
ec.BeginExceptionFilterBlock ();
ec.Emit (OpCodes.Isinst, IsGeneral ? ec.BuiltinTypes.Object : CatchType);
if (Block.HasAwait) {
Block.EmitScopeInitialization (ec);
} else {
Block.Emit (ec);
}
return;
}
if (IsGeneral)
ec.BeginCatchBlock (ec.BuiltinTypes.Object);
else
ec.BeginCatchBlock (CatchType);
if (li == null)
ec.Emit (OpCodes.Pop);
if (Block.HasAwait) {
if (li != null)
EmitCatchVariableStore (ec);
} else {
Block.Emit (ec);
}
}
void EmitCatchVariableStore (EmitContext ec)
{
li.CreateBuilder (ec);
//
// For hoisted catch variable we have to use a temporary local variable
// for captured variable initialization during storey setup because variable
// needs to be on the stack after storey instance for stfld operation
//
if (li.HoistedVariant != null) {
hoisted_temp = new LocalTemporary (li.Type);
hoisted_temp.Store (ec);
// switch to assignment from temporary variable and not from top of the stack
assign.UpdateSource (hoisted_temp);
}
}
public override bool Resolve (BlockContext bc)
{
using (bc.Set (ResolveContext.Options.CatchScope)) {
if (type_expr == null) {
if (CreateExceptionVariable (bc.Module.Compiler.BuiltinTypes.Object)) {
if (!block.HasAwait || Filter != null)
block.AddScopeStatement (new CatchVariableStore (this));
Expression source = new EmptyExpression (li.Type);
assign = new CompilerAssign (new LocalVariableReference (li, Location.Null), source, Location.Null);
Block.AddScopeStatement (new StatementExpression (assign, Location.Null));
}
} else {
type = type_expr.ResolveAsType (bc);
if (type == null)
return false;
if (li == null)
CreateExceptionVariable (type);
if (type.BuiltinType != BuiltinTypeSpec.Type.Exception && !TypeSpec.IsBaseClass (type, bc.BuiltinTypes.Exception, false)) {
bc.Report.Error (155, loc, "The type caught or thrown must be derived from System.Exception");
} else if (li != null) {
li.Type = type;
li.PrepareAssignmentAnalysis (bc);
// source variable is at the top of the stack
Expression source = new EmptyExpression (li.Type);
if (li.Type.IsGenericParameter)
source = new UnboxCast (source, li.Type);
if (!block.HasAwait || Filter != null)
block.AddScopeStatement (new CatchVariableStore (this));
//
// Uses Location.Null to hide from symbol file
//
assign = new CompilerAssign (new LocalVariableReference (li, Location.Null), source, Location.Null);
Block.AddScopeStatement (new StatementExpression (assign, Location.Null));
}
}
if (Filter != null) {
Block.AddScopeStatement (new FilterStatement (this));
}
Block.SetCatchBlock ();
return Block.Resolve (bc);
}
}
bool CreateExceptionVariable (TypeSpec type)
{
if (!Block.HasAwait)
return false;
// TODO: Scan the block for rethrow expression
//if (!Block.HasRethrow)
// return;
li = LocalVariable.CreateCompilerGenerated (type, block, Location.Null);
return true;
}
protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
{
if (li != null && !li.IsCompilerGenerated) {
fc.SetVariableAssigned (li.VariableInfo, true);
}
return block.FlowAnalysis (fc);
}
public override Reachability MarkReachable (Reachability rc)
{
base.MarkReachable (rc);
var c = Filter as Constant;
if (c != null && c.IsDefaultValue)
return Reachability.CreateUnreachable ();
return block.MarkReachable (rc);
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Catch target = (Catch) t;
if (type_expr != null)
target.type_expr = (FullNamedExpression) type_expr.Clone (clonectx);
if (Filter != null)
target.Filter = Filter.Clone (clonectx);
target.block = (ExplicitBlock) clonectx.LookupBlock (block);
}
}
public class TryFinally : TryFinallyBlock
{
ExplicitBlock fini;
List try_exit_dat;
List