Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inference of lambda type and array element type from an antecedent (RFC 45). #2168

Merged
merged 16 commits into from
Sep 13, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pony.g
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ nextatom
| 'this'
| literal
| LPAREN_NEW rawseq tuple? ')'
| LSQUARE_NEW ('as' type ':')? rawseq ']'
| LSQUARE_NEW ('as' type ':')? rawseq? ']'
| 'object' ('\\' ID (',' ID)* '\\')? cap? ('is' type)? members 'end'
| '{' ('\\' ID (',' ID)* '\\')? cap? ID? typeparams? ('(' | LPAREN_NEW) params? ')' lambdacaptures? (':' type)? '?'? '=>' rawseq '}' cap?
| '@{' ('\\' ID (',' ID)* '\\')? cap? ID? typeparams? ('(' | LPAREN_NEW) params? ')' lambdacaptures? (':' type)? '?'? '=>' rawseq '}' cap?
Expand All @@ -219,7 +219,7 @@ atom
| 'this'
| literal
| ('(' | LPAREN_NEW) rawseq tuple? ')'
| ('[' | LSQUARE_NEW) ('as' type ':')? rawseq ']'
| ('[' | LSQUARE_NEW) ('as' type ':')? rawseq? ']'
| 'object' ('\\' ID (',' ID)* '\\')? cap? ('is' type)? members 'end'
| '{' ('\\' ID (',' ID)* '\\')? cap? ID? typeparams? ('(' | LPAREN_NEW) params? ')' lambdacaptures? (':' type)? '?'? '=>' rawseq '}' cap?
| '@{' ('\\' ID (',' ID)* '\\')? cap? ID? typeparams? ('(' | LPAREN_NEW) params? ')' lambdacaptures? (':' type)? '?'? '=>' rawseq '}' cap?
Expand Down
8 changes: 4 additions & 4 deletions src/libponyc/ast/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -421,23 +421,23 @@ DEF(arraytype);
SKIP(NULL, TK_COLON);
DONE();

// (LSQUARE | LSQUARE_NEW) rawseq {COMMA rawseq} RSQUARE
// (LSQUARE | LSQUARE_NEW) [rawseq] RSQUARE
DEF(array);
PRINT_INLINE();
AST_NODE(TK_ARRAY);
SKIP(NULL, TK_LSQUARE, TK_LSQUARE_NEW);
OPT RULE("element type", arraytype);
RULE("array elements", rawseq);
OPT RULE("array elements", rawseq);
TERMINATE("array literal", TK_RSQUARE);
DONE();

// LSQUARE_NEW rawseq {COMMA rawseq} RSQUARE
// LSQUARE_NEW rawseq [rawseq] RSQUARE
DEF(nextarray);
PRINT_INLINE();
AST_NODE(TK_ARRAY);
SKIP(NULL, TK_LSQUARE_NEW);
OPT RULE("element type", arraytype);
RULE("array elements", rawseq);
OPT RULE("array elements", rawseq);
TERMINATE("array literal", TK_RSQUARE);
DONE();

Expand Down
2 changes: 1 addition & 1 deletion src/libponyc/ast/treecheckdef.h
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ RULE(lambda_capture,

RULE(array_literal,
CHILD(type, none)
ONE_OR_MORE(rawseq),
CHILD(rawseq, none),
TK_ARRAY);

RULE(object_literal,
Expand Down
252 changes: 252 additions & 0 deletions src/libponyc/expr/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,259 @@
#include "../pass/refer.h"
#include "../type/alias.h"
#include "../type/assemble.h"
#include "../type/reify.h"
#include "../type/subtype.h"
#include "../type/lookup.h"
#include "ponyassert.h"

static ast_t* build_array_type(ast_t* scope, ast_t* elem_type, token_id cap)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason to use this new function instead of type_builtin_args?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know about that function.

In 943c1ba I refactored build_array_type to use type_builtin_args as part of its implementation.

{
elem_type = ast_dup(elem_type);

BUILD(array_type, elem_type,
NODE(TK_NOMINAL,
ID("$0")
ID("Array")
NODE(TK_TYPEARGS, TREE(elem_type))
NODE(cap)
NODE(TK_EPHEMERAL)
NODE(TK_NONE)));

// Link the nominal type to the entity definition for Array.
ast_setdata(array_type, ast_get(scope, stringtab("Array"), NULL));

return array_type;
}

static void find_possible_element_types(pass_opt_t* opt, ast_t* ast,
astlist_t** list)
{
switch(ast_id(ast))
{
case TK_NOMINAL:
{
AST_GET_CHILDREN(ast, package, name, typeargs, cap, eph);

// If it's an actual Array type, note it as a possibility and move on.
if(stringtab("Array") == ast_name(name))
{
*list = astlist_push(*list, ast_child(typeargs));
return;
}

// Otherwise, an Array-matching type must be an interface.
ast_t* def = (ast_t*)ast_data(ast);
if((def == NULL) || (ast_id(def) != TK_INTERFACE))
return;

// The interface must have an apply method for us to find it.
ast_t* apply = ast_get(def, stringtab("apply"), NULL);
if((apply == NULL) || (ast_id(apply) != TK_FUN))
return;

// The apply method must match the signature we're expecting.
AST_GET_CHILDREN(apply, receiver_cap, apply_name, type_params, params,
ret_type, question);
if((ast_id(receiver_cap) != TK_BOX) ||
(ast_id(type_params) != TK_NONE) ||
(ast_childcount(params) != 1) ||
(ast_id(question) != TK_QUESTION))
return;

ast_t* param = ast_child(params);
ast_t* param_type = ast_childidx(param, 1);
if(ast_name(ast_childidx(param_type, 1)) != stringtab("USize"))
return;

// Based on the apply method we try to figure out the element type.
ast_t* elem_type = ret_type;

ast_t* typeparams = ast_childidx(def, 1);
if(ast_id(typeparams) == TK_TYPEPARAMS)
elem_type = reify(ret_type, typeparams, typeargs, opt, true);

if((ast_id(elem_type) == TK_ARROW) &&
(ast_id(ast_child(elem_type)) == TK_THISTYPE))
elem_type = ast_childidx(elem_type, 1);

// Construct a guess of the corresponding Array type.
// Use iso^ so that the object cap isn't a concern for subtype checking.
ast_t* array_type = build_array_type(ast, elem_type, TK_ISO);

// The guess type must be a subtype of the interface type.
if(!is_subtype(array_type, ast, NULL, opt))
{
ast_free_unattached(array_type);
return;
}

ast_free_unattached(array_type);

// Note this as a possible element type and move on.
*list = astlist_push(*list, elem_type);
}

case TK_ARROW:
find_possible_element_types(opt, ast_childidx(ast, 1), list);
return;

case TK_TYPEPARAMREF:
{
ast_t* def = (ast_t*)ast_data(ast);
pony_assert(ast_id(def) == TK_TYPEPARAM);
find_possible_element_types(opt, ast_childidx(def, 1), list);
return;
}

case TK_UNIONTYPE:
case TK_ISECTTYPE:
{
for(ast_t* c = ast_child(ast); c != NULL; c = ast_sibling(c))
find_possible_element_types(opt, c, list);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd feel more comfortable with a break or return here, to avoid problems in future refactorings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Fixed in 943c1ba.


default:
break;
}
}

static bool infer_element_type(pass_opt_t* opt, ast_t* ast,
ast_t** type_spec_p, ast_t* antecedent_type)
{
// List the element types of all array-matching types in the antecedent type.
astlist_t* possible_element_types = NULL;
find_possible_element_types(opt, antecedent_type, &possible_element_types);

// If there's more than one possible element type, test against the elements,
// creating a new list containing only the possibilities that are supertypes.
if(astlist_length(possible_element_types) > 1)
{
astlist_t* new_list = NULL;

astlist_t* cursor = possible_element_types;
for(; cursor != NULL; cursor = astlist_next(cursor))
{
bool supertype_of_all = true;
ast_t* elem = ast_child(ast_childidx(ast, 1));
for(; elem != NULL; elem = ast_sibling(elem))
{
// Catch up the elem to the expr pass, so we can get its type.
if(ast_visit(&elem, pass_pre_expr, pass_expr, opt, PASS_EXPR) != AST_OK)
return false;

ast_t* elem_type = ast_type(elem);
if(is_typecheck_error(elem_type) || ast_id(elem_type) == TK_LITERAL)
break;

ast_t* a_type = alias(elem_type);

if(!is_subtype(a_type, astlist_data(cursor), NULL, opt))
supertype_of_all = false;

ast_free_unattached(a_type);
}

if(supertype_of_all)
new_list = astlist_push(new_list, astlist_data(cursor));
}

astlist_free(possible_element_types);
possible_element_types = new_list;
}

// If there's still more than one possible element type, choose the most
// specific type (removing types that are supertypes of one or more others).
if(astlist_length(possible_element_types) > 1)
{
astlist_t* new_list = NULL;

astlist_t* super_cursor = possible_element_types;
for(; super_cursor != NULL; super_cursor = astlist_next(super_cursor))
{
bool supertype_of_any = false;

astlist_t* sub_cursor = possible_element_types;
for(; sub_cursor != NULL; sub_cursor = astlist_next(sub_cursor))
{
if((sub_cursor != super_cursor) && is_subtype(astlist_data(sub_cursor),
astlist_data(super_cursor), NULL, opt))
supertype_of_any = true;
}

if(!supertype_of_any)
new_list = astlist_push(new_list, astlist_data(super_cursor));
}

astlist_free(possible_element_types);
possible_element_types = new_list;
}

// If there's exactly one possible element type remaining, use it.
if(astlist_length(possible_element_types) == 1)
ast_replace(type_spec_p, astlist_data(possible_element_types));

return true;
}

ast_result_t expr_pre_array(pass_opt_t* opt, ast_t** astp)
{
ast_t* ast = *astp;

pony_assert(ast_id(ast) == TK_ARRAY);
AST_GET_CHILDREN(ast, type_spec, elements);

// Try to find an antecedent type, or bail out if none was found.
bool is_recovered = false;
ast_t* antecedent_type = find_antecedent_type(opt, ast, &is_recovered);
if(antecedent_type == NULL)
return AST_OK;

// If we don't have an explicit element type, try to infer it.
if(ast_id(type_spec) == TK_NONE)
{
if(!infer_element_type(opt, ast, &type_spec, antecedent_type))
return AST_ERROR;
}

// If we still don't have an element type, bail out.
if(ast_id(type_spec) == TK_NONE)
return AST_OK;

// If there is no recover statement between the antecedent type and here,
// and if the array literal is not a subtype of the antecedent type,
// but would be if the object cap were ignored, then recover it.
ast_t* array_type = build_array_type(ast, type_spec, TK_REF);
if(!is_recovered && !is_subtype(array_type, antecedent_type, NULL, opt) &&
is_subtype_ignore_cap(array_type, antecedent_type, NULL, opt))
{
ast_free_unattached(array_type);

BUILD(recover, ast,
NODE(TK_RECOVER,
NODE(TK_ISO)
NODE(TK_SEQ, TREE(ast))));

ast_replace(astp, recover);

// Run the expr pass on this recover block.
if(ast_visit(astp, pass_pre_expr, pass_expr, opt, PASS_EXPR) != AST_OK)
return AST_ERROR;

// We've already processed the expr pass for the array, so ignore it now.
return AST_IGNORE;
}

ast_free_unattached(array_type);
return AST_OK;
}

bool expr_array(pass_opt_t* opt, ast_t** astp)
{
ast_t* ast = *astp;
ast_t* type = NULL;
bool told_type = false;

pony_assert(ast_id(ast) == TK_ARRAY);
AST_GET_CHILDREN(ast, type_spec, elements);
size_t size = ast_childcount(elements);

Expand All @@ -27,6 +272,13 @@ bool expr_array(pass_opt_t* opt, ast_t** astp)
told_type = true;
}

if(!told_type && (ast_childcount(elements) == 0))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can use the size local instead of the duplicate call to ast_childcount.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Fixed in 943c1ba.

{
ast_error(opt->check.errors, ast, "an empty array literal must specify "
"the element type or it must be inferable from context");
return false;
}

for(ast_t* ele = ast_child(elements); ele != NULL; ele = ast_sibling(ele))
{
if(ast_checkflag(ele, AST_FLAG_JUMPS_AWAY))
Expand Down
2 changes: 2 additions & 0 deletions src/libponyc/expr/array.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

PONY_EXTERN_C_BEGIN

ast_result_t expr_pre_array(pass_opt_t* opt, ast_t** astp);

bool expr_array(pass_opt_t* opt, ast_t** astp);

PONY_EXTERN_C_END
Expand Down
Loading