private static void ExplCheck(IScriptCodeGen scg, Token errorAt, bool explicitAllowed, string oldString, string newString) { if (!explicitAllowed) { scg.ErrorMsg(errorAt, "must explicitly cast from " + oldString + " to " + newString); } }
/** * @brief Emit code that converts the top stack item from 'oldType' to 'newType' * @param scg = what script we are compiling * @param errorAt = token used for source location for error messages * @param oldType = type of item currently on the stack * @param newType = type to convert it to * @param explicitAllowed = false: only consider implicit casts * true: consider both implicit and explicit casts * @returns with code emitted for conversion (or error message output if not allowed, and stack left unchanged) */ public static void CastTopOfStack(IScriptCodeGen scg, Token errorAt, TokenType oldType, TokenType newType, bool explicitAllowed) { CastDelegate castDelegate; string oldString = oldType.ToString(); string newString = newType.ToString(); // 'key' -> 'bool' is the only time we care about key being different than string. if ((oldString == "key") && (newString == "bool")) { LSLUnwrap(scg, errorAt, oldType); scg.ilGen.Emit(errorAt, OpCodes.Call, keyToBoolMethodInfo); LSLWrap(scg, errorAt, newType); return; } // Treat key and string as same type for all other type casts. if (oldString == "key") { oldString = "string"; } if (newString == "key") { newString = "string"; } // If the types are the same, there is no conceptual casting needed. // However, there may be wraping/unwraping to/from the LSL wrappers. if (oldString == newString) { if (oldType.ToLSLWrapType() != newType.ToLSLWrapType()) { LSLUnwrap(scg, errorAt, oldType); LSLWrap(scg, errorAt, newType); } return; } // Script-defined classes can be cast up and down the tree. if ((oldType is TokenTypeSDTypeClass) && (newType is TokenTypeSDTypeClass)) { TokenDeclSDTypeClass oldSDTC = ((TokenTypeSDTypeClass)oldType).decl; TokenDeclSDTypeClass newSDTC = ((TokenTypeSDTypeClass)newType).decl; // implicit cast allowed from leaf toward root for (TokenDeclSDTypeClass sdtc = oldSDTC; sdtc != null; sdtc = sdtc.extends) { if (sdtc == newSDTC) { return; } } // explicit cast allowed from root toward leaf for (TokenDeclSDTypeClass sdtc = newSDTC; sdtc != null; sdtc = sdtc.extends) { if (sdtc == oldSDTC) { ExplCheck(scg, errorAt, explicitAllowed, oldString, newString); scg.ilGen.Emit(errorAt, OpCodes.Ldc_I4, newSDTC.sdTypeIndex); scg.ilGen.Emit(errorAt, OpCodes.Call, sdTypeClassCastClass2ClassMethodInfo); return; } } // not on same branch goto illcast; } // One script-defined interface type cannot be cast to another script-defined interface type, // unless the old interface declares that it implements the new interface. That proves that // the underlying object, no matter what type, implements the new interface. if ((oldType is TokenTypeSDTypeInterface) && (newType is TokenTypeSDTypeInterface)) { TokenDeclSDTypeInterface oldDecl = ((TokenTypeSDTypeInterface)oldType).decl; TokenDeclSDTypeInterface newDecl = ((TokenTypeSDTypeInterface)newType).decl; if (!oldDecl.Implements(newDecl)) { goto illcast; } scg.ilGen.Emit(errorAt, OpCodes.Ldstr, newType.ToString()); scg.ilGen.Emit(errorAt, OpCodes.Call, sdTypeClassCastObj2IFaceMethodInfo); return; } // A script-defined class type can be implicitly cast to a script-defined interface type that it // implements. The result is an array of delegates that give the class's implementation of the // various methods defined by the interface. if ((oldType is TokenTypeSDTypeClass) && (newType is TokenTypeSDTypeInterface)) { TokenDeclSDTypeClass oldSDTC = ((TokenTypeSDTypeClass)oldType).decl; int intfIndex; if (!oldSDTC.intfIndices.TryGetValue(newType.ToString(), out intfIndex)) { goto illcast; } scg.ilGen.Emit(errorAt, OpCodes.Ldfld, sdtcITableFieldInfo); scg.ilGen.Emit(errorAt, OpCodes.Ldc_I4, intfIndex); scg.ilGen.Emit(errorAt, OpCodes.Ldelem, typeof(Delegate[])); return; } // A script-defined interface type can be explicitly cast to a script-defined class type by // extracting the Target property from element 0 of the delegate array that is the interface // object and making sure it casts to the correct script-defined class type. // // But then only if the class type implements the interface type. if ((oldType is TokenTypeSDTypeInterface) && (newType is TokenTypeSDTypeClass)) { TokenTypeSDTypeInterface oldSDTI = (TokenTypeSDTypeInterface)oldType; TokenTypeSDTypeClass newSDTC = (TokenTypeSDTypeClass)newType; if (!newSDTC.decl.CanCastToIntf(oldSDTI.decl)) { goto illcast; } ExplCheck(scg, errorAt, explicitAllowed, oldString, newString); scg.ilGen.Emit(errorAt, OpCodes.Ldc_I4, newSDTC.decl.sdTypeIndex); scg.ilGen.Emit(errorAt, OpCodes.Call, sdTypeClassCastIFace2ClassMethodInfo); return; } // A script-defined interface type can be implicitly cast to object. if ((oldType is TokenTypeSDTypeInterface) && (newType is TokenTypeObject)) { return; } // An object can be explicitly cast to a script-defined interface. if ((oldType is TokenTypeObject) && (newType is TokenTypeSDTypeInterface)) { ExplCheck(scg, errorAt, explicitAllowed, oldString, newString); scg.ilGen.Emit(errorAt, OpCodes.Ldstr, newString); scg.ilGen.Emit(errorAt, OpCodes.Call, sdTypeClassCastObj2IFaceMethodInfo); return; } // Cast to void is always allowed, such as discarding value from 'i++' or function return value. if (newType is TokenTypeVoid) { scg.ilGen.Emit(errorAt, OpCodes.Pop); return; } // Cast from undef to object or script-defined type is always allowed. if ((oldType is TokenTypeUndef) && ((newType is TokenTypeObject) || (newType is TokenTypeSDTypeClass) || (newType is TokenTypeSDTypeInterface))) { return; } // Script-defined classes can be implicitly cast to objects. if ((oldType is TokenTypeSDTypeClass) && (newType is TokenTypeObject)) { return; } // Script-defined classes can be explicitly cast from objects and other script-defined classes. // Note that we must manually check that it is the correct SDTypeClass however because as far as // mono is concerned, all SDTypeClass's are the same. if ((oldType is TokenTypeObject) && (newType is TokenTypeSDTypeClass)) { ExplCheck(scg, errorAt, explicitAllowed, oldString, newString); scg.ilGen.Emit(errorAt, OpCodes.Ldc_I4, ((TokenTypeSDTypeClass)newType).decl.sdTypeIndex); scg.ilGen.Emit(errorAt, OpCodes.Call, sdTypeClassCastClass2ClassMethodInfo); return; } // Delegates can be implicitly cast to/from objects. if ((oldType is TokenTypeSDTypeDelegate) && (newType is TokenTypeObject)) { return; } if ((oldType is TokenTypeObject) && (newType is TokenTypeSDTypeDelegate)) { scg.ilGen.Emit(errorAt, OpCodes.Castclass, newType.ToSysType()); return; } // Some actual conversion is needed, see if it is in table of legal casts. string key = oldString + " " + newString; if (!legalTypeCasts.TryGetValue(key, out castDelegate)) { key = oldString + "*" + newString; if (!legalTypeCasts.TryGetValue(key, out castDelegate)) { goto illcast; } ExplCheck(scg, errorAt, explicitAllowed, oldString, newString); } // Ok, output cast. But make sure it is in native form without any LSL wrapping // before passing to our casting routine. Then if caller is expecting an LSL- // wrapped value on the stack upon return, wrap it up after our casting. LSLUnwrap(scg, errorAt, oldType); castDelegate(scg, errorAt); LSLWrap(scg, errorAt, newType); return; illcast: scg.ErrorMsg(errorAt, "illegal to cast from " + oldString + " to " + newString); if (!(oldType is TokenTypeVoid)) { scg.ilGen.Emit(errorAt, OpCodes.Pop); } scg.PushDefaultValue(newType); }