Skip to content

Commit

Permalink
Implement array and lambda inference.
Browse files Browse the repository at this point in the history
  • Loading branch information
jemc committed Aug 16, 2017
1 parent 6a44eaa commit 9a2145c
Show file tree
Hide file tree
Showing 18 changed files with 1,554 additions and 168 deletions.
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
6 changes: 3 additions & 3 deletions src/libponyc/ast/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -421,13 +421,13 @@ 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();

Expand All @@ -437,7 +437,7 @@ DEF(nextarray);
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)
{
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);
}

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))
{
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

0 comments on commit 9a2145c

Please sign in to comment.