Open skunkiferous opened 3 years ago
Yes I agree that it would be good to add it. I just don't know how I can easily enforce this at a call site. But I agree it should be possible to check the function itself / or insert type assertions on return values.
I've achieved some success on trying to implement that.
lparser.c
static void body (LexState *ls, expdesc *e, int ismethod, int line, int deferred) {
/* body -> '(' parlist ')' block END */
FuncState new_fs;
BlockCnt bl;
new_fs.f = addprototype(ls);
new_fs.f->linedefined = line;
open_func(ls, &new_fs, &bl);
if (!deferred) {
checknext(ls, '(');
if (ismethod) {
new_localvarliteral(ls, "self"); /* create 'self' parameter */
adjustlocalvars(ls, 1);
}
parlist(ls);
checknext(ls, ')');
}
// here we try to find expected return value(s) types
if (testnext(ls, ':')) {
TString *typename = str_checkname(ls);
char* str = getstr(typename); // our type is here, what to do with it?
/* maybe we should use this to support typechecking on multiple return values */
// expdesc e = {.ravi_type_map = RAVI_TM_ANY, .pc = -1};
// int nexps = explist(ls, &e);
/* we should also put function (static int explist (LexState *ls, expdesc *v)) behind current function, otherwise compile error */
}
statlist(ls);
new_fs.f->lastlinedefined = ls->linenumber;
check_match(ls, TK_END, TK_FUNCTION, line);
codeclosure(ls, e, deferred);
close_func(ls);
}
So, I've successfully parsed return type annotation(s), but have no idea what to do with it next. I've found some mentions on that in function
static void ravi_typecheck(LexState *ls, expdesc *v, int *vars, int nvars, int n)
...
int nrets = GETARG_C(*pc) - 1; /* operand C contains
number of return values expected */
/* Note that at this stage nrets is always 1
* - as Lua patches in the this value for the last
* function call in a variable declaration statement
* in adjust_assign and localvar_adjust_assign */
/* all return values that are going to be assigned
to typed local vars must be converted to the correct type */
int i;
for (i = n; i < (n+nrets); i++)
/* do we need to convert ? */
Maybe we can check return values somewhere around that. But we also have to store expected return types somewhere (the ones we got on previous step)
I still poorly understand the internal logic, so maybe @dibyendumajumdar will tell me what to do next?
Thanks
What we need:
return
statementMaybe for single return values
function foo() : type
end
For multiple values:
function foo() : (type1, type2, type3)
end
The parser uses FuncState
structure to hold details of the current function being compiled. We probably should add there an array of return types - including the typename for user data types. As we parse we need to store the types in the FuncState
structure.
return
op code. This can be done in luaK_ret
where we generate OP_RETURN
. We need to go through all of the values and check that types match the function signature and generate TO*
opcodes against each register when we cannot check at compile time that the type is correct.
Note that some return values may be constants, so we cannot do any conversions when this is true - or we need to move the constant to a temp register before generating TOINT/TOFLT opcodes.
function foo(): (integer, integer)
return 4,2
end
The proposal above validates the return values in the return
statement.
There is no change at callsite. This is harder problem because at callsite we only know at runtime what function we will be calling. We will probably need to store the type signature in Proto
structure if any runtime callsite optimzation/checks need to be done. For now, this should be out of scope.
AFAIK, function return-type are normally specified in statically-typed language for (at least) 3 reasons:
In the manual, you say the following:
Function return types cannot be annotated because in Lua, functions are un-named values and there is no reliable way for a static analysis of a function call’s return value.
But this only apply to #3. #1 and #2 are still valid reasons to annotate the return type. I already know I would write all my functions like this:
This is silly. What I really want, is to write them like this:
Even if the annotation was not validated at all, just for documentation purpose alone I think it is worth having it.