From dd94a6bd672ada14b4535382d105bf5ee9a6d58c Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 27 Sep 2019 17:31:10 +0200 Subject: [PATCH] add pragma to check compiler version --- src/aeso_ast_infer_types.erl | 27 ++++++++++++++++++++++- src/aeso_compiler.erl | 12 ++++++++++ src/aeso_parser.erl | 13 ++++++++++- src/aeso_pretty.erl | 5 +++++ src/aeso_syntax.erl | 7 +++++- test/aeso_compiler_tests.erl | 9 ++++++++ test/contracts/wrong_compiler_version.aes | 7 ++++++ 7 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 test/contracts/wrong_compiler_version.aes diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 0aa09db..a55ef69 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -591,7 +591,10 @@ infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) -> check_scope_name_clash(Env, namespace, Name), {Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options), Namespace1 = {namespace, Ann, Name, Code1}, - infer1(pop_scope(Env1), Rest, [Namespace1 | Acc], Options). + infer1(pop_scope(Env1), Rest, [Namespace1 | Acc], Options); +infer1(Env, [{pragma, _, _} | Rest], Acc, Options) -> + %% Pragmas are checked in check_modifiers + infer1(Env, Rest, Acc, Options). check_scope_name_clash(Env, Kind, Name) -> case get_scope(Env, qname(Name)) of @@ -713,10 +716,23 @@ check_modifiers(Env, Contracts) -> _ -> ok end; {namespace, _, _, Decls} -> check_modifiers1(namespace, Decls); + {pragma, Ann, Pragma} -> check_pragma(Env, Ann, Pragma); Decl -> type_error({bad_top_level_decl, Decl}) end || C <- Contracts ], destroy_and_report_type_errors(Env). +-spec check_pragma(env(), aeso_syntax:ann(), aeso_syntax:pragma()) -> ok. +check_pragma(_Env, Ann, {compiler, Op, Ver}) -> + case aeso_compiler:numeric_version() of + {error, Err} -> type_error({failed_to_get_compiler_version, Err}); + {ok, Version} -> + Strip = fun(V) -> lists:reverse(lists:dropwhile(fun(X) -> X == 0 end, lists:reverse(V))) end, + case apply(erlang, Op, [Strip(Version), Strip(Ver)]) of + true -> ok; + false -> type_error({compiler_version_mismatch, Ann, Version, Op, Ver}) + end + end. + -spec check_modifiers1(contract | namespace, [aeso_syntax:decl()] | aeso_syntax:decl()) -> ok. check_modifiers1(What, Decls) when is_list(Decls) -> _ = [ check_modifiers1(What, Decl) || Decl <- Decls ], @@ -2382,6 +2398,15 @@ mk_error({unsolved_bytes_constraint, Ann, split, A, B, C}) -> [ pp_type(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A), pp_type(" - ", B), pp_loc(B)]), mk_t_err(pos(Ann), Msg); +mk_error({failed_to_get_compiler_version, Err}) -> + Msg = io_lib:format("Failed to get compiler version. Error:\n ~p\n", [Err]), + mk_t_err(pos(0, 0), Msg); +mk_error({compiler_version_mismatch, Ann, Version, Op, Bound}) -> + PrintV = fun(V) -> string:join([integer_to_list(N) || N <- V], ".") end, + Msg = io_lib:format("Cannot compile with this version of the compiler,\n" + "because it does not satisfy the constraint" + " ~s ~s ~s\n", [PrintV(Version), Op, PrintV(Bound)]), + mk_t_err(pos(Ann), Msg); mk_error(Err) -> Msg = io_lib:format("Unknown error: ~p\n", [Err]), mk_t_err(pos(0, 0), Msg). diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 4fc00c8..a2ec0bc 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -15,6 +15,7 @@ , create_calldata/3 %% deprecated , create_calldata/4 , version/0 + , numeric_version/0 , sophia_type_to_typerep/1 , to_sophia_value/4 %% deprecated, need a backend , to_sophia_value/5 @@ -65,6 +66,17 @@ version() -> {ok, list_to_binary(VsnString)} end. +-spec numeric_version() -> {ok, [non_neg_integer()]} | {error, term()}. +numeric_version() -> + case version() of + {ok, Bin} -> + [NoSuf | _] = binary:split(Bin, <<"-">>), + Numbers = binary:split(NoSuf, <<".">>, [global]), + {ok, [binary_to_integer(Num) || Num <- Numbers]}; + {error, _} = Err -> + Err + end. + -spec file(string()) -> {ok, map()} | {error, [aeso_errors:error()]}. file(Filename) -> file(Filename, []). diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index d02ee35..4628236 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -87,6 +87,7 @@ decl() -> , ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5})) , ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4}) , ?RULE(keyword(include), str(), {include, get_ann(_1), _2}) + , pragma() %% Type declarations TODO: format annotation for "type bla" vs "type bla()" , ?RULE(keyword(type), id(), {type_decl, _1, _2, []}) @@ -104,6 +105,16 @@ decl() -> , ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2)) ])). +pragma() -> + Op = choice([token(T) || T <- ['<', '=<', '==', '>=', '>']]), + ?RULE(tok('@'), id("compiler"), Op, version(), {pragma, get_ann(_1), {compiler, element(1, _3), _4}}). + +version() -> + ?RULE(token(int), many({tok('.'), token(int)}), mk_version(_1, _2)). + +mk_version({int, _, Maj}, Rest) -> + [Maj | [N || {_, {int, _, N}} <- Rest]]. + fun_or_entry() -> choice([?RULE(keyword(function), {function, _1}), ?RULE(keyword(entrypoint), {entrypoint, _1})]). @@ -406,7 +417,7 @@ token(Tag) -> id(Id) -> ?LET_P({id, A, X} = Y, id(), if X == Id -> Y; - true -> fail({A, "expected 'bytes'"}) + true -> fail({A, "expected '" ++ Id ++ "'"}) end). id_or_addr() -> diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index dc36374..2472ead 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -149,6 +149,7 @@ decl({contract, _, C, Ds}) -> block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds)); decl({namespace, _, C, Ds}) -> block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds)); +decl({pragma, _, Pragma}) -> pragma(Pragma); decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars); decl({type_def, _, T, Vars, Def}) -> Kind = element(1, Def), @@ -170,6 +171,10 @@ decl(D = {letfun, Attrs, _, _, _, _}) -> hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]); decl(D = {letval, _, _, _, _}) -> letdecl("let", D). +-spec pragma(aeso_syntax:pragma()) -> doc(). +pragma({compiler, Op, Ver}) -> + text("@compiler " ++ atom_to_list(Op) ++ " " ++ string:join([integer_to_list(N) || N <- Ver], ".")). + -spec expr(aeso_syntax:expr(), options()) -> doc(). expr(E, Options) -> with_options(Options, fun() -> expr(E) end). diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 2e9f69a..4eb52ef 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -13,7 +13,7 @@ -export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]). -export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]). -export_type([bin_op/0, un_op/0]). --export_type([decl/0, letbind/0, typedef/0]). +-export_type([decl/0, letbind/0, typedef/0, pragma/0]). -export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]). -export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]). -export_type([ast/0]). @@ -36,11 +36,16 @@ -type decl() :: {contract, ann(), con(), [decl()]} | {namespace, ann(), con(), [decl()]} + | {pragma, ann(), pragma()} | {type_decl, ann(), id(), [tvar()]} | {type_def, ann(), id(), [tvar()], typedef()} | {fun_decl, ann(), id(), type()} | letbind(). +-type compiler_version() :: [non_neg_integer()]. + +-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}. + -type letbind() :: {letval, ann(), id(), type(), expr()} | {letfun, ann(), id(), [arg()], type(), expr()}. diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 9d6def9..78d4a96 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -176,6 +176,8 @@ not_yet_compilable(aevm) -> []. -define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)). failing_contracts() -> + {ok, V} = aeso_compiler:numeric_version(), + Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")), %% Parse errors [ ?PARSE_ERROR(field_parse_error, [< "and result types\n" " - bytes(20) (at line 18, column 25)\n" " - 'a (at line 19, column 5)">>]) + , ?TYPE_ERROR(wrong_compiler_version, + [<>, + <>]) ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/wrong_compiler_version.aes b/test/contracts/wrong_compiler_version.aes new file mode 100644 index 0000000..ecb827c --- /dev/null +++ b/test/contracts/wrong_compiler_version.aes @@ -0,0 +1,7 @@ +@compiler < 1.0 +@compiler == 9.9.9 +@compiler >= 0.1 +@compiler =< 100.0.1 + +contract Fail = + entrypoint foo() = ()