private RuntimeBinderException ReportReadOnlyError(ExprField field, CheckLvalueKind kind, bool isNested) { Debug.Assert(field != null); bool isStatic = field.FieldWithType.Field().isStatic; int index = (isNested ? 4 : 0) + (isStatic ? 2 : 0) + (kind == CheckLvalueKind.OutParameter ? 0 : 1); ErrorCode err = s_ReadOnlyErrors[index]; return(ErrorContext.Error(err, isNested ? new ErrArg[] { field.FieldWithType } : Array.Empty <ErrArg>())); }
protected void ReportLocalError(LocalVariableSymbol local, CheckLvalueKind kind, bool isNested) { Debug.Assert(local != null); int index = kind == CheckLvalueKind.OutParameter ? 0 : 1; Debug.Assert(index != 2 && index != 3); // There is no way that we can have no cause AND a read-only local nested in a struct with a // writable field. What would make the local read-only if not one of the causes above? (Const // locals may not be structs, so we would already have errored out in that scenario.) ErrorCode err = s_ReadOnlyLocalErrors[index]; ErrorContext.Error(err, local.name); }
private RuntimeBinderException ReportLocalError(LocalVariableSymbol local, CheckLvalueKind kind, bool isNested) { Debug.Assert(local != null); int index = kind == CheckLvalueKind.OutParameter ? 0 : 1; Debug.Assert(index != 2 && index != 3); // There is no way that we can have no cause AND a read-only local nested in a struct with a // writable field. What would make the local read-only if not one of the causes above? (Const // locals may not be structs, so we would already have errored out in that scenario.) ErrorCode err = s_ReadOnlyLocalErrors[index]; return(ErrorContext.Error(err, local.name)); }
protected void ReportReadOnlyError(EXPRFIELD field, CheckLvalueKind kind, bool isNested) { Debug.Assert(field != null); bool isStatic = field.fwt.Field().isStatic; int index = (isNested ? 4 : 0) + (isStatic ? 2 : 0) + (kind == CheckLvalueKind.OutParameter ? 0 : 1); ErrorCode err = s_ReadOnlyErrors[index]; if (isNested) { ErrorContext.Error(err, field.fwt); } else { ErrorContext.Error(err); } }
protected void ReportReadOnlyError(EXPRFIELD field, CheckLvalueKind kind, bool isNested) { Debug.Assert(field != null); bool isStatic = field.fwt.Field().isStatic; int index = (isNested ? 4 : 0) + (isStatic ? 2 : 0) + (kind == CheckLvalueKind.OutParameter ? 0 : 1); ErrorCode err = s_ReadOnlyErrors[index]; if (isNested) { ErrorContext.Error(err, field.fwt); } else { ErrorContext.Error(err); } }
private void ReportReadOnlyError(ExprField field, CheckLvalueKind kind, bool isNested) { Debug.Assert(field != null); bool isStatic = field.FieldWithType.Field().isStatic; int index = (isNested ? 4 : 0) + (isStatic ? 2 : 0) + (kind == CheckLvalueKind.OutParameter ? 0 : 1); ErrorCode err = s_ReadOnlyErrors[index]; if (isNested) { ErrorContext.Error(err, field.FieldWithType); } else { ErrorContext.Error(err); } }
// Return true if we actually report a failure. private void TryReportLvalueFailure(Expr expr, CheckLvalueKind kind) { Debug.Assert(expr != null); // We have a lvalue failure. Was the reason because this field // was marked readonly? Give special messages for this case. bool isNested = false; // Did we recurse on a field or property to give a better error? while (true) { Debug.Assert(expr != null); if (expr is ExprLocal local && local.IsOK) { throw ReportLocalError(local.Local, kind, isNested); } Expr pObject = null; if (expr is ExprProperty prop) { // We've already reported read-only-property errors. Debug.Assert(prop.MethWithTypeSet != null); pObject = prop.MemberGroup.OptionalObject; } else if (expr is ExprField field) { if (field.FieldWithType.Field().isReadOnly) { throw ReportReadOnlyError(field, kind, isNested); } if (!field.FieldWithType.Field().isStatic) { pObject = field.OptionalObject; } } if (pObject != null && pObject.Type.isStructOrEnum()) { if (pObject is IExprWithArgs withArgs) { // assigning to RHS of method or property getter returning a value-type on the stack or // passing RHS of method or property getter returning a value-type on the stack, as ref or out throw ErrorContext.Error(ErrorCode.ERR_ReturnNotLValue, withArgs.GetSymWithType()); } if (pObject is ExprCast) { // An unboxing conversion. // // In the static compiler, we give the following error here: // ErrorContext.Error(pObject.GetTree(), ErrorCode.ERR_UnboxNotLValue); // // But in the runtime, we allow this - mark that we're doing an // unbox here, so that we gen the correct expression tree for it. pObject.Flags |= EXPRFLAG.EXF_UNBOXRUNTIME; return; } } // everything else if (pObject != null && !pObject.isLvalue() && (expr is ExprField || (!isNested && expr is ExprProperty))) { Debug.Assert(pObject.Type.isStructOrEnum()); expr = pObject; } else { throw ErrorContext.Error(GetStandardLvalueError(kind)); } isNested = true; } }
// Return true if we actually report a failure. protected bool TryReportLvalueFailure(EXPR expr, CheckLvalueKind kind) { Debug.Assert(expr != null); // We have a lvalue failure. Was the reason because this field // was marked readonly? Give special messages for this case. bool isNested = false; // Did we recurse on a field or property to give a better error? EXPR walk = expr; while (true) { Debug.Assert(walk != null); if (walk.isANYLOCAL_OK()) { ReportLocalError(walk.asANYLOCAL().local, kind, isNested); return(true); } EXPR pObject = null; if (walk.isPROP()) { // We've already reported read-only-property errors. Debug.Assert(walk.asPROP().mwtSet != null); pObject = walk.asPROP().GetMemberGroup().GetOptionalObject(); } else if (walk.isFIELD()) { EXPRFIELD field = walk.asFIELD(); if (field.fwt.Field().isReadOnly) { ReportReadOnlyError(field, kind, isNested); return(true); } if (!field.fwt.Field().isStatic) { pObject = field.GetOptionalObject(); } } if (pObject != null && pObject.type.isStructOrEnum()) { if (pObject.isCALL() || pObject.isPROP()) { // assigning to RHS of method or property getter returning a value-type on the stack or // passing RHS of method or property getter returning a value-type on the stack, as ref or out ErrorContext.Error(ErrorCode.ERR_ReturnNotLValue, pObject.GetSymWithType()); return(true); } if (pObject.isCAST()) { // An unboxing conversion. // // In the static compiler, we give the following error here: // ErrorContext.Error(pObject.GetTree(), ErrorCode.ERR_UnboxNotLValue); // // But in the runtime, we allow this - mark that we're doing an // unbox here, so that we gen the correct expression tree for it. pObject.flags |= EXPRFLAG.EXF_UNBOXRUNTIME; return(false); } } // everything else if (pObject != null && !pObject.isLvalue() && (walk.isFIELD() || (!isNested && walk.isPROP()))) { Debug.Assert(pObject.type.isStructOrEnum()); walk = pObject; } else { ErrorContext.Error(GetStandardLvalueError(kind)); return(true); } isNested = true; } }
//////////////////////////////////////////////////////////////////////////////// // A false return means not to process the expr any further - it's totally out // of place. For example - a method group or an anonymous method. internal bool checkLvalue(EXPR expr, CheckLvalueKind kind) { if (!expr.isOK()) return false; if (expr.isLvalue()) { if (expr.isPROP()) { CheckLvalueProp(expr.asPROP()); } markFieldAssigned(expr); return true; } switch (expr.kind) { case ExpressionKind.EK_PROP: if (kind == CheckLvalueKind.OutParameter) { // passing a property as ref or out ErrorContext.Error(ErrorCode.ERR_RefProperty); return true; } if (!expr.asPROP().mwtSet) { // Assigning to a property without a setter. // If we have // bool? b = true; (bool)b = false; // then this is realized immediately as // b.Value = false; // and no ExpressionKind.EK_CAST is generated. We'd rather not give a "you're writing // to a read-only property" error in the case where the property access // is not explicit in the source code. Fortunately in this case the // cast is still hanging around in the parse tree, so we can look for it. // POSSIBLE ERROR: It would be nice to also give this error for other situations // POSSIBLE ERROR: in which the user is attempting to assign to a value, such as // POSSIBLE ERROR: an explicit (bool)b.Value = false; // POSSIBLE ERROR: Unfortunately we cannot use this trick in that situation because // POSSIBLE ERROR: we've already discarded the OperatorKind.OP_CAST node. (This is an SyntaxKind.Dot). // SPEC VIOLATION: More generally: // SPEC VIOLATION: The spec states that the result of any cast is a value, not a // SPEC VIOLATION: variable. Unfortunately we do not correctly implement this // SPEC VIOLATION: and we probably should not start implementing it because this // SPEC VIOLATION: would be a breaking change. We currently discard "no op" casts // SPEC VIOLATION: very aggressively rather than generating an ExpressionKind.EK_CAST node. ErrorContext.Error(ErrorCode.ERR_AssgReadonlyProp, expr.asPROP().pwtSlot); return true; } break; case ExpressionKind.EK_ARRAYLENGTH: if (kind == CheckLvalueKind.OutParameter) { // passing a property as ref or out ErrorContext.Error(ErrorCode.ERR_RefProperty); } else { // Special case, the length property of an array ErrorContext.Error(ErrorCode.ERR_AssgReadonlyProp, GetSymbolLoader().getPredefinedMembers().GetProperty(PREDEFPROP.PP_ARRAY_LENGTH)); } return true; case ExpressionKind.EK_BOUNDLAMBDA: case ExpressionKind.EK_UNBOUNDLAMBDA: case ExpressionKind.EK_CONSTANT: ErrorContext.Error(GetStandardLvalueError(kind)); return false; case ExpressionKind.EK_MEMGRP: { ErrorCode err = (kind == CheckLvalueKind.OutParameter) ? ErrorCode.ERR_RefReadonlyLocalCause : ErrorCode.ERR_AssgReadonlyLocalCause; ErrorContext.Error(err, expr.asMEMGRP().name, new ErrArgIds(MessageID.MethodGroup)); return false; } default: break; } return !TryReportLvalueFailure(expr, kind); }
private static ErrorCode GetStandardLvalueError(CheckLvalueKind kind) { switch (kind) { default: VSFAIL("bad kind"); return ErrorCode.ERR_AssgLvalueExpected; case CheckLvalueKind.Assignment: return ErrorCode.ERR_AssgLvalueExpected; case CheckLvalueKind.OutParameter: return ErrorCode.ERR_RefLvalueExpected; case CheckLvalueKind.Increment: return ErrorCode.ERR_IncrementLvalueExpected; } }
// Return true if we actually report a failure. protected bool TryReportLvalueFailure(EXPR expr, CheckLvalueKind kind) { Debug.Assert(expr != null); // We have a lvalue failure. Was the reason because this field // was marked readonly? Give special messages for this case. bool isNested = false; // Did we recurse on a field or property to give a better error? EXPR walk = expr; while (true) { Debug.Assert(walk != null); if (walk.isANYLOCAL_OK()) { ReportLocalError(walk.asANYLOCAL().local, kind, isNested); return true; } EXPR pObject = null; if (walk.isPROP()) { // We've already reported read-only-property errors. Debug.Assert(walk.asPROP().mwtSet != null); pObject = walk.asPROP().GetMemberGroup().GetOptionalObject(); } else if (walk.isFIELD()) { EXPRFIELD field = walk.asFIELD(); if (field.fwt.Field().isReadOnly) { ReportReadOnlyError(field, kind, isNested); return true; } if (!field.fwt.Field().isStatic) { pObject = field.GetOptionalObject(); } } if (pObject != null && pObject.type.isStructOrEnum()) { if (pObject.isCALL() || pObject.isPROP()) { // assigning to RHS of method or property getter returning a value-type on the stack or // passing RHS of method or property getter returning a value-type on the stack, as ref or out ErrorContext.Error(ErrorCode.ERR_ReturnNotLValue, pObject.GetSymWithType()); return true; } if (pObject.isCAST()) { // An unboxing conversion. // // In the static compiler, we give the following error here: // ErrorContext.Error(pObject.GetTree(), ErrorCode.ERR_UnboxNotLValue); // // But in the runtime, we allow this - mark that we're doing an // unbox here, so that we gen the correct expression tree for it. pObject.flags |= EXPRFLAG.EXF_UNBOXRUNTIME; return false; } } // everything else if (pObject != null && !pObject.isLvalue() && (walk.isFIELD() || (!isNested && walk.isPROP()))) { Debug.Assert(pObject.type.isStructOrEnum()); walk = pObject; } else { ErrorContext.Error(GetStandardLvalueError(kind)); return true; } isNested = true; } }