diff --git a/pony.g b/pony.g index 51235cbd3f..ff20ab99a4 100644 --- a/pony.g +++ b/pony.g @@ -90,6 +90,7 @@ binop nextterm : 'if' ('\\' ID (',' ID)* '\\')? rawseq 'then' rawseq (elseif | ('else' annotatedrawseq))? 'end' | 'ifdef' ('\\' ID (',' ID)* '\\')? infix 'then' rawseq (elseifdef | ('else' annotatedrawseq))? 'end' + | 'iftype' ('\\' ID (',' ID)* '\\')? type '<:' type 'then' rawseq (elseiftype | ('else' annotatedrawseq))? 'end' | 'match' ('\\' ID (',' ID)* '\\')? rawseq caseexpr* ('else' annotatedrawseq)? 'end' | 'while' ('\\' ID (',' ID)* '\\')? rawseq 'do' rawseq ('else' annotatedrawseq)? 'end' | 'repeat' ('\\' ID (',' ID)* '\\')? rawseq 'until' annotatedrawseq ('else' annotatedrawseq)? 'end' @@ -105,6 +106,7 @@ nextterm term : 'if' ('\\' ID (',' ID)* '\\')? rawseq 'then' rawseq (elseif | ('else' annotatedrawseq))? 'end' | 'ifdef' ('\\' ID (',' ID)* '\\')? infix 'then' rawseq (elseifdef | ('else' annotatedrawseq))? 'end' + | 'iftype' ('\\' ID (',' ID)* '\\')? type '<:' type 'then' rawseq (elseiftype | ('else' annotatedrawseq))? 'end' | 'match' ('\\' ID (',' ID)* '\\')? rawseq caseexpr* ('else' annotatedrawseq)? 'end' | 'while' ('\\' ID (',' ID)* '\\')? rawseq 'do' rawseq ('else' annotatedrawseq)? 'end' | 'repeat' ('\\' ID (',' ID)* '\\')? rawseq 'until' annotatedrawseq ('else' annotatedrawseq)? 'end' @@ -125,6 +127,10 @@ caseexpr : '|' ('\\' ID (',' ID)* '\\')? pattern? ('if' rawseq)? ('=>' rawseq)? ; +elseiftype + : 'elseiftype' ('\\' ID (',' ID)* '\\')? type '<:' type 'then' rawseq (elseiftype | ('else' annotatedrawseq))? + ; + elseifdef : 'elseif' ('\\' ID (',' ID)* '\\')? infix 'then' rawseq (elseifdef | ('else' annotatedrawseq))? ; diff --git a/src/libponyc/ast/frame.c b/src/libponyc/ast/frame.c index d0fea21fdb..e0b517c6be 100644 --- a/src/libponyc/ast/frame.c +++ b/src/libponyc/ast/frame.c @@ -112,6 +112,9 @@ bool frame_push(typecheck_t* t, ast_t* ast) { ast_t* parent = ast_parent(ast); + if(parent == NULL) + return pop; + switch(ast_id(parent)) { case TK_TYPEPARAM: @@ -259,6 +262,20 @@ bool frame_push(typecheck_t* t, ast_t* ast) break; } + case TK_IFTYPE: + { + AST_GET_CHILDREN(parent, l_type, r_type, body); + + pop = push_frame(t); + if(r_type == ast) + { + t->frame->iftype_constraint = ast; + } else if(body == ast) { + t->frame->iftype_body = ast; + } + break; + } + default: {} } break; diff --git a/src/libponyc/ast/frame.h b/src/libponyc/ast/frame.h index 90cd23a169..ecbd622853 100644 --- a/src/libponyc/ast/frame.h +++ b/src/libponyc/ast/frame.h @@ -32,6 +32,8 @@ typedef struct typecheck_frame_t ast_t* recover; ast_t* ifdef_cond; ast_t* ifdef_clause; + ast_t* iftype_constraint; + ast_t* iftype_body; struct typecheck_frame_t* prev; } typecheck_frame_t; diff --git a/src/libponyc/ast/lexer.c b/src/libponyc/ast/lexer.c index 04370cb1b0..4e5875e56d 100644 --- a/src/libponyc/ast/lexer.c +++ b/src/libponyc/ast/lexer.c @@ -80,6 +80,8 @@ static const lextoken_t symbols[] = { ".>", TK_CHAIN }, + { "<:", TK_SUBTYPE }, + { "\\", TK_BACKSLASH }, { "{", TK_LBRACE }, @@ -165,9 +167,11 @@ static const lextoken_t keywords[] = { "if", TK_IF }, { "ifdef", TK_IFDEF }, + { "iftype", TK_IFTYPE }, { "then", TK_THEN }, { "else", TK_ELSE }, { "elseif", TK_ELSEIF }, + { "elseiftype", TK_ELSEIFTYPE }, { "end", TK_END }, { "for", TK_FOR }, { "in", TK_IN }, diff --git a/src/libponyc/ast/parser.c b/src/libponyc/ast/parser.c index cf53d0d3e2..7d2dba03b2 100644 --- a/src/libponyc/ast/parser.c +++ b/src/libponyc/ast/parser.c @@ -678,6 +678,35 @@ DEF(ifdef); REORDER(0, 2, 3, 1); DONE(); +// ELSEIFTYPE [annotations] type <: type THEN seq [elseiftype | (ELSE seq)] +DEF(elseiftype); + AST_NODE(TK_IFTYPE); + SCOPE(); + SKIP(NULL, TK_ELSEIFTYPE); + ANNOTATE(annotations); + RULE("type", type); + SKIP(NULL, TK_SUBTYPE); + RULE("type", type); + SKIP(NULL, TK_THEN); + RULE("then value", seq); + OPT RULE("else clause", elseiftype, elseclause); + DONE(); + +// IFTYPE [annotations] type <: type THEN seq [elseiftype | (ELSE seq)] END +DEF(iftype); + PRINT_INLINE(); + TOKEN(NULL, TK_IFTYPE); + ANNOTATE(annotations); + SCOPE(); + RULE("type", type); + SKIP(NULL, TK_SUBTYPE); + RULE("type", type); + SKIP(NULL, TK_THEN); + RULE("then value", seq); + OPT RULE("else clause", elseiftype, elseclause); + TERMINATE("iftype expression", TK_END); + DONE(); + // PIPE [annotations] [infix] [WHERE rawseq] [ARROW rawseq] DEF(caseexpr); AST_NODE(TK_CASE); @@ -876,18 +905,18 @@ DEF(test_ifdef_flag); TOKEN(NULL, TK_ID); DONE(); -// cond | ifdef | match | whileloop | repeat | forloop | with | try | +// cond | ifdef | iftype | match | whileloop | repeat | forloop | with | try | // recover | consume | pattern | const_expr | test_ DEF(term); - RULE("value", cond, ifdef, match, whileloop, repeat, forloop, with, + RULE("value", cond, ifdef, iftype, 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 | match | whileloop | repeat | forloop | with | try | +// cond | ifdef | iftype | match | whileloop | repeat | forloop | with | try | // recover | consume | pattern | const_expr | test_ DEF(nextterm); - RULE("value", cond, ifdef, match, whileloop, repeat, forloop, with, + RULE("value", cond, ifdef, iftype, 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(); diff --git a/src/libponyc/ast/token.h b/src/libponyc/ast/token.h index 18ff8b8dcb..70b4380b58 100644 --- a/src/libponyc/ast/token.h +++ b/src/libponyc/ast/token.h @@ -82,6 +82,7 @@ typedef enum token_id TK_ISECTTYPE, TK_EPHEMERAL, TK_ALIASED, + TK_SUBTYPE, TK_QUESTION, TK_UNARY_MINUS, @@ -143,9 +144,11 @@ typedef enum token_id TK_IF, TK_IFDEF, + TK_IFTYPE, TK_THEN, TK_ELSE, TK_ELSEIF, + TK_ELSEIFTYPE, TK_END, TK_WHILE, TK_DO, diff --git a/src/libponyc/codegen/gencontrol.c b/src/libponyc/codegen/gencontrol.c index c61f523463..b96351bd13 100644 --- a/src/libponyc/codegen/gencontrol.c +++ b/src/libponyc/codegen/gencontrol.c @@ -152,6 +152,16 @@ LLVMValueRef gen_if(compile_t* c, ast_t* ast) return GEN_NOTNEEDED; } +LLVMValueRef gen_iftype(compile_t* c, ast_t* ast) +{ + AST_GET_CHILDREN(ast, subtype, supertype, left, right); + + if(is_subtype_constraint(subtype, supertype, NULL, c->opt)) + return gen_expr(c, left); + + return gen_expr(c, right); +} + LLVMValueRef gen_while(compile_t* c, ast_t* ast) { bool needed = is_result_needed(ast); diff --git a/src/libponyc/codegen/gencontrol.h b/src/libponyc/codegen/gencontrol.h index 820392815c..039eacb3d1 100644 --- a/src/libponyc/codegen/gencontrol.h +++ b/src/libponyc/codegen/gencontrol.h @@ -10,6 +10,8 @@ LLVMValueRef gen_seq(compile_t* c, ast_t* ast); LLVMValueRef gen_if(compile_t* c, ast_t* ast); +LLVMValueRef gen_iftype(compile_t* c, ast_t* ast); + LLVMValueRef gen_while(compile_t* c, ast_t* ast); LLVMValueRef gen_repeat(compile_t* c, ast_t* ast); diff --git a/src/libponyc/codegen/genexpr.c b/src/libponyc/codegen/genexpr.c index b4c0156aa0..322869f7fa 100644 --- a/src/libponyc/codegen/genexpr.c +++ b/src/libponyc/codegen/genexpr.c @@ -63,6 +63,10 @@ LLVMValueRef gen_expr(compile_t* c, ast_t* ast) ret = gen_if(c, ast); break; + case TK_IFTYPE: + ret = gen_iftype(c, ast); + break; + case TK_WHILE: ret = gen_while(c, ast); break; diff --git a/src/libponyc/expr/control.c b/src/libponyc/expr/control.c index 313f1cab47..7f623a25a9 100644 --- a/src/libponyc/expr/control.c +++ b/src/libponyc/expr/control.c @@ -10,6 +10,7 @@ #include "../type/assemble.h" #include "../type/subtype.h" #include "../type/alias.h" +#include "../type/typeparam.h" #include "ponyassert.h" bool expr_seq(pass_opt_t* opt, ast_t* ast) @@ -133,6 +134,44 @@ bool expr_if(pass_opt_t* opt, ast_t* ast) return true; } +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); + + ast_t* type = NULL; + + if(!ast_checkflag(left, AST_FLAG_JUMPS_AWAY)) + { + if(is_typecheck_error(ast_type(left))) + return false; + + type = control_type_add_branch(opt, type, left); + } + + if(!ast_checkflag(right, AST_FLAG_JUMPS_AWAY)) + { + if(is_typecheck_error(ast_type(right))) + return false; + + type = control_type_add_branch(opt, type, right); + } + + if(type == NULL) + { + if((ast_id(ast_parent(ast)) == TK_SEQ) && ast_sibling(ast) != NULL) + { + ast_error(opt->check.errors, ast_sibling(ast), "unreachable code"); + return false; + } + } + + ast_settype(ast, type); + literal_unify_control(ast, opt); + + return true; +} + bool expr_while(pass_opt_t* opt, ast_t* ast) { pony_assert(ast_id(ast) == TK_WHILE); diff --git a/src/libponyc/expr/control.h b/src/libponyc/expr/control.h index 2708405a96..26a127d990 100644 --- a/src/libponyc/expr/control.h +++ b/src/libponyc/expr/control.h @@ -9,6 +9,7 @@ PONY_EXTERN_C_BEGIN bool expr_seq(pass_opt_t* opt, ast_t* ast); bool expr_if(pass_opt_t* opt, ast_t* ast); +bool expr_iftype(pass_opt_t* opt, ast_t* ast); bool expr_while(pass_opt_t* opt, ast_t* ast); bool expr_repeat(pass_opt_t* opt, ast_t* ast); bool expr_try(pass_opt_t* opt, ast_t* ast); diff --git a/src/libponyc/expr/reference.c b/src/libponyc/expr/reference.c index eea0b3ebd5..1c16b4e13b 100644 --- a/src/libponyc/expr/reference.c +++ b/src/libponyc/expr/reference.c @@ -13,6 +13,7 @@ #include "../type/cap.h" #include "../type/reify.h" #include "../type/lookup.h" +#include "../type/typeparam.h" #include "../ast/astbuild.h" #include "ponyassert.h" @@ -127,6 +128,8 @@ bool expr_fieldref(pass_opt_t* opt, ast_t* ast, ast_t* find, token_id tid) AST_GET_CHILDREN(find, id, f_type, init); + f_type = typeparam_current(opt, f_type, ast); + // Viewpoint adapted type of the field. ast_t* type = viewpoint_type(l_type, f_type); @@ -368,6 +371,8 @@ bool expr_localref(pass_opt_t* opt, ast_t* ast) if(is_typecheck_error(type)) return false; + type = typeparam_current(opt, type, ast); + if(!sendable(type)) { if(opt->check.frame->recover != NULL) @@ -425,6 +430,8 @@ bool expr_paramref(pass_opt_t* opt, ast_t* ast) if(is_typecheck_error(type)) return false; + type = typeparam_current(opt, type, ast); + if(!sendable(type) && (opt->check.frame->recover != NULL)) { ast_t* parent = ast_parent(ast); diff --git a/src/libponyc/pass/expr.c b/src/libponyc/pass/expr.c index 1b867be4e6..217a7c588d 100644 --- a/src/libponyc/pass/expr.c +++ b/src/libponyc/pass/expr.c @@ -56,6 +56,13 @@ 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. + if((ast_child(parent) == ast) || (ast_childidx(parent, 1) == ast)) + return false; + + return is_result_needed(parent); + case TK_REPEAT: // Cond needed, body/else needed only if parent needed. if(ast_childidx(parent, 1) == ast) @@ -128,6 +135,12 @@ bool is_method_result(typecheck_t* t, ast_t* ast) return false; break; + case TK_IFTYPE: + // The subtype and the supertype are not the result. + if((ast_child(parent) == ast) || (ast_childidx(parent, 1) == ast)) + return false; + break; + case TK_REPEAT: // The condition is not the result. if(ast_childidx(parent, 1) == ast) @@ -241,6 +254,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_WHILE: r = expr_while(options, ast); break; case TK_REPEAT: r = expr_repeat(options, ast); break; case TK_TRY_NO_CHECK: diff --git a/src/libponyc/pass/flatten.c b/src/libponyc/pass/flatten.c index e4fe02eacf..4cc267a46b 100644 --- a/src/libponyc/pass/flatten.c +++ b/src/libponyc/pass/flatten.c @@ -56,6 +56,7 @@ static ast_result_t flatten_isect(pass_opt_t* opt, ast_t* ast) AST_EXTRACT_CHILDREN(ast, left, right); if((opt->check.frame->constraint == NULL) && + (opt->check.frame->iftype_constraint == NULL) && (opt->check.frame->provides == NULL) && !is_compat_type(left, right)) { diff --git a/src/libponyc/pass/names.c b/src/libponyc/pass/names.c index bf38fdc6f2..a91be727fa 100644 --- a/src/libponyc/pass/names.c +++ b/src/libponyc/pass/names.c @@ -190,6 +190,8 @@ static bool names_typeparam(pass_opt_t* opt, ast_t** astp, ast_t* def) } } + const char* name = ast_name(id); + // Change to a typeparamref. REPLACE(astp, NODE(TK_TYPEPARAMREF, @@ -197,6 +199,9 @@ static bool names_typeparam(pass_opt_t* opt, ast_t** astp, ast_t* def) TREE(cap) TREE(ephemeral))); + if(opt->check.frame->iftype_body != NULL) + def = ast_get(ast, name, NULL); + ast_setdata(*astp, def); return true; } @@ -210,7 +215,8 @@ static bool names_type(pass_opt_t* opt, ast_t** astp, ast_t* def) if(tcap == TK_NONE) { - if(opt->check.frame->constraint != NULL) + if((opt->check.frame->constraint != NULL) || + (opt->check.frame->iftype_constraint != NULL)) { // A primitive constraint is a val, otherwise #any. if(ast_id(def) == TK_PRIMITIVE) diff --git a/src/libponyc/pass/pass.c b/src/libponyc/pass/pass.c index 681fbfb6b2..1978104ca8 100644 --- a/src/libponyc/pass/pass.c +++ b/src/libponyc/pass/pass.c @@ -298,7 +298,7 @@ bool generate_passes(ast_t* program, pass_opt_t* options) } -static void record_ast_pass(ast_t* ast, pass_id pass) +void ast_pass_record(ast_t* ast, pass_id pass) { pony_assert(ast != NULL); @@ -346,7 +346,7 @@ ast_result_t ast_visit(ast_t** ast, ast_visit_t pre, ast_visit_t post, break; case AST_FATAL: - record_ast_pass(*ast, pass); + ast_pass_record(*ast, pass); if(pop) frame_pop(t); @@ -376,7 +376,7 @@ ast_result_t ast_visit(ast_t** ast, ast_visit_t pre, ast_visit_t post, break; case AST_FATAL: - record_ast_pass(*ast, pass); + ast_pass_record(*ast, pass); if(pop) frame_pop(t); @@ -401,7 +401,7 @@ ast_result_t ast_visit(ast_t** ast, ast_visit_t pre, ast_visit_t post, break; case AST_FATAL: - record_ast_pass(*ast, pass); + ast_pass_record(*ast, pass); if(pop) frame_pop(t); @@ -413,7 +413,7 @@ ast_result_t ast_visit(ast_t** ast, ast_visit_t pre, ast_visit_t post, if(pop) frame_pop(t); - record_ast_pass(*ast, pass); + ast_pass_record(*ast, pass); return ret; } diff --git a/src/libponyc/pass/pass.h b/src/libponyc/pass/pass.h index 599ce818b9..157579a489 100644 --- a/src/libponyc/pass/pass.h +++ b/src/libponyc/pass/pass.h @@ -304,6 +304,10 @@ bool ast_passes_subtree(ast_t** astp, pass_opt_t* options, pass_id last_pass); */ bool generate_passes(ast_t* program, pass_opt_t* options); +/** Record the specified pass as done for the given AST. + */ +void ast_pass_record(ast_t* ast, pass_id pass); + typedef ast_result_t(*ast_visit_t)(ast_t** astp, pass_opt_t* options); diff --git a/src/libponyc/pass/refer.c b/src/libponyc/pass/refer.c index 27cbc63e2c..2d944da8fc 100644 --- a/src/libponyc/pass/refer.c +++ b/src/libponyc/pass/refer.c @@ -916,6 +916,38 @@ static bool refer_if(pass_opt_t* opt, ast_t* ast) return true; } +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); + + size_t branch_count = 0; + + if(!ast_checkflag(left, AST_FLAG_JUMPS_AWAY)) + { + branch_count++; + ast_inheritbranch(ast, left); + } + + if(!ast_checkflag(right, AST_FLAG_JUMPS_AWAY)) + { + branch_count++; + ast_inheritbranch(ast, right); + } + + ast_consolidate_branches(ast, branch_count); + + // If all branches jump away with no value, then we do too. + if(branch_count == 0) + ast_setflag(ast, AST_FLAG_JUMPS_AWAY); + + // Push our symbol status to our parent scope. + ast_inheritstatus(ast_parent(ast), ast); + + return true; +} + static bool refer_while(pass_opt_t* opt, ast_t* ast) { pony_assert(ast_id(ast) == TK_WHILE); @@ -1237,6 +1269,7 @@ 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_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; diff --git a/src/libponyc/pass/scope.c b/src/libponyc/pass/scope.c index e1738b85ed..b1bb397020 100644 --- a/src/libponyc/pass/scope.c +++ b/src/libponyc/pass/scope.c @@ -13,8 +13,8 @@ /** * Insert a name->AST mapping into the specified scope. */ -static bool set_scope(pass_opt_t* opt, ast_t* scope, - ast_t* name, ast_t* value) +static bool set_scope(pass_opt_t* opt, ast_t* scope, ast_t* name, ast_t* value, + bool allow_shadowing) { pony_assert(ast_id(name) == TK_ID); const char* s = ast_name(name); @@ -58,7 +58,7 @@ static bool set_scope(pass_opt_t* opt, ast_t* scope, return false; } - if(!ast_set(scope, s, value, status, false)) + if(!ast_set(scope, s, value, status, allow_shadowing)) { ast_t* prev = ast_get(scope, s, NULL); ast_t* prev_nocase = ast_get_case(scope, s, NULL); @@ -89,7 +89,7 @@ bool use_package(ast_t* ast, const char* path, ast_t* name, } if(name != NULL && ast_id(name) == TK_ID) // We have an alias - return set_scope(options, ast, name, package); + return set_scope(options, ast, name, package, false); // Store the package so we can import it later without having to look it up // again @@ -101,7 +101,7 @@ static bool scope_method(pass_opt_t* opt, ast_t* ast) { ast_t* id = ast_childidx(ast, 1); - if(!set_scope(opt, ast_parent(ast), id, ast)) + if(!set_scope(opt, ast_parent(ast), id, ast, false)) return false; return true; @@ -111,7 +111,7 @@ static ast_result_t scope_entity(pass_opt_t* opt, ast_t* ast) { AST_GET_CHILDREN(ast, id, typeparams, cap, provides, members); - if(!set_scope(opt, opt->check.frame->package, id, ast)) + if(!set_scope(opt, opt->check.frame->package, id, ast, false)) return AST_ERROR; // Scope fields and methods immediately, so that the contents of method @@ -125,7 +125,7 @@ static ast_result_t scope_entity(pass_opt_t* opt, ast_t* ast) case TK_FVAR: case TK_FLET: case TK_EMBED: - if(!set_scope(opt, member, ast_child(member), member)) + if(!set_scope(opt, member, ast_child(member), member, false)) return AST_ERROR; break; @@ -147,6 +147,142 @@ static ast_result_t scope_entity(pass_opt_t* opt, ast_t* ast) return AST_OK; } +static ast_t* make_iftype_typeparam(pass_opt_t* opt, ast_t* subtype, + ast_t* supertype, ast_t* scope) +{ + pony_assert(ast_id(subtype) == TK_NOMINAL); + + const char* name = ast_name(ast_childidx(subtype, 1)); + ast_t* def = ast_get(scope, name, NULL); + if(def == NULL) + { + ast_error(opt->check.errors, ast_child(subtype), + "can't find definition of '%s'", name); + return NULL; + } + + if(ast_id(def) != TK_TYPEPARAM) + { + ast_error(opt->check.errors, subtype, "the subtype in an iftype condition " + "must be a type parameter or a tuple of type parameters"); + return NULL; + } + + ast_t* current_constraint = ast_childidx(def, 1); + ast_t* new_constraint = ast_dup(supertype); + if((ast_id(current_constraint) != TK_NOMINAL) || + (ast_name(ast_childidx(current_constraint, 1)) != name)) + { + // If the constraint is the type parameter itself, there is no constraint. + // We can't use type_isect to build the new constraint because we don't have + // full type information yet. + BUILD(isect, new_constraint, + NODE(TK_ISECTTYPE, + TREE(ast_dup(current_constraint)) + TREE(new_constraint))); + + new_constraint = isect; + } + + BUILD(typeparam, def, + NODE(TK_TYPEPARAM, + ID(name) + TREE(new_constraint) + NONE)); + + ast_setdata(typeparam, typeparam); + + return typeparam; +} + +static ast_result_t scope_iftype(pass_opt_t* opt, ast_t* ast) +{ + AST_GET_CHILDREN(ast, subtype, supertype, then_clause); + + 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); + if(typeparam == NULL) + { + ast_free_unattached(typeparams); + return AST_ERROR; + } + + if(!set_scope(opt, then_clause, ast_child(typeparam), typeparam, true)) + { + ast_free_unattached(typeparams); + return AST_ERROR; + } + + ast_add(typeparams, typeparam); + break; + } + + case TK_TUPLETYPE: + { + if(ast_id(supertype) != TK_TUPLETYPE) + { + ast_error(opt->check.errors, subtype, "iftype subtype is a tuple but " + "supertype isn't"); + ast_error_continue(opt->check.errors, supertype, "Supertype is %s", + ast_print_type(supertype)); + ast_free_unattached(typeparams); + return AST_ERROR; + } + + if(ast_childcount(subtype) != ast_childcount(supertype)) + { + ast_error(opt->check.errors, subtype, "the subtype and the supertype " + "in an iftype condition must have the same cardinality"); + ast_free_unattached(typeparams); + return AST_ERROR; + } + + ast_t* sub_child = ast_child(subtype); + ast_t* super_child = ast_child(supertype); + while(sub_child != NULL) + { + ast_t* typeparam = make_iftype_typeparam(opt, sub_child, super_child, + then_clause); + if(typeparam == NULL) + { + ast_free_unattached(typeparams); + return AST_ERROR; + } + + if(!set_scope(opt, then_clause, ast_child(typeparam), typeparam, true)) + { + ast_free_unattached(typeparams); + return AST_ERROR; + } + + ast_add(typeparams, typeparam); + sub_child = ast_sibling(sub_child); + super_child = ast_sibling(super_child); + } + + break; + } + + default: + ast_error(opt->check.errors, subtype, "the subtype in an iftype " + "condition must be a type parameter or a tuple of type parameters"); + ast_free_unattached(typeparams); + return AST_ERROR; + } + + // We don't want the scope pass to run on typeparams. The compiler would think + // that type parameters are declared twice. + ast_pass_record(typeparams, PASS_SCOPE); + ast_append(ast, typeparams); + return AST_OK; +} + ast_result_t pass_scope(ast_t** astp, pass_opt_t* options) { ast_t* ast = *astp; @@ -169,12 +305,12 @@ ast_result_t pass_scope(ast_t** astp, pass_opt_t* options) case TK_LET: case TK_PARAM: case TK_MATCH_CAPTURE: - if(!set_scope(options, ast, ast_child(ast), ast)) + if(!set_scope(options, ast, ast_child(ast), ast, false)) return AST_ERROR; break; case TK_TYPEPARAM: - if(!set_scope(options, ast, ast_child(ast), ast)) + if(!set_scope(options, ast, ast_child(ast), ast, false)) return AST_ERROR; // Store the original definition of the typeparam in the data field here. @@ -182,6 +318,9 @@ ast_result_t pass_scope(ast_t** astp, pass_opt_t* options) ast_setdata(ast, ast); break; + case TK_IFTYPE: + return scope_iftype(options, ast); + default: {} } diff --git a/src/libponyc/pass/sugar.c b/src/libponyc/pass/sugar.c index ebf2a1b0e7..993a2a962f 100644 --- a/src/libponyc/pass/sugar.c +++ b/src/libponyc/pass/sugar.c @@ -459,9 +459,9 @@ static ast_result_t sugar_return(pass_opt_t* opt, ast_t* ast) } -static ast_result_t sugar_else(ast_t* ast) +static ast_result_t sugar_else(ast_t* ast, size_t index) { - ast_t* else_clause = ast_childidx(ast, 2); + ast_t* else_clause = ast_childidx(ast, index); expand_none(else_clause, true); return AST_OK; } @@ -976,7 +976,7 @@ static ast_result_t sugar_ifdef(pass_opt_t* opt, ast_t* ast) return AST_ERROR; } - return sugar_else(ast); + return sugar_else(ast, 2); } @@ -1229,7 +1229,8 @@ ast_result_t pass_sugar(ast_t** astp, pass_opt_t* options) case TK_IF: case TK_MATCH: case TK_WHILE: - case TK_REPEAT: return sugar_else(ast); + case TK_REPEAT: return sugar_else(ast, 2); + case TK_IFTYPE: return sugar_else(ast, 3); case TK_TRY: return sugar_try(ast); case TK_FOR: return sugar_for(options, astp); case TK_WITH: return sugar_with(options, astp); diff --git a/src/libponyc/pass/syntax.c b/src/libponyc/pass/syntax.c index ad655d35b4..3011e31414 100644 --- a/src/libponyc/pass/syntax.c +++ b/src/libponyc/pass/syntax.c @@ -447,7 +447,8 @@ static ast_result_t syntax_arrow(pass_opt_t* opt, ast_t* ast) pony_assert(ast != NULL); AST_GET_CHILDREN(ast, left, right); - if((opt->check.frame->constraint != NULL) && + if(((opt->check.frame->constraint != NULL) || + (opt->check.frame->iftype_constraint != NULL)) && (opt->check.frame->method == NULL)) { ast_error(opt->check.errors, ast, @@ -1124,7 +1125,8 @@ static ast_result_t syntax_cap(pass_opt_t* opt, ast_t* ast) static ast_result_t syntax_cap_set(pass_opt_t* opt, ast_t* ast) { // Cap sets can only appear in type parameter constraints. - if(opt->check.frame->constraint == NULL) + if((opt->check.frame->constraint == NULL) && + (opt->check.frame->iftype_constraint == NULL)) { ast_error(opt->check.errors, ast, "a capability set can only appear in a type constraint"); diff --git a/src/libponyc/reach/reach.c b/src/libponyc/reach/reach.c index e9894bff3f..d75a26b597 100644 --- a/src/libponyc/reach/reach.c +++ b/src/libponyc/reach/reach.c @@ -1102,6 +1102,23 @@ static void reachable_expr(reach_t* r, ast_t* ast, pass_opt_t* opt) break; } + case TK_IFTYPE: + { + AST_GET_CHILDREN(ast, sub, super, left, right); + + ast_t* type = ast_type(ast); + + if(is_result_needed(ast) && !ast_checkflag(ast, AST_FLAG_JUMPS_AWAY)) + add_type(r, type, opt); + + if(is_subtype_constraint(sub, super, NULL, opt)) + reachable_expr(r, left, opt); + else + reachable_expr(r, right, opt); + + return; + } + case TK_MATCH: case TK_WHILE: case TK_REPEAT: diff --git a/src/libponyc/type/reify.c b/src/libponyc/type/reify.c index 6168cb8237..5d71465371 100644 --- a/src/libponyc/type/reify.c +++ b/src/libponyc/type/reify.c @@ -6,7 +6,7 @@ #include "../ast/token.h" #include "ponyassert.h" -static void reify_typeparamref(ast_t** astp, ast_t* typeparam, ast_t* typearg) +static void reify_typeparamref(pass_opt_t* opt, ast_t** astp, ast_t* typeparam, ast_t* typearg) { ast_t* ast = *astp; pony_assert(ast_id(ast) == TK_TYPEPARAMREF); @@ -17,9 +17,19 @@ static void reify_typeparamref(ast_t** astp, ast_t* typeparam, ast_t* typearg) 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); if(ref_def != param_def) - return; + { + if(ast_name(ref_name) == ast_name(param_name)) + { + if(!is_subtype(ref_constraint, param_constraint, NULL, opt)) + return; + } else { + return; + } + } // Keep ephemerality. switch(ast_id(ast_childidx(ast, 2))) @@ -87,26 +97,26 @@ static void reify_reference(ast_t** astp, ast_t* typeparam, ast_t* typearg) ast_settype(ast, typearg); } -static void reify_one(ast_t** astp, ast_t* typeparam, ast_t* typearg) +static void reify_one(pass_opt_t* opt, ast_t** astp, ast_t* typeparam, ast_t* typearg) { ast_t* ast = *astp; ast_t* child = ast_child(ast); while(child != NULL) { - reify_one(&child, typeparam, typearg); + reify_one(opt, &child, typeparam, typearg); child = ast_sibling(child); } ast_t* type = ast_type(ast); if(type != NULL) - reify_one(&type, typeparam, typearg); + reify_one(opt, &type, typeparam, typearg); switch(ast_id(ast)) { case TK_TYPEPARAMREF: - reify_typeparamref(astp, typeparam, typearg); + reify_typeparamref(opt, astp, typeparam, typearg); break; case TK_ARROW: @@ -204,7 +214,7 @@ ast_t* reify(ast_t* ast, ast_t* typeparams, ast_t* typeargs, pass_opt_t* opt, while((typeparam != NULL) && (typearg != NULL)) { - reify_one(&r_ast, typeparam, typearg); + reify_one(opt, &r_ast, typeparam, typearg); typeparam = ast_sibling(typeparam); typearg = ast_sibling(typearg); } diff --git a/src/libponyc/type/typeparam.c b/src/libponyc/type/typeparam.c index 35fc0783f3..3e104dc4be 100644 --- a/src/libponyc/type/typeparam.c +++ b/src/libponyc/type/typeparam.c @@ -1,8 +1,12 @@ #include "typeparam.h" +#include "assemble.h" #include "cap.h" #include "subtype.h" +#include "matchtype.h" #include "../ast/token.h" +#include "../../libponyrt/mem/pool.h" #include "ponyassert.h" +#include static token_id cap_union_constraint(token_id a, token_id b) { @@ -547,3 +551,75 @@ void typeparam_set_cap(ast_t* typeparamref) token_id tcap = cap_from_constraint(constraint); ast_setid(cap, tcap); } + +static ast_t* typeparamref_current(ast_t* typeparamref, ast_t* scope) +{ + pony_assert(ast_id(typeparamref) == TK_TYPEPARAMREF); + + ast_t* def = (ast_t*)ast_data(typeparamref); + ast_t* id = ast_child(typeparamref); + + ast_t* current_def = ast_get(scope, ast_name(id), NULL); + ast_t* new_typeparamref = ast_dup(typeparamref); + ast_setdata(new_typeparamref, current_def); + if(def == current_def) + return new_typeparamref; + + typeparam_set_cap(new_typeparamref); + return new_typeparamref; +} + +static ast_t* typeparam_current_inner(ast_t* type, ast_t* scope) +{ + switch(ast_id(type)) + { + case TK_TYPEPARAMREF: + return typeparamref_current(type, scope); + + case TK_NOMINAL: + return ast_dup(type); + + case TK_UNIONTYPE: + case TK_ISECTTYPE: + case TK_TUPLETYPE: + { + ast_t* new_type = ast_from(type, ast_id(type)); + ast_t* child = ast_child(type); + + while(child != NULL) + { + ast_t* new_child = typeparam_current_inner(child, scope); + ast_append(new_type, new_child); + child = ast_sibling(child); + } + + return new_type; + } + + case TK_ARROW: + { + AST_GET_CHILDREN(type, left, right); + + ast_t* new_type = ast_from(type, TK_ARROW); + ast_t* new_left = typeparam_current_inner(left, scope); + ast_t* new_right = typeparam_current_inner(right, scope); + + ast_add(type, new_right); + ast_add(type, new_left); + + return new_type; + } + + default: + pony_assert(0); + return NULL; + } +} + +ast_t* typeparam_current(pass_opt_t* opt, ast_t* type, ast_t* scope) +{ + if(opt->check.frame->iftype_body == NULL) + return type; + + return typeparam_current_inner(type, scope); +} diff --git a/src/libponyc/type/typeparam.h b/src/libponyc/type/typeparam.h index 471af7443f..ba6387b2cc 100644 --- a/src/libponyc/type/typeparam.h +++ b/src/libponyc/type/typeparam.h @@ -4,6 +4,7 @@ #include #include "../ast/ast.h" #include "../ast/frame.h" +#include "../pass/pass.h" PONY_EXTERN_C_BEGIN @@ -29,6 +30,11 @@ ast_t* typeparam_lower(ast_t* typeparamref); */ void typeparam_set_cap(ast_t* typeparamref); +/** + * The constraint of the typeparam in the current scope. + */ +ast_t* typeparam_current(pass_opt_t* opt, ast_t* typeparamref, ast_t* scope); + PONY_EXTERN_C_END #endif diff --git a/test/libponyc/iftype.cc b/test/libponyc/iftype.cc new file mode 100644 index 0000000000..ec1246f959 --- /dev/null +++ b/test/libponyc/iftype.cc @@ -0,0 +1,231 @@ +#include +#include + +#include + +#include "util.h" + + +#define TEST_COMPILE(src) DO(test_compile(src, "ir")) + +#define TEST_ERROR(src, err) \ + { const char* errs[] = {err, NULL}; \ + DO(test_expected_errors(src, "ir", errs)); } + + +class IftypeTest : public PassTest +{}; + + +TEST_F(IftypeTest, ThenClause_TypeConstraint) +{ + 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: T](x: A) =>\n" + " iftype A <: C then\n" + " x.c()\n" + " end"; + + TEST_COMPILE(src); +} + + +TEST_F(IftypeTest, ElseClause_NoTypeConstraint) +{ + 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: T](x: A) =>\n" + " iftype A <: C then\n" + " None\n" + " else\n" + " x.c()\n" + " end"; + + TEST_ERROR(src, "couldn't find 'c' in 'T'"); +} + + +TEST_F(IftypeTest, ThenClause_CapConstraint) +{ + const char* src = + "class C\n" + " fun c() => None\n" + + "actor Main\n" + " new create(env: Env) =>\n" + " foo[C](C)\n" + + " fun foo[A: C](x: A) =>\n" + " iftype A <: C box then\n" + " x.c()\n" + " end"; + + TEST_COMPILE(src); +} + + +TEST_F(IftypeTest, ElseClause_NoCapConstraint) +{ + const char* src = + "class C\n" + " fun c() => None\n" + + "actor Main\n" + " new create(env: Env) =>\n" + " foo[C](C)\n" + + " fun foo[A: C](x: A) =>\n" + " iftype A <: C box then\n" + " None\n" + " else\n" + " x.c()\n" + " end"; + + TEST_ERROR(src, "receiver type is not a subtype of target type"); +} + + +TEST_F(IftypeTest, Cond_InvalidType) +{ + const char* src = + "trait T\n" + "class C is T\n" + + "actor Main\n" + " new create(env: Env) =>\n" + " foo[C]()\n" + + " fun foo[A: T]() =>\n" + " iftype C <: A then\n" + " None\n" + " end"; + + TEST_ERROR(src, "the subtype in an iftype condition must be a type parameter " + "or a tuple of type parameters"); +} + + +TEST_F(IftypeTest, TupleCond_Valid) +{ + const char* src = + "trait T1\n" + "trait T2\n" + "class C1 is T1\n" + "class C2 is T2\n" + + "actor Main\n" + " new create(env: Env) =>\n" + " foo[C1, C2]()\n" + + " fun foo[A: T1, B: T2]() =>\n" + " iftype (A, B) <: (C1, C2) then\n" + " None\n" + " end"; + + TEST_COMPILE(src); +} + + +TEST_F(IftypeTest, TupleCond_InvalidType) +{ + const char* src = + "trait T1\n" + "trait T2\n" + "class C1 is T1\n" + "class C2 is T2\n" + + "actor Main\n" + " new create(env: Env) =>\n" + " foo[C1, C2]()\n" + + " fun foo[A: T1, B: T2]() =>\n" + " iftype (A, B) <: C1 then\n" + " None\n" + " end"; + + TEST_ERROR(src, "iftype subtype is a tuple but supertype isn't"); +} + + +TEST_F(IftypeTest, TupleCond_InvalidCardinality) +{ + const char* src = + "trait T1\n" + "trait T2\n" + "class C1 is T1\n" + "class C2 is T2\n" + + "actor Main\n" + " new create(env: Env) =>\n" + " foo[C1, C2]()\n" + + " fun foo[A: T1, B: T2]() =>\n" + " iftype (A, B) <: (C1, C2, C1) then\n" + " None\n" + " end"; + + TEST_ERROR(src, "the subtype and the supertype in an iftype condition must " + "have the same cardinality"); +} + + +TEST_F(IftypeTest, Codegen_True) +{ + const char* src = + "trait T\n" + "class C is T\n" + + "actor Main\n" + " new create(env: Env) =>\n" + " foo[C](C)\n" + + " fun foo[A: T](x: A) =>\n" + " iftype A <: C then\n" + " @pony_exitcode[None](I32(1))\n" + " end"; + + TEST_COMPILE(src); + + int exit_code = 0; + ASSERT_TRUE(run_program(&exit_code)); + ASSERT_EQ(exit_code, 1); +} + + +TEST_F(IftypeTest, Codegen_False) +{ + const char* src = + "trait T\n" + "class C is T\n" + + "actor Main\n" + " new create(env: Env) =>\n" + " foo[T](C)\n" + + " fun foo[A: T](x: A) =>\n" + " iftype A <: C then\n" + " None\n" + " else\n" + " @pony_exitcode[None](I32(1))\n" + " end"; + + TEST_COMPILE(src); + + int exit_code = 0; + ASSERT_TRUE(run_program(&exit_code)); + ASSERT_EQ(exit_code, 1); +}