WIP: Improve type checking records#141
Conversation
My main intention is to catch undefined record and record field, the precise type is an "if we can, why not" extra.
It is possible to override/refine the types provided in the record
definitions.
So far Gradualizer only handled types like `#rec{}` - now it can also
handle types like `#rec{f1 :: t()}`. But currently it ignores the
refined field type information.
|
It makes sense to me that we should not need to do the same checks as the compiler already does. With that said, warnings are not errors and Gradualizer should not reject typechecking a module just because there are unexported functions (it should even check unexported functions in my opinion). |
zuiderkwast
left a comment
There was a problem hiding this comment.
Very nice stuff, especially the precise types.
"if we can, why not"
Well, OK, if it doesn't add complexity.
For milestone 1, I think we should really focus on code that compiles without a warning or error. With this reasoning we can postpone things like variable shadowing because it gives a compiler warning by default.
Have you considered #40 as well? I seems to be related.
| type_check_expr(Env, {record_index, Anno, Name, FieldNameAtom}) -> | ||
| %% we still fetch record field index even if infer=false | ||
| %% to be able to report undefined record or record field. | ||
| %% ("we do our best for the customer") |
There was a problem hiding this comment.
In other words, we're checking things that we don't need, to be able to report an error that the compiler would also report.
In principle, I think it's the wrong approach, but in this case, I don't see that there might be any performance impact and the complexity of the code is the same either way, so keep it!
Reporting an exact type for record index when inference is on is very good!
| -export([f/0, g/0]). | ||
| -export([f/0, g/0, | ||
| undefined_record/0, | ||
| undefined_record_infer/0, |
There was a problem hiding this comment.
These are not automatically tested as only one fail per module is enough for should_fail. We should really fix #129.
| end. | ||
|
|
||
| expect_record_type(Record, {type, _, record, [{atom, _, Name}]}, _TEnv) -> | ||
| expect_record_type(Record, {type, _, record, [{atom, _, Name}|_]}, _TEnv) -> |
There was a problem hiding this comment.
In general, please add a TODO comment when adding code to ignore unimplemented stuff.
|
thank you guys for the feedback. I split this into multiple PRs
|
|
closing this as usable parts are merged in separate PRs. There is work left in refined record types |
This is a work in progress. I have two questions:
I started to improve handling undefined records and record fields. But then I started to think how Gradualizer is used. In case it will be used as a parse transform (that takes place after pre-processing but before any other compiler steps) then sure these errors can happen. However if Gradualizer is used as other steps like xref, eunit, dialyzer, they are run after successful compilation. If Gradualizer works from a beam file this is surely that case. So why duplicate checks that the compiler already does. Maybe Gradualizer could just run the erlang linter (after epp) in case it works from a source file and the rest of the code can assume there are no linting errors in the AST. What do you think? (There are eg unexported functions in the should_pass examples which result in compiler warnings but Gradualizer happily checks them. I introduced undefined records but compilation fails, that's how I started to think about this)
It is possible to override/refine the types provided in the record definitions in types. eg:
In this case we could make use somehow of the
boolean()type. Either type-checkis_integer/1both againstatom()(from the record definition) and againstboolean()(from the expected type) ie the intersection ofatom()andboolean(). Or infer the type of the whole record update and check that against the wholeResTy.So in general I think there are multiple flows how to verify the extra info in type-inference and type-checking flows.
Also I'm not sure if this syntax is a pure override or field type (in the above case
boolean()) must strictly be a subtype of the type from the rec def (ieatom()) and whether this also needs to be checked?