//
// statement.cs: Statement representation for the IL tree.
//
// Author:
// Miguel de Icaza (miguel@ximian.com)
// Martin Baulig (martin@ximian.com)
// Marek Safar (marek.safar@seznam.cz)
//
// Copyright 2001, 2002, 2003 Ximian, Inc.
// Copyright 2003, 2004 Novell, Inc.
//
using System;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics;
using System.Collections.Generic;
namespace Mono.CSharp {
public abstract class Statement {
public Location loc;
///
/// Resolves the statement, true means that all sub-statements
/// did resolve ok.
//
public virtual bool Resolve (BlockContext ec)
{
return true;
}
///
/// We already know that the statement is unreachable, but we still
/// need to resolve it to catch errors.
///
public virtual bool ResolveUnreachable (BlockContext ec, bool warn)
{
//
// This conflicts with csc's way of doing this, but IMHO it's
// the right thing to do.
//
// If something is unreachable, we still check whether it's
// correct. This means that you cannot use unassigned variables
// in unreachable code, for instance.
//
if (warn)
ec.Report.Warning (162, 2, loc, "Unreachable code detected");
ec.StartFlowBranching (FlowBranching.BranchingType.Block, loc);
bool ok = Resolve (ec);
ec.KillFlowBranching ();
return ok;
}
///
/// 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);
}
//
// 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 Statement PerformClone ()
{
CloneContext clonectx = new CloneContext ();
return Clone (clonectx);
}
}
public sealed class EmptyStatement : Statement
{
public EmptyStatement (Location loc)
{
this.loc = loc;
}
public override bool Resolve (BlockContext ec)
{
return true;
}
public override bool ResolveUnreachable (BlockContext ec, bool warn)
{
return true;
}
public override void Emit (EmitContext ec)
{
}
protected override void DoEmit (EmitContext ec)
{
throw new NotSupportedException ();
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
// nothing needed.
}
}
public class If : Statement {
Expression expr;
public Statement TrueStatement;
public Statement FalseStatement;
bool is_true_ret;
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 override bool Resolve (BlockContext ec)
{
bool ok = true;
Report.Debug (1, "START IF BLOCK", loc);
expr = expr.Resolve (ec);
if (expr == null) {
ok = false;
} else {
//
// Dead code elimination
//
if (expr is Constant) {
bool take = !((Constant) expr).IsDefaultValue;
if (take) {
if (!TrueStatement.Resolve (ec))
return false;
if ((FalseStatement != null) &&
!FalseStatement.ResolveUnreachable (ec, true))
return false;
FalseStatement = null;
} else {
if (!TrueStatement.ResolveUnreachable (ec, true))
return false;
TrueStatement = null;
if ((FalseStatement != null) &&
!FalseStatement.Resolve (ec))
return false;
}
return true;
}
}
ec.StartFlowBranching (FlowBranching.BranchingType.Conditional, loc);
ok &= TrueStatement.Resolve (ec);
is_true_ret = ec.CurrentBranching.CurrentUsageVector.IsUnreachable;
ec.CurrentBranching.CreateSibling ();
if (FalseStatement != null)
ok &= FalseStatement.Resolve (ec);
ec.EndFlowBranching ();
Report.Debug (1, "END IF BLOCK", loc);
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 (!is_true_ret){
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 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 class Do : Statement {
public Expression expr;
public Statement EmbeddedStatement;
public Do (Statement statement, BooleanExpression bool_expr, Location l)
{
expr = bool_expr;
EmbeddedStatement = statement;
loc = l;
}
public override bool Resolve (BlockContext ec)
{
bool ok = true;
ec.StartFlowBranching (FlowBranching.BranchingType.Loop, loc);
bool was_unreachable = ec.CurrentBranching.CurrentUsageVector.IsUnreachable;
ec.StartFlowBranching (FlowBranching.BranchingType.Embedded, loc);
if (!EmbeddedStatement.Resolve (ec))
ok = false;
ec.EndFlowBranching ();
if (ec.CurrentBranching.CurrentUsageVector.IsUnreachable && !was_unreachable)
ec.Report.Warning (162, 2, expr.Location, "Unreachable code detected");
expr = expr.Resolve (ec);
if (expr == null)
ok = false;
else if (expr is Constant){
bool infinite = !((Constant) expr).IsDefaultValue;
if (infinite)
ec.CurrentBranching.CurrentUsageVector.Goto ();
}
ec.EndFlowBranching ();
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);
EmbeddedStatement.Emit (ec);
ec.MarkLabel (ec.LoopBegin);
//
// 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 void CloneTo (CloneContext clonectx, Statement t)
{
Do target = (Do) t;
target.EmbeddedStatement = EmbeddedStatement.Clone (clonectx);
target.expr = expr.Clone (clonectx);
}
}
public class While : Statement {
public Expression expr;
public Statement Statement;
bool infinite, empty;
public While (BooleanExpression bool_expr, Statement statement, Location l)
{
this.expr = bool_expr;
Statement = statement;
loc = l;
}
public override bool Resolve (BlockContext ec)
{
bool ok = true;
expr = expr.Resolve (ec);
if (expr == null)
ok = false;
//
// Inform whether we are infinite or not
//
if (expr is Constant){
bool value = !((Constant) expr).IsDefaultValue;
if (value == false){
if (!Statement.ResolveUnreachable (ec, true))
return false;
empty = true;
return true;
} else
infinite = true;
}
ec.StartFlowBranching (FlowBranching.BranchingType.Loop, loc);
if (!infinite)
ec.CurrentBranching.CreateSibling ();
ec.StartFlowBranching (FlowBranching.BranchingType.Embedded, loc);
if (!Statement.Resolve (ec))
ok = false;
ec.EndFlowBranching ();
// There's no direct control flow from the end of the embedded statement to the end of the loop
ec.CurrentBranching.CurrentUsageVector.Goto ();
ec.EndFlowBranching ();
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);
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;
}
public override void Emit (EmitContext ec)
{
DoEmit (ec);
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
While target = (While) t;
target.expr = expr.Clone (clonectx);
target.Statement = Statement.Clone (clonectx);
}
}
public class For : Statement {
Expression Test;
Statement InitStatement;
Statement Increment;
public Statement Statement;
bool infinite, empty;
public For (Statement init_statement,
BooleanExpression test,
Statement increment,
Statement statement,
Location l)
{
InitStatement = init_statement;
Test = test;
Increment = increment;
Statement = statement;
loc = l;
}
public override bool Resolve (BlockContext ec)
{
bool ok = true;
if (InitStatement != null){
if (!InitStatement.Resolve (ec))
ok = false;
}
if (Test != null){
Test = Test.Resolve (ec);
if (Test == null)
ok = false;
else if (Test is Constant){
bool value = !((Constant) Test).IsDefaultValue;
if (value == false){
if (!Statement.ResolveUnreachable (ec, true))
return false;
if ((Increment != null) &&
!Increment.ResolveUnreachable (ec, false))
return false;
empty = true;
return true;
} else
infinite = true;
}
} else
infinite = true;
ec.StartFlowBranching (FlowBranching.BranchingType.Loop, loc);
if (!infinite)
ec.CurrentBranching.CreateSibling ();
bool was_unreachable = ec.CurrentBranching.CurrentUsageVector.IsUnreachable;
ec.StartFlowBranching (FlowBranching.BranchingType.Embedded, loc);
if (!Statement.Resolve (ec))
ok = false;
ec.EndFlowBranching ();
if (Increment != null){
if (ec.CurrentBranching.CurrentUsageVector.IsUnreachable) {
if (!Increment.ResolveUnreachable (ec, !was_unreachable))
ok = false;
} else {
if (!Increment.Resolve (ec))
ok = false;
}
}
// There's no direct control flow from the end of the embedded statement to the end of the loop
ec.CurrentBranching.CurrentUsageVector.Goto ();
ec.EndFlowBranching ();
return ok;
}
protected override void DoEmit (EmitContext ec)
{
if (InitStatement != null)
InitStatement.Emit (ec);
if (empty) {
Test.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);
Increment.Emit (ec);
ec.MarkLabel (test);
//
// If test is null, there is no test, and we are just
// an infinite loop
//
if (Test != null){
//
// The Resolve code already catches the case for
// Test == Constant (false) so we know that
// this is true
//
if (Test is Constant) {
Test.EmitSideEffect (ec);
ec.Emit (OpCodes.Br, loop);
} else {
Test.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 (InitStatement != null)
target.InitStatement = InitStatement.Clone (clonectx);
if (Test != null)
target.Test = Test.Clone (clonectx);
if (Increment != null)
target.Increment = Increment.Clone (clonectx);
target.Statement = Statement.Clone (clonectx);
}
}
public class StatementExpression : Statement {
ExpressionStatement expr;
public StatementExpression (ExpressionStatement expr)
{
this.expr = expr;
loc = expr.Location;
}
public override bool Resolve (BlockContext ec)
{
expr = expr.ResolveStatement (ec);
return expr != null;
}
protected override void DoEmit (EmitContext ec)
{
expr.EmitStatement (ec);
}
public override string ToString ()
{
return "StatementExpression (" + expr + ")";
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
StatementExpression target = (StatementExpression) t;
target.expr = (ExpressionStatement) expr.Clone (clonectx);
}
}
//
// 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 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));
}
}
// A 'return' or a 'yield break'
public abstract class ExitStatement : Statement
{
protected bool unwind_protect;
protected abstract bool DoResolve (BlockContext ec);
public virtual void Error_FinallyClause (Report Report)
{
Report.Error (157, loc, "Control cannot leave the body of a finally clause");
}
public sealed override bool Resolve (BlockContext ec)
{
if (!DoResolve (ec))
return false;
unwind_protect = ec.CurrentBranching.AddReturnOrigin (ec.CurrentBranching.CurrentUsageVector, this);
if (unwind_protect)
ec.NeedReturnLabel ();
ec.CurrentBranching.CurrentUsageVector.Goto ();
return true;
}
}
///
/// Implements the return statement
///
public class Return : ExitStatement
{
protected Expression Expr;
public Return (Expression expr, Location l)
{
Expr = expr;
loc = l;
}
#region Properties
public Expression Expression {
get {
return Expr;
}
}
#endregion
protected override bool DoResolve (BlockContext ec)
{
if (Expr == null) {
if (ec.ReturnType == TypeManager.void_type)
return true;
if (ec.CurrentIterator != null) {
Error_ReturnFromIterator (ec);
} else {
ec.Report.Error (126, loc,
"An object of a type convertible to `{0}' is required for the return statement",
ec.ReturnType.GetSignatureForError ());
}
return false;
}
Expr = Expr.Resolve (ec);
AnonymousExpression am = ec.CurrentAnonymousMethod;
if (am == null) {
if (ec.ReturnType == TypeManager.void_type) {
ec.Report.Error (127, loc,
"`{0}': A return keyword must not be followed by any expression when method returns void",
ec.GetSignatureForError ());
}
} else {
if (am.IsIterator) {
Error_ReturnFromIterator (ec);
return false;
}
var l = am as AnonymousMethodBody;
if (l != null && l.ReturnTypeInference != null && Expr != null) {
l.ReturnTypeInference.AddCommonTypeBound (Expr.Type);
return true;
}
}
if (Expr == null)
return false;
if (Expr.Type != ec.ReturnType) {
Expr = Convert.ImplicitConversionRequired (ec, Expr, ec.ReturnType, loc);
if (Expr == null) {
if (am != null) {
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) {
Expr.Emit (ec);
if (unwind_protect)
ec.Emit (OpCodes.Stloc, ec.TemporaryReturn ());
}
if (unwind_protect)
ec.Emit (OpCodes.Leave, ec.ReturnLabel);
else
ec.Emit (OpCodes.Ret);
}
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");
}
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 class Goto : Statement {
string target;
LabeledStatement label;
bool unwind_protect;
public override bool Resolve (BlockContext ec)
{
unwind_protect = ec.CurrentBranching.AddGotoOrigin (ec.CurrentBranching.CurrentUsageVector, this);
ec.CurrentBranching.CurrentUsageVector.Goto ();
return true;
}
public Goto (string label, Location l)
{
loc = l;
target = label;
}
public string Target {
get { return target; }
}
public void SetResolvedTarget (LabeledStatement label)
{
this.label = label;
label.AddReference ();
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
// Nothing to clone
}
protected override void DoEmit (EmitContext ec)
{
if (label == null)
throw new InternalErrorException ("goto emitted before target resolved");
Label l = label.LabelTarget (ec);
ec.Emit (unwind_protect ? OpCodes.Leave : OpCodes.Br, l);
}
}
public class LabeledStatement : Statement {
string name;
bool defined;
bool referenced;
Label label;
FlowBranching.UsageVector vectors;
public LabeledStatement (string name, Location l)
{
this.name = name;
this.loc = l;
}
public Label LabelTarget (EmitContext ec)
{
if (defined)
return label;
label = ec.DefineLabel ();
defined = true;
return label;
}
public string Name {
get { return name; }
}
public bool IsDefined {
get { return defined; }
}
public bool HasBeenReferenced {
get { return referenced; }
}
public FlowBranching.UsageVector JumpOrigins {
get { return vectors; }
}
public void AddUsageVector (FlowBranching.UsageVector vector)
{
vector = vector.Clone ();
vector.Next = vectors;
vectors = vector;
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
// nothing to clone
}
public override bool Resolve (BlockContext ec)
{
// this flow-branching will be terminated when the surrounding block ends
ec.StartFlowBranching (this);
return true;
}
protected override void DoEmit (EmitContext ec)
{
LabelTarget (ec);
ec.MarkLabel (label);
}
public void AddReference ()
{
referenced = true;
}
}
///
/// `goto default' statement
///
public class GotoDefault : Statement {
public GotoDefault (Location l)
{
loc = l;
}
protected override void CloneTo (CloneContext clonectx, Statement target)
{
// nothing to clone
}
public override bool Resolve (BlockContext ec)
{
ec.CurrentBranching.CurrentUsageVector.Goto ();
if (ec.Switch == null) {
ec.Report.Error (153, loc, "A goto case is only valid inside a switch statement");
return false;
}
if (!ec.Switch.GotDefault) {
FlowBranchingBlock.Error_UnknownLabel (loc, "default", ec.Report);
return false;
}
return true;
}
protected override void DoEmit (EmitContext ec)
{
ec.Emit (OpCodes.Br, ec.Switch.DefaultTarget);
}
}
///
/// `goto case' statement
///
public class GotoCase : Statement {
Expression expr;
SwitchLabel sl;
public GotoCase (Expression e, Location l)
{
expr = e;
loc = l;
}
public override bool Resolve (BlockContext ec)
{
if (ec.Switch == null){
ec.Report.Error (153, loc, "A goto case is only valid inside a switch statement");
return false;
}
ec.CurrentBranching.CurrentUsageVector.Goto ();
expr = expr.Resolve (ec);
if (expr == null)
return false;
Constant c = expr as Constant;
if (c == null) {
ec.Report.Error (150, expr.Location, "A constant value is expected");
return false;
}
TypeSpec type = ec.Switch.SwitchType;
Constant res = c.TryReduce (ec, type, c.Location);
if (res == null) {
c.Error_ValueCannotBeConverted (ec, loc, 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}'",
TypeManager.CSharpName (type));
object val = res.GetValue ();
if (val == null)
val = SwitchLabel.NullStringCase;
if (!ec.Switch.Elements.TryGetValue (val, out sl)) {
FlowBranchingBlock.Error_UnknownLabel (loc, "case " +
(c.GetValue () == null ? "null" : val.ToString ()), ec.Report);
return false;
}
return true;
}
protected override void DoEmit (EmitContext ec)
{
ec.Emit (OpCodes.Br, sl.GetILLabelCode (ec));
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
GotoCase target = (GotoCase) t;
target.expr = expr.Clone (clonectx);
}
}
public class Throw : Statement {
Expression expr;
public Throw (Expression expr, Location l)
{
this.expr = expr;
loc = l;
}
public override bool Resolve (BlockContext ec)
{
if (expr == null) {
ec.CurrentBranching.CurrentUsageVector.Goto ();
return ec.CurrentBranching.CheckRethrow (loc);
}
expr = expr.Resolve (ec, ResolveFlags.Type | ResolveFlags.VariableOrValue);
ec.CurrentBranching.CurrentUsageVector.Goto ();
if (expr == null)
return false;
if (Convert.ImplicitConversionExists (ec, expr, TypeManager.exception_type))
expr = Convert.ImplicitConversion (ec, expr, TypeManager.exception_type, 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)
ec.Emit (OpCodes.Rethrow);
else {
expr.Emit (ec);
ec.Emit (OpCodes.Throw);
}
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Throw target = (Throw) t;
if (expr != null)
target.expr = expr.Clone (clonectx);
}
}
public class Break : Statement {
public Break (Location l)
{
loc = l;
}
bool unwind_protect;
public override bool Resolve (BlockContext ec)
{
unwind_protect = ec.CurrentBranching.AddBreakOrigin (ec.CurrentBranching.CurrentUsageVector, loc);
ec.CurrentBranching.CurrentUsageVector.Goto ();
return true;
}
protected override void DoEmit (EmitContext ec)
{
ec.Emit (unwind_protect ? OpCodes.Leave : OpCodes.Br, ec.LoopEnd);
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
// nothing needed
}
}
public class Continue : Statement {
public Continue (Location l)
{
loc = l;
}
bool unwind_protect;
public override bool Resolve (BlockContext ec)
{
unwind_protect = ec.CurrentBranching.AddContinueOrigin (ec.CurrentBranching.CurrentUsageVector, loc);
ec.CurrentBranching.CurrentUsageVector.Goto ();
return true;
}
protected override void DoEmit (EmitContext ec)
{
ec.Emit (unwind_protect ? OpCodes.Leave : OpCodes.Br, ec.LoopBegin);
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
// nothing needed.
}
}
public interface ILocalVariable
{
void Emit (EmitContext ec);
void EmitAssign (EmitContext ec);
void EmitAddressOf (EmitContext ec);
}
public interface IKnownVariable {
Block Block { get; }
Location Location { get; }
}
//
// The information about a user-perceived local variable
//
public class LocalInfo : IKnownVariable, ILocalVariable {
public readonly FullNamedExpression Type;
public TypeSpec VariableType;
public readonly string Name;
public readonly Location Location;
public readonly Block Block;
public VariableInfo VariableInfo;
HoistedVariable hoisted_variant;
[Flags]
enum Flags : byte {
Used = 1,
ReadOnly = 2,
Pinned = 4,
IsThis = 8,
AddressTaken = 32,
CompilerGenerated = 64,
IsConstant = 128
}
public enum ReadOnlyContext: byte {
Using,
Foreach,
Fixed
}
Flags flags;
ReadOnlyContext ro_context;
LocalBuilder builder;
public LocalInfo (FullNamedExpression type, string name, Block block, Location l)
{
Type = type;
Name = name;
Block = block;
Location = l;
}
public LocalInfo (TypeContainer ds, Block block, Location l)
{
VariableType = ds.IsGeneric ? ds.CurrentType : ds.Definition;
Block = block;
Location = l;
}
public void ResolveVariable (EmitContext ec)
{
if (HoistedVariant != null)
return;
if (builder == null) {
builder = ec.DeclareLocal (VariableType, Pinned);
}
}
public void Emit (EmitContext ec)
{
ec.Emit (OpCodes.Ldloc, builder);
}
public void EmitAssign (EmitContext ec)
{
ec.Emit (OpCodes.Stloc, builder);
}
public void EmitAddressOf (EmitContext ec)
{
ec.Emit (OpCodes.Ldloca, builder);
}
public void EmitSymbolInfo (EmitContext ec)
{
if (builder != null)
ec.DefineLocalVariable (Name, builder);
}
//
// Hoisted local variable variant
//
public HoistedVariable HoistedVariant {
get {
return hoisted_variant;
}
set {
hoisted_variant = value;
}
}
public bool IsThisAssigned (BlockContext ec, Block block)
{
if (VariableInfo == null)
throw new Exception ();
if (!ec.DoFlowAnalysis || ec.CurrentBranching.IsAssigned (VariableInfo))
return true;
return VariableInfo.TypeInfo.IsFullyInitialized (ec, VariableInfo, block.StartLocation);
}
public bool IsAssigned (BlockContext ec)
{
if (VariableInfo == null)
throw new Exception ();
return !ec.DoFlowAnalysis || ec.CurrentBranching.IsAssigned (VariableInfo);
}
public bool Resolve (ResolveContext ec)
{
if (VariableType != null)
return true;
TypeExpr texpr = Type.ResolveAsContextualType (ec, false);
if (texpr == null)
return false;
VariableType = texpr.Type;
if (VariableType.IsStatic) {
FieldBase.Error_VariableOfStaticClass (Location, Name, VariableType, ec.Report);
return false;
}
if (VariableType.IsPointer && !ec.IsUnsafe)
Expression.UnsafeError (ec, Location);
return true;
}
public bool IsConstant {
get { return (flags & Flags.IsConstant) != 0; }
set { flags |= Flags.IsConstant; }
}
public bool AddressTaken {
get { return (flags & Flags.AddressTaken) != 0; }
set { flags |= Flags.AddressTaken; }
}
public bool CompilerGenerated {
get { return (flags & Flags.CompilerGenerated) != 0; }
set { flags |= Flags.CompilerGenerated; }
}
public override string ToString ()
{
return String.Format ("LocalInfo ({0},{1},{2},{3})",
Name, Type, VariableInfo, Location);
}
public bool Used {
get { return (flags & Flags.Used) != 0; }
set { flags = value ? (flags | Flags.Used) : (unchecked (flags & ~Flags.Used)); }
}
public bool ReadOnly {
get { return (flags & Flags.ReadOnly) != 0; }
}
public void SetReadOnlyContext (ReadOnlyContext context)
{
flags |= Flags.ReadOnly;
ro_context = context;
}
public string GetReadOnlyContext ()
{
if (!ReadOnly)
throw new InternalErrorException ("Variable is not readonly");
switch (ro_context) {
case ReadOnlyContext.Fixed:
return "fixed variable";
case ReadOnlyContext.Foreach:
return "foreach iteration variable";
case ReadOnlyContext.Using:
return "using variable";
}
throw new NotImplementedException ();
}
//
// Whether the variable is pinned, if Pinned the variable has been
// allocated in a pinned slot with DeclareLocal.
//
public bool Pinned {
get { return (flags & Flags.Pinned) != 0; }
set { flags = value ? (flags | Flags.Pinned) : (flags & ~Flags.Pinned); }
}
public bool IsThis {
get { return (flags & Flags.IsThis) != 0; }
set { flags = value ? (flags | Flags.IsThis) : (flags & ~Flags.IsThis); }
}
Block IKnownVariable.Block {
get { return Block; }
}
Location IKnownVariable.Location {
get { return Location; }
}
public LocalInfo Clone (CloneContext clonectx)
{
//
// Variables in anonymous block are not resolved yet
//
if (VariableType == null)
return new LocalInfo ((FullNamedExpression) Type.Clone (clonectx), Name, clonectx.LookupBlock (Block), Location);
//
// Variables in method block are resolved
//
LocalInfo li = new LocalInfo (null, Name, clonectx.LookupBlock (Block), Location);
li.VariableType = VariableType;
return li;
}
}
///
/// 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 {
public Block Parent;
public Location StartLocation;
public Location EndLocation = Location.Null;
public ExplicitBlock Explicit;
public ToplevelBlock Toplevel; // TODO: Use Explicit
[Flags]
public enum Flags
{
Unchecked = 1,
BlockUsed = 2,
VariablesInitialized = 4,
HasRet = 8,
Unsafe = 16,
IsIterator = 32,
HasCapturedVariable = 64,
HasCapturedThis = 1 << 7,
IsExpressionTree = 1 << 8
}
protected Flags flags;
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; }
}
//
// The statements in this block
//
protected List statements;
//
// An array of Blocks. We keep track of children just
// to generate the local variable declarations.
//
// Statements and child statements are handled through the
// statements.
//
List children;
//
// Labels. (label, block) pairs.
//
protected Dictionary labels;
//
// Keeps track of (name, type) pairs
//
Dictionary variables;
//
// Keeps track of constants
Dictionary constants;
//
// Temporary variables.
//
List temporary_variables;
//
// If this is a switch section, the enclosing switch block.
//
protected ExplicitBlock switch_block;
protected List scope_initializers;
List anonymous_children;
int? resolving_init_idx;
protected static int id;
int this_id;
int assignable_slots;
bool unreachable_shown;
bool unreachable;
public Block (Block parent)
: this (parent, (Flags) 0, Location.Null, Location.Null)
{ }
public Block (Block parent, Flags flags)
: this (parent, flags, Location.Null, Location.Null)
{ }
public Block (Block parent, Location start, Location end)
: this (parent, (Flags) 0, start, end)
{ }
//
// Useful when TopLevel block is downgraded to normal block
//
public Block (ToplevelBlock parent, ToplevelBlock source)
: this (parent, source.flags, source.StartLocation, source.EndLocation)
{
statements = source.statements;
children = source.children;
labels = source.labels;
variables = source.variables;
constants = source.constants;
switch_block = source.switch_block;
}
public Block (Block parent, Flags flags, Location start, Location end)
{
if (parent != null) {
parent.AddChild (this);
// the appropriate constructors will fixup these fields
Toplevel = parent.Toplevel;
Explicit = parent.Explicit;
}
this.Parent = parent;
this.flags = flags;
this.StartLocation = start;
this.EndLocation = end;
this.loc = start;
this_id = id++;
statements = new List (4);
}
#region Properties
public int ID {
get { return this_id; }
}
public IDictionary Variables {
get {
if (variables == null)
variables = new Dictionary ();
return variables;
}
}
#endregion
public ExplicitBlock CreateSwitchBlock (Location start)
{
// FIXME: Only explicit block should be created
var new_block = new ExplicitBlock (this, start, start);
new_block.switch_block = Explicit;
return new_block;
}
void AddChild (Block b)
{
if (children == null)
children = new List (1);
children.Add (b);
}
public void SetEndLocation (Location loc)
{
EndLocation = loc;
}
protected void Error_158 (string name, Location loc)
{
Toplevel.Report.Error (158, loc, "The label `{0}' shadows another label " +
"by the same name in a contained scope", name);
}
///
/// Adds a label to the current block.
///
///
///
/// false if the name already exists in this block. true
/// otherwise.
///
///
public bool AddLabel (LabeledStatement target)
{
if (switch_block != null)
return switch_block.AddLabel (target);
string name = target.Name;
Block cur = this;
while (cur != null) {
LabeledStatement s = cur.DoLookupLabel (name);
if (s != null) {
Toplevel.Report.SymbolRelatedToPreviousError (s.loc, s.Name);
Toplevel.Report.Error (140, target.loc, "The label `{0}' is a duplicate", name);
return false;
}
if (this == Explicit)
break;
cur = cur.Parent;
}
while (cur != null) {
if (cur.DoLookupLabel (name) != null) {
Error_158 (name, target.loc);
return false;
}
if (children != null) {
foreach (Block b in children) {
LabeledStatement s = b.DoLookupLabel (name);
if (s == null)
continue;
Toplevel.Report.SymbolRelatedToPreviousError (s.loc, s.Name);
Error_158 (name, target.loc);
return false;
}
}
cur = cur.Parent;
}
Toplevel.CheckError158 (name, target.loc);
if (labels == null)
labels = new Dictionary ();
labels.Add (name, target);
return true;
}
public LabeledStatement LookupLabel (string name)
{
LabeledStatement s = DoLookupLabel (name);
if (s != null)
return s;
if (children == null)
return null;
foreach (Block child in children) {
if (Explicit != child.Explicit)
continue;
s = child.LookupLabel (name);
if (s != null)
return s;
}
return null;
}
LabeledStatement DoLookupLabel (string name)
{
if (switch_block != null)
return switch_block.LookupLabel (name);
if (labels != null)
if (labels.ContainsKey (name))
return labels [name];
return null;
}
public bool CheckInvariantMeaningInBlock (string name, Expression e, Location loc)
{
Block b = this;
IKnownVariable kvi = b.Explicit.GetKnownVariable (name);
while (kvi == null) {
b = b.Explicit.Parent;
if (b == null)
return true;
kvi = b.Explicit.GetKnownVariable (name);
}
if (kvi.Block == b)
return true;
// Is kvi.Block nested inside 'b'
if (b.Explicit != kvi.Block.Explicit) {
//
// If a variable by the same name it defined in a nested block of this
// block, we violate the invariant meaning in a block.
//
if (b == this) {
Toplevel.Report.SymbolRelatedToPreviousError (kvi.Location, name);
Toplevel.Report.Error (135, loc, "`{0}' conflicts with a declaration in a child block", name);
return false;
}
//
// It's ok if the definition is in a nested subblock of b, but not
// nested inside this block -- a definition in a sibling block
// should not affect us.
//
return true;
}
//
// Block 'b' and kvi.Block are the same textual block.
// However, different variables are extant.
//
// Check if the variable is in scope in both blocks. We use
// an indirect check that depends on AddVariable doing its
// part in maintaining the invariant-meaning-in-block property.
//
if (e is VariableReference || (e is Constant && b.GetLocalInfo (name) != null))
return true;
if (this is ToplevelBlock) {
Toplevel.Report.SymbolRelatedToPreviousError (kvi.Location, name);
e.Error_VariableIsUsedBeforeItIsDeclared (Toplevel.Report, name);
return false;
}
//
// Even though we detected the error when the name is used, we
// treat it as if the variable declaration was in error.
//
Toplevel.Report.SymbolRelatedToPreviousError (loc, name);
Error_AlreadyDeclared (kvi.Location, name, "parent or current");
return false;
}
protected bool CheckParentConflictName (ToplevelBlock block, string name, Location l)
{
LocalInfo vi = GetLocalInfo (name);
if (vi != null) {
block.Report.SymbolRelatedToPreviousError (vi.Location, name);
if (Explicit == vi.Block.Explicit) {
Error_AlreadyDeclared (l, name, null);
} else {
Error_AlreadyDeclared (l, name, this is ToplevelBlock ?
"parent or current" : "parent");
}
return false;
}
if (block != null) {
var tblock = block.CheckParameterNameConflict (name);
if (tblock != null) {
if (block == tblock && block is Linq.QueryBlock)
Error_AlreadyDeclared (loc, name);
else
Error_AlreadyDeclared (loc, name, "parent or current");
return false;
}
}
return true;
}
public LocalInfo AddVariable (Expression type, string name, Location l)
{
if (!CheckParentConflictName (Toplevel, name, l))
return null;
IKnownVariable kvi = Explicit.GetKnownVariable (name);
if (kvi != null) {
Toplevel.Report.SymbolRelatedToPreviousError (kvi.Location, name);
Error_AlreadyDeclared (l, name, "child");
return null;
}
LocalInfo vi = new LocalInfo ((FullNamedExpression) type, name, this, l);
AddVariable (vi);
if ((flags & Flags.VariablesInitialized) != 0)
throw new InternalErrorException ("block has already been resolved");
return vi;
}
protected virtual void AddVariable (LocalInfo li)
{
Variables.Add (li.Name, li);
Explicit.AddKnownVariable (li.Name, li);
}
protected virtual void Error_AlreadyDeclared (Location loc, string var, string reason)
{
if (reason == null) {
Error_AlreadyDeclared (loc, var);
return;
}
Toplevel.Report.Error (136, loc, "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", var, reason);
}
protected virtual void Error_AlreadyDeclared (Location loc, string name)
{
Toplevel.Report.Error (128, loc,
"A local variable named `{0}' is already defined in this scope", name);
}
public virtual void Error_AlreadyDeclaredTypeParameter (Location loc, string name, string conflict)
{
Toplevel.Report.Error (412, loc, "The type parameter name `{0}' is the same as `{1}'",
name, conflict);
}
public bool AddConstant (Expression type, string name, Expression value, Location l)
{
if (AddVariable (type, name, l) == null)
return false;
if (constants == null)
constants = new Dictionary ();
constants.Add (name, value);
// A block is considered used if we perform an initialization in a local declaration, even if it is constant.
Use ();
return true;
}
static int next_temp_id = 0;
public LocalInfo AddTemporaryVariable (TypeExpr te, Location loc)
{
Report.Debug (64, "ADD TEMPORARY", this, Toplevel, loc);
if (temporary_variables == null)
temporary_variables = new List ();
int id = ++next_temp_id;
string name = "$s_" + id.ToString ();
LocalInfo li = new LocalInfo (te, name, this, loc);
li.CompilerGenerated = true;
temporary_variables.Add (li);
return li;
}
public LocalInfo GetLocalInfo (string name)
{
LocalInfo ret;
for (Block b = this; b != null; b = b.Parent) {
if (b.variables != null && b.variables.TryGetValue (name, out ret)) {
return ret;
}
}
return null;
}
public Expression GetVariableType (string name)
{
LocalInfo vi = GetLocalInfo (name);
return vi == null ? null : vi.Type;
}
public Expression GetConstantExpression (string name)
{
Expression ret;
for (Block b = this; b != null; b = b.Parent) {
if (b.constants != null) {
if (b.constants.TryGetValue (name, out ret))
return ret;
}
}
return null;
}
//
// 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 AddStatement (Statement s)
{
statements.Add (s);
flags |= Flags.BlockUsed;
}
public bool Used {
get { return (flags & Flags.BlockUsed) != 0; }
}
public void Use ()
{
flags |= Flags.BlockUsed;
}
public bool HasRet {
get { return (flags & Flags.HasRet) != 0; }
}
public int AssignableSlots {
get {
// TODO: Re-enable
// if ((flags & Flags.VariablesInitialized) == 0)
// throw new Exception ("Variables have not been initialized yet");
return assignable_slots;
}
}
public IList AnonymousChildren {
get { return anonymous_children; }
}
public void AddAnonymousChild (ToplevelBlock b)
{
if (anonymous_children == null)
anonymous_children = new List ();
anonymous_children.Add (b);
}
void DoResolveConstants (BlockContext ec)
{
if (constants == null)
return;
if (variables == null)
throw new InternalErrorException ("cannot happen");
foreach (var de in variables) {
string name = de.Key;
LocalInfo vi = de.Value;
TypeSpec variable_type = vi.VariableType;
if (variable_type == null) {
if (vi.Type is VarExpr)
ec.Report.Error (822, vi.Type.Location, "An implicitly typed local variable cannot be a constant");
continue;
}
Expression cv;
if (!constants.TryGetValue (name, out cv))
continue;
// Don't let 'const int Foo = Foo;' succeed.
// Removing the name from 'constants' ensures that we get a LocalVariableReference below,
// which in turn causes the 'must be constant' error to be triggered.
constants.Remove (name);
if (!variable_type.IsConstantCompatible) {
Const.Error_InvalidConstantType (variable_type, loc, ec.Report);
continue;
}
ec.CurrentBlock = this;
Expression e;
using (ec.With (ResolveContext.Options.ConstantCheckState, (flags & Flags.Unchecked) == 0)) {
using (ec.With (ResolveContext.Options.DoFlowAnalysis, false)) {
e = cv.Resolve (ec);
}
}
if (e == null)
continue;
Constant ce = e as Constant;
if (ce == null) {
e.Error_ExpressionMustBeConstant (ec, vi.Location, name);
continue;
}
e = ce.ConvertImplicitly (ec, variable_type);
if (e == null) {
if (TypeManager.IsReferenceType (variable_type))
ce.Error_ConstantCanBeInitializedWithNullOnly (ec, variable_type, vi.Location, vi.Name);
else
ce.Error_ValueCannotBeConverted (ec, vi.Location, variable_type, false);
continue;
}
constants.Add (name, e);
vi.IsConstant = true;
}
}
protected void ResolveMeta (BlockContext ec, int offset)
{
Report.Debug (64, "BLOCK RESOLVE META", this, Parent);
// If some parent block was unsafe, we remain unsafe even if this block
// isn't explicitly marked as such.
using (ec.With (ResolveContext.Options.UnsafeScope, ec.IsUnsafe | Unsafe)) {
flags |= Flags.VariablesInitialized;
if (variables != null) {
foreach (LocalInfo li in variables.Values) {
if (!li.Resolve (ec))
continue;
li.VariableInfo = new VariableInfo (li, offset);
offset += li.VariableInfo.Length;
}
}
assignable_slots = offset;
DoResolveConstants (ec);
if (children == null)
return;
foreach (Block b in children)
b.ResolveMeta (ec, offset);
}
}
//
// Emits the local variable declarations for a block
//
public virtual void EmitMeta (EmitContext ec)
{
if (variables != null){
foreach (LocalInfo vi in variables.Values)
vi.ResolveVariable (ec);
}
if (temporary_variables != null) {
for (int i = 0; i < temporary_variables.Count; i++)
((LocalInfo)temporary_variables[i]).ResolveVariable(ec);
}
if (children != null) {
for (int i = 0; i < children.Count; i++)
((Block)children[i]).EmitMeta(ec);
}
}
void UsageWarning (BlockContext ec)
{
if (variables == null || ec.Report.WarningLevel < 3)
return;
foreach (var de in variables) {
LocalInfo vi = de.Value;
if (!vi.Used) {
string name = de.Key;
// vi.VariableInfo can be null for 'catch' variables
if (vi.VariableInfo != null && vi.VariableInfo.IsEverAssigned)
ec.Report.Warning (219, 3, vi.Location, "The variable `{0}' is assigned but its value is never used", name);
else
ec.Report.Warning (168, 3, vi.Location, "The variable `{0}' is declared but never used", name);
}
}
}
static void CheckPossibleMistakenEmptyStatement (BlockContext ec, Statement s)
{
Statement body;
// Some statements are wrapped by a Block. Since
// others' internal could be changed, here I treat
// them as possibly wrapped by Block equally.
Block b = s as Block;
if (b != null && b.statements.Count == 1)
s = (Statement) b.statements [0];
if (s is Lock)
body = ((Lock) s).Statement;
else if (s is For)
body = ((For) s).Statement;
else if (s is Foreach)
body = ((Foreach) s).Statement;
else if (s is While)
body = ((While) s).Statement;
else if (s is Fixed)
body = ((Fixed) s).Statement;
else if (s is Using)
body = ((Using) s).EmbeddedStatement;
else if (s is UsingTemporary)
body = ((UsingTemporary) s).Statement;
else
return;
if (body == null || body is EmptyStatement)
ec.Report.Warning (642, 3, s.loc, "Possible mistaken empty statement");
}
public override bool Resolve (BlockContext ec)
{
Block prev_block = ec.CurrentBlock;
bool ok = true;
int errors = ec.Report.Errors;
ec.CurrentBlock = this;
ec.StartFlowBranching (this);
Report.Debug (4, "RESOLVE BLOCK", StartLocation, ec.CurrentBranching);
//
// 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 (ec);
}
resolving_init_idx = null;
}
//
// This flag is used to notate nested statements as unreachable from the beginning of this block.
// For the purposes of this resolution, it doesn't matter that the whole block is unreachable
// from the beginning of the function. The outer Resolve() that detected the unreachability is
// responsible for handling the situation.
//
int statement_count = statements.Count;
for (int ix = 0; ix < statement_count; ix++){
Statement s = statements [ix];
// Check possible empty statement (CS0642)
if (ix + 1 < statement_count && ec.Report.WarningLevel >= 3 &&
statements [ix + 1] is ExplicitBlock)
CheckPossibleMistakenEmptyStatement (ec, s);
//
// Warn if we detect unreachable code.
//
if (unreachable) {
if (s is EmptyStatement)
continue;
if (!unreachable_shown && !(s is LabeledStatement)) {
ec.Report.Warning (162, 2, s.loc, "Unreachable code detected");
unreachable_shown = true;
}
Block c_block = s as Block;
if (c_block != null)
c_block.unreachable = c_block.unreachable_shown = true;
}
//
// Note that we're not using ResolveUnreachable() for unreachable
// statements here. ResolveUnreachable() creates a temporary
// flow branching and kills it afterwards. This leads to problems
// if you have two unreachable statements where the first one
// assigns a variable and the second one tries to access it.
//
if (!s.Resolve (ec)) {
ok = false;
if (ec.IsInProbingMode)
break;
statements [ix] = new EmptyStatement (s.loc);
continue;
}
if (unreachable && !(s is LabeledStatement) && !(s is Block))
statements [ix] = new EmptyStatement (s.loc);
unreachable = ec.CurrentBranching.CurrentUsageVector.IsUnreachable;
if (unreachable && s is LabeledStatement)
throw new InternalErrorException ("should not happen");
}
Report.Debug (4, "RESOLVE BLOCK DONE", StartLocation,
ec.CurrentBranching, statement_count);
while (ec.CurrentBranching is FlowBranchingLabeled)
ec.EndFlowBranching ();
bool flow_unreachable = ec.EndFlowBranching ();
ec.CurrentBlock = prev_block;
if (flow_unreachable)
flags |= Flags.HasRet;
// 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 == Toplevel && !Toplevel.IsThisAssigned (ec) && !flow_unreachable)
ok = false;
if ((labels != null) && (ec.Report.WarningLevel >= 2)) {
foreach (LabeledStatement label in labels.Values)
if (!label.HasBeenReferenced)
ec.Report.Warning (164, 2, label.loc, "This label has not been referenced");
}
if (ok && errors == ec.Report.Errors)
UsageWarning (ec);
return ok;
}
public override bool ResolveUnreachable (BlockContext ec, bool warn)
{
unreachable_shown = true;
unreachable = true;
if (warn)
ec.Report.Warning (162, 2, loc, "Unreachable code detected");
ec.StartFlowBranching (FlowBranching.BranchingType.Block, loc);
bool ok = Resolve (ec);
ec.KillFlowBranching ();
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);
ec.Mark (StartLocation);
DoEmit (ec);
if (SymbolWriter.HasSymbolWriter)
EmitSymbolInfo (ec);
}
protected void EmitScopeInitializers (EmitContext ec)
{
SymbolWriter.OpenCompilerGeneratedBlock (ec);
using (ec.With (EmitContext.Options.OmitDebugInfo, true)) {
foreach (Statement s in scope_initializers)
s.Emit (ec);
}
SymbolWriter.CloseCompilerGeneratedBlock (ec);
}
protected virtual void EmitSymbolInfo (EmitContext ec)
{
if (variables != null) {
foreach (LocalInfo vi in variables.Values) {
vi.EmitSymbolInfo (ec);
}
}
}
public override string ToString ()
{
return String.Format ("{0} ({1}:{2})", GetType (), this_id, StartLocation);
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
Block target = (Block) t;
clonectx.AddBlockMap (this, target);
target.Toplevel = (ToplevelBlock) (Toplevel == this ? target : clonectx.LookupBlock (Toplevel));
target.Explicit = (ExplicitBlock) (Explicit == this ? target : clonectx.LookupBlock (Explicit));
if (Parent != null)
target.Parent = clonectx.RemapBlockCopy (Parent);
if (variables != null){
target.variables = new Dictionary ();
foreach (var de in variables){
LocalInfo newlocal = de.Value.Clone (clonectx);
target.variables [de.Key] = newlocal;
clonectx.AddVariableMap (de.Value, newlocal);
}
}
target.statements = new List (statements.Count);
foreach (Statement s in statements)
target.statements.Add (s.Clone (clonectx));
if (target.children != null){
target.children = new List (children.Count);
foreach (Block b in children){
target.children.Add (clonectx.LookupBlock (b));
}
}
//
// TODO: labels, switch_block, constants (?), anonymous_children
//
}
}
public class ExplicitBlock : Block
{
Dictionary known_variables;
protected AnonymousMethodStorey am_storey;
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;
}
//
// Marks a variable with name @name as being used in this or a child block.
// If a variable name has been used in a child block, it's illegal to
// declare a variable with the same name in the current block.
//
internal void AddKnownVariable (string name, IKnownVariable info)
{
if (known_variables == null)
known_variables = new Dictionary ();
known_variables [name] = info;
if (Parent != null)
Parent.Explicit.AddKnownVariable (name, info);
}
public AnonymousMethodStorey AnonymousMethodStorey {
get { return am_storey; }
}
//
// Creates anonymous method storey in current block
//
public AnonymousMethodStorey CreateAnonymousMethodStorey (ResolveContext ec)
{
//
// When referencing a variable in iterator storey from children anonymous method
//
if (Toplevel.am_storey is IteratorStorey) {
return Toplevel.am_storey;
}
//
// An iterator has only 1 storey block
//
if (ec.CurrentIterator != null)
return ec.CurrentIterator.Storey;
//
// Switch block does not follow sequential flow and we cannot emit
// storey initialization inside the block because it can be jumped over
// for all non-first cases. Instead we push it to the parent block to be
// always initialized
//
if (switch_block != null)
return switch_block.CreateAnonymousMethodStorey (ec);
if (am_storey == null) {
MemberBase mc = ec.MemberContext as MemberBase;
GenericMethod gm = mc == null ? null : mc.GenericMethod;
//
// Creates anonymous method storey for this block
//
am_storey = new AnonymousMethodStorey (this, ec.CurrentMemberDefinition.Parent.PartialContainer, mc, gm, "AnonStorey");
}
return am_storey;
}
public override void Emit (EmitContext ec)
{
if (am_storey != null)
am_storey.EmitStoreyInstantiation (ec);
bool emit_debug_info = SymbolWriter.HasSymbolWriter && Parent != null && !(am_storey is IteratorStorey);
if (emit_debug_info)
ec.BeginScope ();
base.Emit (ec);
if (emit_debug_info)
ec.EndScope ();
}
public override void EmitMeta (EmitContext ec)
{
//
// Creates anonymous method storey
//
if (am_storey != null) {
if (ec.CurrentAnonymousMethod != null && ec.CurrentAnonymousMethod.Storey != null) {
//
// Creates parent storey reference when hoisted this is accessible
//
if (am_storey.OriginalSourceBlock.Explicit.HasCapturedThis) {
ExplicitBlock parent = Toplevel.Parent.Explicit;
//
// Hoisted this exists in top-level parent storey only
//
while (parent.am_storey == null || parent.am_storey.Parent is AnonymousMethodStorey)
parent = parent.Parent.Explicit;
am_storey.AddParentStoreyReference (ec, parent.am_storey);
}
am_storey.SetNestedStoryParent (ec.CurrentAnonymousMethod.Storey);
// TODO MemberCache: Review
am_storey.Mutator = ec.CurrentAnonymousMethod.Storey.Mutator;
}
am_storey.CreateType ();
if (am_storey.Mutator == null && ec.CurrentTypeParameters != null)
am_storey.Mutator = new TypeParameterMutator (ec.CurrentTypeParameters, am_storey.CurrentTypeParameters);
am_storey.DefineType ();
am_storey.ResolveTypeParameters ();
am_storey.Define ();
am_storey.Parent.PartialContainer.AddCompilerGeneratedClass (am_storey);
var ref_blocks = am_storey.ReferencesFromChildrenBlock;
if (ref_blocks != null) {
foreach (ExplicitBlock ref_block in ref_blocks) {
for (ExplicitBlock b = ref_block.Explicit; b != this; b = b.Parent.Explicit) {
if (b.am_storey != null) {
b.am_storey.AddParentStoreyReference (ec, am_storey);
// Stop propagation inside same top block
if (b.Toplevel == Toplevel)
break;
b = b.Toplevel;
}
b.HasCapturedVariable = true;
}
}
}
}
base.EmitMeta (ec);
}
public IKnownVariable GetKnownVariable (string name)
{
if (known_variables == null)
return null;
IKnownVariable kw;
known_variables.TryGetValue (name, out kw);
return kw;
}
public bool HasCapturedThis
{
set { flags = value ? flags | Flags.HasCapturedThis : flags & ~Flags.HasCapturedThis; }
get { return (flags & Flags.HasCapturedThis) != 0; }
}
public bool HasCapturedVariable
{
set { flags = value ? flags | Flags.HasCapturedVariable : flags & ~Flags.HasCapturedVariable; }
get { return (flags & Flags.HasCapturedVariable) != 0; }
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
ExplicitBlock target = (ExplicitBlock) t;
target.known_variables = null;
base.CloneTo (clonectx, t);
}
}
public class ToplevelParameterInfo : IKnownVariable {
public readonly ToplevelBlock Block;
public readonly int Index;
public VariableInfo VariableInfo;
Block IKnownVariable.Block {
get { return Block; }
}
public Parameter Parameter {
get { return Block.Parameters [Index]; }
}
public TypeSpec ParameterType {
get { return Block.Parameters.Types [Index]; }
}
public Location Location {
get { return Parameter.Location; }
}
public ToplevelParameterInfo (ToplevelBlock block, int idx)
{
this.Block = block;
this.Index = idx;
}
}
//
// A toplevel block contains extra information, the split is done
// only to separate information that would otherwise bloat the more
// lightweight Block.
//
// In particular, this was introduced when the support for Anonymous
// Methods was implemented.
//
public class ToplevelBlock : ExplicitBlock
{
//
// Block is converted to an expression
//
sealed class BlockScopeExpression : Expression
{
Expression child;
readonly ToplevelBlock block;
public BlockScopeExpression (Expression child, ToplevelBlock block)
{
this.child = child;
this.block = block;
}
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.EmitMeta (ec);
block.EmitScopeInitializers (ec);
child.Emit (ec);
}
}
protected ParametersCompiled parameters;
protected ToplevelParameterInfo[] parameter_info;
LocalInfo this_variable;
bool resolved;
bool unreachable;
CompilerContext compiler;
public HoistedVariable HoistedThisVariable;
public bool Resolved {
get {
return resolved;
}
}
//
// The parameters for the block.
//
public ParametersCompiled Parameters {
get { return parameters; }
}
public Report Report {
get { return compiler.Report; }
}
public ToplevelBlock Container {
get { return Parent == null ? null : Parent.Toplevel; }
}
public ToplevelBlock (CompilerContext ctx, Block parent, ParametersCompiled parameters, Location start) :
this (ctx, parent, (Flags) 0, parameters, start)
{
}
public ToplevelBlock (CompilerContext ctx, ParametersCompiled parameters, Location start) :
this (ctx, null, (Flags) 0, parameters, start)
{
}
ToplevelBlock (CompilerContext ctx, Flags flags, ParametersCompiled parameters, Location start) :
this (ctx, null, flags, parameters, start)
{
}
// We use 'Parent' to hook up to the containing block, but don't want to register the current block as a child.
// So, we use a two-stage setup -- first pass a null parent to the base constructor, and then override 'Parent'.
public ToplevelBlock (CompilerContext ctx, Block parent, Flags flags, ParametersCompiled parameters, Location start) :
base (null, flags, start, Location.Null)
{
this.compiler = ctx;
this.Toplevel = this;
this.parameters = parameters;
this.Parent = parent;
if (parent != null)
parent.AddAnonymousChild (this);
if (!this.parameters.IsEmpty)
ProcessParameters ();
}
public ToplevelBlock (CompilerContext ctx, Location loc)
: this (ctx, null, (Flags) 0, ParametersCompiled.EmptyReadOnlyParameters, loc)
{
}
protected override void CloneTo (CloneContext clonectx, Statement t)
{
base.CloneTo (clonectx, t);
if (parameters.Count != 0) {
ToplevelBlock target = (ToplevelBlock) t;
target.parameter_info = new ToplevelParameterInfo[parameters.Count];
for (int i = 0; i < parameters.Count; ++i)
target.parameter_info[i] = new ToplevelParameterInfo (target, i);
}
}
public bool CheckError158 (string name, Location loc)
{
if (AnonymousChildren != null) {
foreach (ToplevelBlock child in AnonymousChildren) {
if (!child.CheckError158 (name, loc))
return false;
}
}
for (ToplevelBlock c = Container; c != null; c = c.Container) {
if (!c.DoCheckError158 (name, loc))
return false;
}
return true;
}
void ProcessParameters ()
{
int n = parameters.Count;
parameter_info = new ToplevelParameterInfo [n];
ToplevelBlock top_parent = Parent == null ? null : Parent.Toplevel;
for (int i = 0; i < n; ++i) {
parameter_info [i] = new ToplevelParameterInfo (this, i);
Parameter p = parameters [i];
if (p == null)
continue;
string name = p.Name;
if (CheckParentConflictName (top_parent, name, loc))
AddKnownVariable (name, parameter_info [i]);
}
// mark this block as "used" so that we create local declarations in a sub-block
// FIXME: This appears to uncover a lot of bugs
//this.Use ();
}
bool DoCheckError158 (string name, Location loc)
{
LabeledStatement s = LookupLabel (name);
if (s != null) {
Report.SymbolRelatedToPreviousError (s.loc, s.Name);
Error_158 (name, loc);
return false;
}
return true;
}
public override Expression CreateExpressionTree (ResolveContext ec)
{
if (statements.Count == 1) {
Expression expr = ((Statement) statements[0]).CreateExpressionTree (ec);
if (scope_initializers != null)
expr = new BlockScopeExpression (expr, this);
return expr;
}
return base.CreateExpressionTree (ec);
}
//
// Reformats this block to be top-level iterator block
//
public IteratorStorey ChangeToIterator (Iterator iterator, ToplevelBlock source)
{
IsIterator = true;
// Creates block with original statements
AddStatement (new IteratorStatement (iterator, new Block (this, source)));
source.statements = new List (1);
source.AddStatement (new Return (iterator, iterator.Location));
source.IsIterator = false;
IteratorStorey iterator_storey = new IteratorStorey (iterator);
source.am_storey = iterator_storey;
return iterator_storey;
}
//
// 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 arg_expr = new ParameterReference (parameter_info[i], parameters[i].Location);
args.Add (new Argument (arg_expr));
}
return args;
}
//
// Returns a parameter reference expression for the given name,
// or null if there is no such parameter
//
public Expression GetParameterReference (string name, Location loc)
{
for (ToplevelBlock t = this; t != null; t = t.Container) {
if (t.parameters.IsEmpty)
continue;
Expression expr = t.GetParameterReferenceExpression (name, loc);
if (expr != null)
return expr;
}
return null;
}
protected virtual Expression GetParameterReferenceExpression (string name, Location loc)
{
int idx = parameters.GetParameterIndexByName (name);
return idx < 0 ?
null : new ParameterReference (parameter_info [idx], loc);
}
public ToplevelBlock CheckParameterNameConflict (string name)
{
for (ToplevelBlock t = this; t != null; t = t.Container) {
if (t.HasParameterWithName (name))
return t;
}
return null;
}
protected virtual bool HasParameterWithName (string name)
{
return parameters.GetParameterIndexByName (name) >= 0;
}
//
// Returns the "this" instance variable of this block.
// See AddThisVariable() for more information.
//
public LocalInfo ThisVariable {
get { return this_variable; }
}
//
// 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 LocalInfo AddThisVariable (TypeContainer ds, Location l)
{
if (this_variable == null) {
this_variable = new LocalInfo (ds, this, l);
this_variable.Used = true;
this_variable.IsThis = true;
Variables.Add ("this", this_variable);
}
return this_variable;
}
public bool IsIterator {
get { return (flags & Flags.IsIterator) != 0; }
set { flags = value ? flags | Flags.IsIterator : flags & ~Flags.IsIterator; }
}
//
// Block has been converted to expression tree
//
public bool IsExpressionTree {
get { return (flags & Flags.IsExpressionTree) != 0; }
}
public bool IsThisAssigned (BlockContext ec)
{
return this_variable == null || this_variable.IsThisAssigned (ec, this);
}
public bool Resolve (FlowBranching parent, BlockContext rc, ParametersCompiled ip, IMethodData md)
{
if (resolved)
return true;
resolved = true;
if (rc.HasSet (ResolveContext.Options.ExpressionTreeConversion))
flags |= Flags.IsExpressionTree;
try {
if (!ResolveMeta (rc, ip))
return false;
using (rc.With (ResolveContext.Options.DoFlowAnalysis, true)) {
FlowBranchingToplevel top_level = rc.StartFlowBranching (this, parent);
if (!Resolve (rc))
return false;
unreachable = top_level.End ();
}
} catch (Exception e) {
if (e is CompletionResult || rc.Report.IsDisabled)
throw;
if (rc.CurrentBlock != null) {
rc.Report.Error (584, rc.CurrentBlock.StartLocation, "Internal compiler error: {0}", e.Message);
} else {
rc.Report.Error (587, "Internal compiler error: {0}", e.Message);
}
if (Report.DebugFlags > 0)
throw;
}
if (rc.ReturnType != TypeManager.void_type && !unreachable) {
if (rc.CurrentAnonymousMethod == null) {
rc.Report.Error (161, md.Location, "`{0}': not all code paths return a value", md.GetSignatureForError ());
return false;
} else if (!rc.CurrentAnonymousMethod.IsIterator) {
rc.Report.Error (1643, rc.CurrentAnonymousMethod.Location, "Not all code paths return a value in anonymous method of type `{0}'",
rc.CurrentAnonymousMethod.GetSignatureForError ());
return false;
}
}
return true;
}
bool ResolveMeta (BlockContext ec, ParametersCompiled ip)
{
int errors = ec.Report.Errors;
int orig_count = parameters.Count;
if (ip != null)
parameters = ip;
// Assert: orig_count != parameter.Count => orig_count == 0
if (orig_count != 0 && orig_count != parameters.Count)
throw new InternalErrorException ("parameter information mismatch");
int offset = Parent == null ? 0 : Parent.AssignableSlots;
for (int i = 0; i < orig_count; ++i) {
Parameter.Modifier mod = parameters.FixedParameters [i].ModFlags;
if ((mod & Parameter.Modifier.OUT) != Parameter.Modifier.OUT)
continue;
VariableInfo vi = new VariableInfo (ip, i, offset);
parameter_info [i].VariableInfo = vi;
offset += vi.Length;
}
ResolveMeta (ec, offset);
return ec.Report.Errors == errors;
}
//
// Check whether all `out' parameters have been assigned.
//
public void CheckOutParameters (FlowBranching.UsageVector vector, Location loc)
{
if (vector.IsUnreachable)
return;
int n = parameter_info == null ? 0 : parameter_info.Length;
for (int i = 0; i < n; i++) {
VariableInfo var = parameter_info [i].VariableInfo;
if (var == null)
continue;
if (vector.IsAssigned (var, false))
continue;
Report.Error (177, loc, "The out parameter `{0}' must be assigned to before control leaves the current method",
var.Name);
}
}
public override void Emit (EmitContext ec)
{
if (Report.Errors > 0)
return;
#if PRODUCTION
try {
#endif
EmitMeta (ec);
if (ec.HasReturnLabel)
ec.ReturnLabel = ec.DefineLabel ();
base.Emit (ec);
ec.Mark (EndLocation);
if (ec.HasReturnLabel)
ec.MarkLabel (ec.ReturnLabel);
if (ec.return_value != null) {
ec.Emit (OpCodes.Ldloc, ec.return_value);
ec.Emit (OpCodes.Ret);
} else {
//
// 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 || !unreachable) {
if (ec.ReturnType != TypeManager.void_type)
ec.Emit (OpCodes.Ldloc, ec.TemporaryReturn ());
ec.Emit (OpCodes.Ret);
}
}
#if PRODUCTION
} catch (Exception e){
Console.WriteLine ("Exception caught by the compiler while emitting:");
Console.WriteLine (" Block that caused the problem begin at: " + block.loc);
Console.WriteLine (e.GetType ().FullName + ": " + e.Message);
throw;
}
#endif
}
public override void EmitMeta (EmitContext ec)
{
// Avoid declaring an IL variable for this_variable since it is not accessed
// from the generated IL
if (this_variable != null)
Variables.Remove ("this");
base.EmitMeta (ec);
}
protected override void EmitSymbolInfo (EmitContext ec)
{
AnonymousExpression ae = ec.CurrentAnonymousMethod;
if ((ae != null) && (ae.Storey != null))
SymbolWriter.DefineScopeVariable (ae.Storey.ID);
base.EmitSymbolInfo (ec);
}
}
public class SwitchLabel {
Expression label;
object converted;
Location loc;
Label il_label;
bool il_label_set;
Label il_label_code;
bool il_label_code_set;
public static readonly object NullStringCase = new object ();
//
// if expr == null, then it is the default case.
//
public SwitchLabel (Expression expr, Location l)
{
label = expr;
loc = l;
}
public Expression Label {
get {
return label;
}
}
public Location Location {
get { return loc; }
}
public object Converted {
get {
return converted;
}
}
public Label GetILLabel (EmitContext ec)
{
if (!il_label_set){
il_label = ec.DefineLabel ();
il_label_set = true;
}
return il_label;
}
public Label GetILLabelCode (EmitContext ec)
{
if (!il_label_code_set){
il_label_code = ec.DefineLabel ();
il_label_code_set = true;
}
return il_label_code;
}
//
// Resolves the expression, reduces it to a literal if possible
// and then converts it to the requested type.
//
public bool ResolveAndReduce (ResolveContext ec, TypeSpec required_type, bool allow_nullable)
{
Expression e = label.Resolve (ec);
if (e == null)
return false;
Constant c = e as Constant;
if (c == null){
ec.Report.Error (150, loc, "A constant value is expected");
return false;
}
if (required_type == TypeManager.string_type && c.GetValue () == null) {
converted = NullStringCase;
return true;
}
if (allow_nullable && c.GetValue () == null) {
converted = NullStringCase;
return true;
}
c = c.ImplicitConversionRequired (ec, required_type, loc);
if (c == null)
return false;
converted = c.GetValue ();
return true;
}
public void Error_AlreadyOccurs (ResolveContext ec, TypeSpec switch_type, SwitchLabel collision_with)
{
string label;
if (converted == null)
label = "default";
else if (converted == NullStringCase)
label = "null";
else
label = converted.ToString ();
ec.Report.SymbolRelatedToPreviousError (collision_with.loc, null);
ec.Report.Error (152, loc, "The label `case {0}:' already occurs in this switch statement", label);
}
public SwitchLabel Clone (CloneContext clonectx)
{
return new SwitchLabel (label.Clone (clonectx), loc);
}
}
public class SwitchSection {
// An array of SwitchLabels.
public readonly List Labels;
public readonly Block Block;
public SwitchSection (List labels, Block block)
{
Labels = labels;
Block = block;
}
public SwitchSection Clone (CloneContext clonectx)
{
var cloned_labels = new List ();
foreach (SwitchLabel sl in cloned_labels)
cloned_labels.Add (sl.Clone (clonectx));
return new SwitchSection (cloned_labels, clonectx.LookupBlock (Block));
}
}
public class Switch : Statement {
public List Sections;
public Expression Expr;
///
/// Maps constants whose type type SwitchType to their SwitchLabels.
///
public IDictionary