View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0000666 | LDMud 3.5 | Runtime | public | 2009-06-24 06:13 | 2017-10-04 19:35 |
Reporter | zesstra | Assigned To | zesstra | ||
Priority | normal | Severity | feature | Reproducibility | N/A |
Status | resolved | Resolution | open | ||
Target Version | 3.5.0 | ||||
Summary | 0000666: RfC: Type-checking at run-time | ||||
Description | Currently, types are checked at compile-time (if checked at all). One consequence is, that it is not possible to ensure that functions are only called with arguments of the correct type. Which is the reason for the existence of if (!stringp(arg) && !closurep(arg)) handle_error(); // e.g. raise_error(). at the beginning of many functions to ensure correct input data ('garbage in - garbage out'). I would like to discuss an optional type-checking at run-time (restricted to stuff which can't be checked at compile-time, e.g. function arguments during call_other()), which can be enabled by a pragma 'runtime_type_check' (Prequesite is save_types, which could IMHO be implicitly enabled then.) This way programmers don't have to check their arguments themselves but just rely on the correct types if they use '#pragma runtime_type_check'. We may have to check how big the run-time impact of such an optional check (for programs without that pragma) is. (If programs use the pragma and don't check in LPC, we improve performance.) | ||||
Tags | No tags attached. | ||||
related to | 0000072 | closed | clash between several features re. type-safety |
|
I think it's a good idea, especially for library code. Do we want to check only call_others or internal calls as well? For call_others the overhead is insignificant, but for internal calls it might have a small impact. On the other side the compile-time type checks are insufficient for internal calls (because of 'mixed' variables or overriding functions with other argument types). Do we want to check unsafe assignments to variables (a mixed or unknown value to a variable with a type != mixed)? |
|
I agree that this would be a major improvement. It would get rid of a lot of typechecking that is being done explicitely in the LPC-code right now. It would be very nice to have it for internal calls as well if it does not have a too severe impact on performance - nevertheless for call_other it seems even more important to me (for all that stuff that is handled by central applications that receive calls from a lot of programs of different coders). I personally like the idea of warnings at unsafe assignments though I would do that only if strong_types or even strict_types has been declared. |
|
Yes, I would also use it especially for library code. :-) Originally I thought, a check for inter-object calls would sufficient, but because of the behaviour Gnomi mentioned, Im afraid, we should check intra-object calls as well. Which contradicts my wish to decrease function call overhead for intra-object calls. :-( So I have no definite position on this yet. Warnings for 'unsafe' assignments might be a good idea, but we should do that in a third step and after evaluating the run-time impact. (I would see function calls/arguments as first step and definitely wrong assignments as second step.) I agree, that all of this should only be in effect for strong_types/strict_types, for programs using weak_types it is probably anyway useless (too much work). |
|
The really interesting question about performance is: would it be slower than the existing "if( !stringp(foo) && !closurep(foo) )"s? Another point: take the example above: foo would be declared as 'mixed' here, like eg. void bar(mixed foo) { if(...) ... } How shall the driver check a type against mixed? Strictly speaking if we want to get rid of that if() we would have to declare the function somewhat like void bar(string|closure foo) { ... } For any other type than mixed checking would be a nice feature, but OTOH it would be rather inconsistent simple because there *is* a 'mixed' type at all, that has to be checked (if at all) manually anyways. Looking at it that way the existing checks (e.g. at assignments from call_other) seem sufficient; the mere existence of 'mixed' makes LPC not inherently typesafe, thus there is no "clean" way to introduce type checking... |
|
Doing the check in C will be significantly faster (at least an order of magnitude, I guess) than in LPC. However, not all functions check their argument types. Some wizards forget it, some are too lazy (both would profit in theory), some don't think it is necessary for the specific function. I admit, my example above it not the best one, because that function argument would be mixed and then checks of the driver are pointless/not possible. A syntax like void bar(string|closure foo) would be nice for this, but (because IMHO only a tiny fraction of functions use 'mixed' arguments) not a prerequisite for a run-time type check to be useful. I don't think it is inconsistent. If you deliberately use 'mixed' you just tell the driver that you don't care (this time) and that the driver should not care, thats all. Why should it be inconsistent to check all other types, only because you have a type which can hold any type? If I don't use mixed (I think I wrote only 5-10 functions accepting 'mixed' in the last years) and have a string argument, I would still be happy if the driver takes care that my function is only called with a string argument. |
|
We use mixed very often for functions that offer different ways of calling. I agree this is not 100% clean either, but our coders are used to it :-) And of course in that case typechecking is up to the coder anyways. |
|
What about the zero? (as an unitialized variable or destructed object) Should it be treated like an int like in many error messages or as a special case of the declared argument variable type? example: void dance(object who); ... object ob; dance(&ob); // unitialized, but maybe I want to get a referenced object back ob= ...; destruct(ob); dance(ob); // destructed object, syntactically this is still type 'object' |
|
Zero has to be considered as an element of every type. Otherwise this would break a lot of code. |
|
I really really really really want this ;-) My whole LIB contains a LOT of checks just for types. We introduced a system like this for every lib method in this way: void dance(object who, int foo) { check(who, IS_OBJECT, foo, IS_INT); ... } It produces something like 'Bad argument 2 to dance(): expected 'int' but got 'object'.' its OK, but i would love to get rid of these ;-) |
|
Actually, I started working on these checks yesterday on a private branch. There is still quite a lot to discuss and decide, but some first patches will probably find their way into trunk during the next weeks. The first problem is that svalues and vartype_s/fulltype_s (typeid_t, typeflags_t) use very different schemes to encode types and modifiers. |
|
I had a look today at the possibility for type-checks on variable assignments. This is not so easy, because the compiler just generates F_ASSIGN/F_VOID_ASSIGN, which transfer/copy data from one svalue to another. In this case, there is no data available to decide, which type the destination svalue should have. In the case of global variables, this data at leasts exists as variable_t.type in ob->prog->variables, but in case of local vars, this information is lost after compilation. So we have to make the type info available first. Therefore I will concentrate first on type-checks upon function calls. |
|
ok. experimentally in 3.5. (trunk) now: type checks at runtime are enabled by a pragma 'rtt_checks'. Currently the argument types upon function calls are checked. The checks depend on the pragma set in the defining program, not in the inheritee. If you like to test it, I am happy about feedback. But keep in mind, that it is experimental and there are surely errors. |
|
I added checks for the correct return types when returning from lfuns and sefuns. Interestingly, this caused more errors in our mudlib so far than the checks for function arguments... ;-) |
|
Bardioc notified me that it would be better to accept structs having the declared one as a base. ;-) So r2793 changed that behaviour and does not check to type identity anymore. |
|
restore_object() now checks the types of restored variables (if the restored entity from the savefile has the same type as the variable the data was restored to). Similarly restore_struct() checks the types of the restored data with the declared types of its members. |
Date Modified | Username | Field | Change |
---|---|---|---|
2009-06-24 06:13 | zesstra | New Issue | |
2009-06-24 06:13 | zesstra | Relationship added | related to 0000072 |
2009-06-24 08:09 | Gnomi | Note Added: 0001233 | |
2009-06-24 08:20 | Sorcerer | Note Added: 0001234 | |
2009-06-24 09:08 | zesstra | Note Added: 0001235 | |
2009-06-24 17:28 | invisible | Note Added: 0001236 | |
2009-06-24 17:32 | invisible | Note Edited: 0001236 | |
2009-06-25 02:04 | zesstra | Note Added: 0001237 | |
2009-06-25 17:22 | invisible | Note Added: 0001238 | |
2009-06-26 00:46 | _xtian_ | Note Added: 0001239 | |
2009-06-26 00:56 | Gnomi | Note Added: 0001240 | |
2009-09-04 09:57 | Bardioc | Note Added: 0001250 | |
2009-09-04 10:14 | zesstra | Note Added: 0001251 | |
2009-10-28 10:28 | zesstra | Note Added: 0001568 | |
2009-10-28 10:28 | zesstra | Assigned To | => zesstra |
2009-10-28 10:28 | zesstra | Status | new => assigned |
2009-10-31 09:23 | zesstra | Note Added: 0001575 | |
2009-11-01 15:44 | zesstra | Note Added: 0001579 | |
2009-11-02 15:22 | zesstra | Note Added: 0001583 | |
2009-11-04 17:22 | zesstra | Note Added: 0001602 | |
2010-03-27 05:22 | zesstra | Target Version | => 3.5.0 |
2017-10-04 19:35 | zesstra | Status | assigned => resolved |