Skip to content

Commit

Permalink
Fix support for recursive constraints in iftype conditions. (#1961)
Browse files Browse the repository at this point in the history
* Keep the data of TK_TYPEPARAM pointing to the original definition.

iftype is implemented by shadowing the type parameter with a new definition
and a new constraint. Storing the pointer of the original definition
allows us to compare identity of type parameters easily.

This replaces the more complicated subtyping-based check to determine
whether a type parameter should be reified.

* Create a new AST node for iftype clauses.

This creates a new scope with both the constraint and the body of the
iftype, but without the else clause.

* Fix support for recursive constraints in iftype conditions.

By defining the shadowing TK_TYPEPARAM on the entire iftype clause, which
contains both the constraint and the body, the appropriate definition
of the type variable is used when it appears in the constraint.

Fixes #1960

* Rename TK_IFTYPE and TK_IFTYPE_CLAUSE to be clearer
  • Loading branch information
plietar authored and jemc committed Jul 2, 2017
1 parent 2adc4e6 commit a064d3a
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 55 deletions.
2 changes: 1 addition & 1 deletion src/libponyc/ast/lexer.c
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ static const lextoken_t keywords[] =

{ "if", TK_IF },
{ "ifdef", TK_IFDEF },
{ "iftype", TK_IFTYPE },
{ "iftype", TK_IFTYPE_SET },
{ "then", TK_THEN },
{ "else", TK_ELSE },
{ "elseif", TK_ELSEIF },
Expand Down
37 changes: 20 additions & 17 deletions src/libponyc/ast/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -738,31 +738,34 @@ DEF(ifdef);
REORDER(0, 2, 3, 1);
DONE();

// ELSEIF [annotations] type <: type THEN seq [elseiftype | (ELSE seq)]
DEF(elseiftype);
// type <: type THEN seq
DEF(iftype);
AST_NODE(TK_IFTYPE);
SCOPE();
SKIP(NULL, TK_ELSEIF);
ANNOTATE(annotations);
RULE("type", type);
SKIP(NULL, TK_SUBTYPE);
RULE("type", type);
SKIP(NULL, TK_THEN);
RULE("then value", seq);
DONE();

// ELSEIF [annotations] iftype [elseiftype | (ELSE seq)]
DEF(elseiftype);
AST_NODE(TK_IFTYPE_SET);
SKIP(NULL, TK_ELSEIF);
ANNOTATE(annotations);
SCOPE();
RULE("iftype clause", iftype);
OPT RULE("else clause", elseiftype, elseclause);
DONE();

// IFTYPE [annotations] type <: type THEN seq [elseiftype | (ELSE seq)] END
DEF(iftype);
// IFTYPE_SET [annotations] iftype [elseiftype | (ELSE seq)] END
DEF(iftypeset);
PRINT_INLINE();
TOKEN(NULL, TK_IFTYPE);
ANNOTATE(annotations);
TOKEN(NULL, TK_IFTYPE_SET);
SCOPE();
RULE("type", type);
SKIP(NULL, TK_SUBTYPE);
RULE("type", type);
SKIP(NULL, TK_THEN);
RULE("then value", seq);
ANNOTATE(annotations);
RULE("iftype clause", iftype);
OPT RULE("else clause", elseiftype, elseclause);
TERMINATE("iftype expression", TK_END);
DONE();
Expand Down Expand Up @@ -965,18 +968,18 @@ DEF(test_ifdef_flag);
TOKEN(NULL, TK_ID);
DONE();

// cond | ifdef | iftype | match | whileloop | repeat | forloop | with | try |
// cond | ifdef | iftypeset | match | whileloop | repeat | forloop | with | try |
// recover | consume | pattern | const_expr | test_<various>
DEF(term);
RULE("value", cond, ifdef, iftype, match, whileloop, repeat, forloop, with,
RULE("value", cond, ifdef, iftypeset, match, whileloop, repeat, forloop, with,
try_block, recover, consume, pattern, const_expr, test_noseq,
test_seq_scope, test_try_block, test_ifdef_flag, test_prefix);
DONE();

// cond | ifdef | iftype | match | whileloop | repeat | forloop | with | try |
// cond | ifdef | iftypeset | match | whileloop | repeat | forloop | with | try |
// recover | consume | pattern | const_expr | test_<various>
DEF(nextterm);
RULE("value", cond, ifdef, iftype, match, whileloop, repeat, forloop, with,
RULE("value", cond, ifdef, iftypeset, match, whileloop, repeat, forloop, with,
try_block, recover, consume, nextpattern, const_expr, test_noseq,
test_seq_scope, test_try_block, test_ifdef_flag, test_prefix);
DONE();
Expand Down
1 change: 1 addition & 0 deletions src/libponyc/ast/token.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ typedef enum token_id
TK_IF,
TK_IFDEF,
TK_IFTYPE,
TK_IFTYPE_SET,
TK_THEN,
TK_ELSE,
TK_ELSEIF,
Expand Down
5 changes: 3 additions & 2 deletions src/libponyc/codegen/gencontrol.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,11 @@ LLVMValueRef gen_if(compile_t* c, ast_t* ast)

LLVMValueRef gen_iftype(compile_t* c, ast_t* ast)
{
AST_GET_CHILDREN(ast, subtype, supertype, left, right);
AST_GET_CHILDREN(ast, left, right);
AST_GET_CHILDREN(left, subtype, supertype, body);

if(is_subtype_constraint(subtype, supertype, NULL, c->opt))
return gen_expr(c, left);
return gen_expr(c, body);

return gen_expr(c, right);
}
Expand Down
2 changes: 1 addition & 1 deletion src/libponyc/codegen/genexpr.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ LLVMValueRef gen_expr(compile_t* c, ast_t* ast)
ret = gen_if(c, ast);
break;

case TK_IFTYPE:
case TK_IFTYPE_SET:
ret = gen_iftype(c, ast);
break;

Expand Down
5 changes: 3 additions & 2 deletions src/libponyc/expr/control.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,9 @@ bool expr_if(pass_opt_t* opt, ast_t* ast)

bool expr_iftype(pass_opt_t* opt, ast_t* ast)
{
pony_assert(ast_id(ast) == TK_IFTYPE);
AST_GET_CHILDREN(ast, sub, super, left, right);
pony_assert(ast_id(ast) == TK_IFTYPE_SET);
AST_GET_CHILDREN(ast, left_control, right);
AST_GET_CHILDREN(left_control, sub, super, left);

ast_t* type = NULL;

Expand Down
6 changes: 4 additions & 2 deletions src/libponyc/pass/expr.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ bool is_result_needed(ast_t* ast)
return is_result_needed(parent);

case TK_IFTYPE:
// Sub/supertype not needed, body/else needed only if parent needed.
// Sub/supertype not needed, body needed only if parent needed.
if((ast_child(parent) == ast) || (ast_childidx(parent, 1) == ast))
return false;

Expand All @@ -78,6 +78,7 @@ bool is_result_needed(ast_t* ast)
return is_result_needed(parent);

case TK_CASES:
case TK_IFTYPE_SET:
case TK_TRY:
case TK_TRY_NO_CHECK:
case TK_RECOVER:
Expand Down Expand Up @@ -154,6 +155,7 @@ bool is_method_result(typecheck_t* t, ast_t* ast)
break;

case TK_CASES:
case TK_IFTYPE_SET:
case TK_RECOVER:
// These can be results.
break;
Expand Down Expand Up @@ -254,7 +256,7 @@ ast_result_t pass_expr(ast_t** astp, pass_opt_t* options)
case TK_CALL: r = expr_call(options, astp); break;
case TK_IFDEF:
case TK_IF: r = expr_if(options, ast); break;
case TK_IFTYPE: r = expr_iftype(options, ast); break;
case TK_IFTYPE_SET: r = expr_iftype(options, ast); break;
case TK_WHILE: r = expr_while(options, ast); break;
case TK_REPEAT: r = expr_repeat(options, ast); break;
case TK_TRY_NO_CHECK:
Expand Down
9 changes: 6 additions & 3 deletions src/libponyc/pass/refer.c
Original file line number Diff line number Diff line change
Expand Up @@ -935,8 +935,10 @@ static bool refer_if(pass_opt_t* opt, ast_t* ast)
static bool refer_iftype(pass_opt_t* opt, ast_t* ast)
{
(void)opt;
pony_assert(ast_id(ast) == TK_IFTYPE);
AST_GET_CHILDREN(ast, sub, super, left, right);
pony_assert(ast_id(ast) == TK_IFTYPE_SET);

AST_GET_CHILDREN(ast, left_clause, right);
AST_GET_CHILDREN(left_clause, sub, super, left);

size_t branch_count = 0;

Expand Down Expand Up @@ -1289,7 +1291,8 @@ ast_result_t pass_refer(ast_t** astp, pass_opt_t* options)
case TK_SEQ: r = refer_seq(options, ast); break;
case TK_IFDEF:
case TK_IF: r = refer_if(options, ast); break;
case TK_IFTYPE: r = refer_iftype(options, ast); break;
case TK_IFTYPE_SET:
r = refer_iftype(options, ast); break;
case TK_WHILE: r = refer_while(options, ast); break;
case TK_REPEAT: r = refer_repeat(options, ast); break;
case TK_MATCH: r = refer_match(options, ast); break;
Expand Down
16 changes: 9 additions & 7 deletions src/libponyc/pass/scope.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,30 +193,32 @@ static ast_t* make_iftype_typeparam(pass_opt_t* opt, ast_t* subtype,
TREE(new_constraint)
NONE));

ast_setdata(typeparam, typeparam);
// keep data pointing to the original def
ast_setdata(typeparam, ast_data(def));

return typeparam;
}

static ast_result_t scope_iftype(pass_opt_t* opt, ast_t* ast)
{
AST_GET_CHILDREN(ast, subtype, supertype, then_clause);
pony_assert(ast_id(ast) == TK_IFTYPE);

AST_GET_CHILDREN(ast, subtype, supertype);

ast_t* typeparams = ast_from(ast, TK_TYPEPARAMS);

switch(ast_id(subtype))
{
case TK_NOMINAL:
{
ast_t* typeparam = make_iftype_typeparam(opt, subtype, supertype,
then_clause);
ast_t* typeparam = make_iftype_typeparam(opt, subtype, supertype, ast);
if(typeparam == NULL)
{
ast_free_unattached(typeparams);
return AST_ERROR;
}

if(!set_scope(opt, then_clause, ast_child(typeparam), typeparam, true))
if(!set_scope(opt, ast, ast_child(typeparam), typeparam, true))
{
ast_free_unattached(typeparams);
return AST_ERROR;
Expand Down Expand Up @@ -251,14 +253,14 @@ static ast_result_t scope_iftype(pass_opt_t* opt, ast_t* ast)
while(sub_child != NULL)
{
ast_t* typeparam = make_iftype_typeparam(opt, sub_child, super_child,
then_clause);
ast);
if(typeparam == NULL)
{
ast_free_unattached(typeparams);
return AST_ERROR;
}

if(!set_scope(opt, then_clause, ast_child(typeparam), typeparam, true))
if(!set_scope(opt, ast, ast_child(typeparam), typeparam, true))
{
ast_free_unattached(typeparams);
return AST_ERROR;
Expand Down
2 changes: 1 addition & 1 deletion src/libponyc/pass/sugar.c
Original file line number Diff line number Diff line change
Expand Up @@ -1267,7 +1267,7 @@ ast_result_t pass_sugar(ast_t** astp, pass_opt_t* options)
case TK_IF:
case TK_WHILE:
case TK_REPEAT: return sugar_else(ast, 2);
case TK_IFTYPE: return sugar_else(ast, 3);
case TK_IFTYPE_SET: return sugar_else(ast, 1);
case TK_TRY: return sugar_try(ast);
case TK_FOR: return sugar_for(options, astp);
case TK_WITH: return sugar_with(options, astp);
Expand Down
5 changes: 3 additions & 2 deletions src/libponyc/reach/reach.c
Original file line number Diff line number Diff line change
Expand Up @@ -1240,9 +1240,10 @@ static void reachable_expr(reach_t* r, ast_t* ast, pass_opt_t* opt)
break;
}

case TK_IFTYPE:
case TK_IFTYPE_SET:
{
AST_GET_CHILDREN(ast, sub, super, left, right);
AST_GET_CHILDREN(ast, left_clause, right);
AST_GET_CHILDREN(left_clause, sub, super, left);

ast_t* type = ast_type(ast);

Expand Down
28 changes: 11 additions & 17 deletions src/libponyc/type/reify.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,25 @@
#include "../ast/token.h"
#include "ponyassert.h"

static void reify_typeparamref(pass_opt_t* opt, ast_t** astp, ast_t* typeparam, ast_t* typearg)
static void reify_typeparamref(ast_t** astp, ast_t* typeparam, ast_t* typearg)
{
ast_t* ast = *astp;
pony_assert(ast_id(ast) == TK_TYPEPARAMREF);
pony_assert(ast_id(typeparam) == TK_TYPEPARAM);

ast_t* ref_def = (ast_t*)ast_data(ast);
ast_t* param_def = (ast_t*)ast_data(typeparam);

// We can't compare ref_def and typeparam, as they could be a copy or
// a iftype shadowing. However, their data points back to the original
// typeparam definition, which can be compared.
ref_def = (ast_t*)ast_data(ref_def);
typeparam = (ast_t*)ast_data(typeparam);

pony_assert(ref_def != NULL);
pony_assert(param_def != NULL);
AST_GET_CHILDREN(ref_def, ref_name, ref_constraint);
AST_GET_CHILDREN(param_def, param_name, param_constraint);
pony_assert(typeparam != NULL);

if(ref_def != param_def)
{
if(ast_name(ref_name) == ast_name(param_name))
{
if((ast_id(param_constraint) != TK_TYPEPARAMREF) &&
!is_subtype(ref_constraint, param_constraint, NULL, opt))
return;
} else {
return;
}
}
if(ref_def != typeparam)
return;

// Keep ephemerality.
switch(ast_id(ast_childidx(ast, 2)))
Expand Down Expand Up @@ -117,7 +111,7 @@ static void reify_one(pass_opt_t* opt, ast_t** astp, ast_t* typeparam, ast_t* ty
switch(ast_id(ast))
{
case TK_TYPEPARAMREF:
reify_typeparamref(opt, astp, typeparam, typearg);
reify_typeparamref(astp, typeparam, typearg);
break;

case TK_ARROW:
Expand Down
72 changes: 72 additions & 0 deletions test/libponyc/iftype.cc
Original file line number Diff line number Diff line change
Expand Up @@ -273,3 +273,75 @@ TEST_F(IftypeTest, UnconstrainedTypeparam)

TEST_COMPILE(src);
}

TEST_F(IftypeTest, RecursiveConstraint)
{
const char* src =
"trait T[X: T[X] #any]\n"
" fun tag m(): X\n"

"class val C is T[C]\n"
" fun tag m(): C => C.create()\n"

"actor Main\n"
" new create(env: Env) =>\n"
" foo[C](C)\n"

" fun foo[A](x': A) =>\n"
" var x = x'\n"
" iftype A <: T[A] then\n"
" x = x.m()\n"
" end";

TEST_COMPILE(src);
}


TEST_F(IftypeTest, Tuple_MutuallyRecursiveConstraint)
{
const char* src =
"trait T[X]\n"
" fun tag m(): X\n"

"class val C is T[D]\n"
" fun tag m(): D => D.create()\n"

"class val D is T[C]\n"
" fun tag m(): C => C.create()\n"

"actor Main\n"
" new create(env: Env) =>\n"
" foo[C,D](C, D)\n"

" fun foo[A, B](x': A, y': B) =>\n"
" var x = x'\n"
" var y = y'\n"
" iftype (A, B) <: (T[B], T[A]) then\n"
" x = y.m()\n"
" y = x.m()\n"
" end";

TEST_COMPILE(src);
}


TEST_F(IftypeTest, NestedCond)
{
const char* src =
"trait T\n"
"class C is T\n"
" fun tag c() => None\n"

"actor Main\n"
" new create(env: Env) =>\n"
" foo[C](C)\n"

" fun foo[A](x: A) =>\n"
" iftype A <: T then\n"
" iftype A <: C then\n"
" x.c()\n"
" end\n"
" end";

TEST_COMPILE(src);
}

0 comments on commit a064d3a

Please sign in to comment.