diff --git a/CHANGELOG.md b/CHANGELOG.md index eb7cf78..aa6afb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,13 @@ # Changelog -All notable changes to this project will be documented in this file. +All notable changes to this project shall be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +and this project adheres to [Semantic +Versioning](https://semver.org/spec/v2.0.0.html). One deviation from _Keep a +Changelog_ is that "Unreleased" may suggest a specific version bump in case of +breaking changes. - -## [Unreleased] -### Added -### Changed -### Removed -### Fixed - -## [8.x.x] +## [Unreleased] [8.x.x] ### Added ### Changed - `pp_assembler` option to `pp_fate` as it is more specific. diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index ac0ef42..69c9a2b 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -834,62 +834,46 @@ global_env() -> option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}. map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}. --spec infer(aeso_syntax:ast()) -> {aeso_syntax:ast(), aeso_syntax:ast(), [aeso_warnings:warning()]} | {env(), aeso_syntax:ast(), aeso_syntax:ast(), [aeso_warnings:warning()]}. -infer(Contracts) -> - infer(Contracts, []). - -type option() :: return_env | dont_unfold | no_code | debug_mode | term(). +init_ets(Options) -> + -spec init_env(list(option())) -> env(). init_env(_Options) -> global_env(). --spec infer(aeso_syntax:ast(), list(option())) -> - {aeso_syntax:ast(), aeso_syntax:ast(), [aeso_warnings:warning()]} | {env(), aeso_syntax:ast(), aeso_syntax:ast(), [aeso_warnings:warning()]}. -infer([], Options) -> - create_type_errors(), - type_error({no_decls, proplists:get_value(src_file, Options, no_file)}), - destroy_and_report_type_errors(init_env(Options)); -infer(Contracts, Options) -> - ets_init(), %% Init the ETS table state - try - Env = init_env(Options), - create_options(Options), - ets_new(defined_contracts, [bag]), - ets_new(type_vars, [set]), - ets_new(warnings, [bag]), - ets_new(type_vars_variance, [set]), - ets_new(functions_to_implement, [set]), - %% Set the variance for builtin types - ets_insert(type_vars_variance, {"list", [covariant]}), - ets_insert(type_vars_variance, {"option", [covariant]}), - ets_insert(type_vars_variance, {"map", [covariant, covariant]}), - ets_insert(type_vars_variance, {"oracle", [contravariant, covariant]}), - ets_insert(type_vars_variance, {"oracle_query", [covariant, covariant]}), +-spec infer([aeso_syntax:ast()]) -> {aeso_syntax:ast(), aeso_syntax:ast(), [aeso_warnings:warning()]} | {env(), aeso_syntax:ast(), aeso_syntax:ast(), [aeso_warnings:warning()]}. +infer(Modules) -> + infer(Modules, []). - when_warning(warn_unused_functions, fun() -> create_unused_functions() end), - check_modifiers(Env, Contracts), - create_type_errors(), - Contracts1 = identify_main_contract(Contracts, Options), - destroy_and_report_type_errors(Env), - {Env1, Decls} = infer1(Env, Contracts1, [], Options), +infer(Modules, Options) -> + infer(init_env(Options), Modules, Options). + +infer(Env, Modules, Options) -> + ets_init(), + try + {Env1, Modules1} = infer(Env, Modules, [], Options), when_warning(warn_unused_functions, fun() -> destroy_and_report_unused_functions() end), when_option(warn_error, fun() -> destroy_and_report_warnings_as_type_errors() end), WarningsUnsorted = lists:map(fun mk_warning/1, ets_tab2list(warnings)), Warnings = aeso_warnings:sort_warnings(WarningsUnsorted), - {Env2, DeclsFolded, DeclsUnfolded} = - case proplists:get_value(dont_unfold, Options, false) of - true -> {Env1, Decls, Decls}; - false -> E = on_scopes(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end), - {E, Decls, unfold_record_types(E, Decls)} - end, - case proplists:get_value(return_env, Options, false) of - false -> {DeclsFolded, DeclsUnfolded, Warnings}; - true -> {Env2, DeclsFolded, DeclsUnfolded, Warnings} + {Env1, Modules1, Warnings} end after clean_up_ets() end. +-spec infer(aeso_syntax:ast(), list(option())) -> + {aeso_syntax:ast(), aeso_syntax:ast(), [aeso_warnings:warning()]} | {env(), aeso_syntax:ast(), aeso_syntax:ast(), [aeso_warnings:warning()]}. +infer(Env, [[]|Rest], Acc, Options) -> + type_error({no_decls, proplists:get_value(src_file, Options, no_file)}), + infer(Env, Rest, Acc, Options); +infer(Env, [Module|Rest], Acc, Options) -> + {Env1, Modules1} = infer1(Env, Module, [], Options), + infer(Env1, Rest, [Module1|Acc], Options); +infer(Env, [], Acc, _) -> + Modules = lists:reverse(Acc), + {Env, Modules}. + -spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}. infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)}; @@ -2308,7 +2292,17 @@ clean_up_ets() -> %% The interface functions behave as the standard ETS interface. ets_init() -> - put(aeso_ast_infer_types, #{}). + put(aeso_ast_infer_types, #{}), + create_options(Options), + ets_new(defined_contracts, [bag]), + ets_new(type_vars, [set]), + ets_new(warnings, [bag]), + ets_new(functions_to_implement, [set]), + when_warning(warn_unused_functions, fun() -> create_unused_functions() end), + check_modifiers(Env, Contracts), + create_type_var_variance(), + create_type_errors(), + ok. ets_tab_exists(Name) -> Tabs = get(aeso_ast_infer_types), @@ -3347,6 +3341,16 @@ destroy_and_report_warnings_as_type_errors() -> Errors = lists:map(fun mk_t_err_from_warn/1, Warnings), aeso_errors:throw(Errors). %% No-op if Warnings == [] +create_type_vars_variance() -> + ets_new(type_vars_variance, [set]), + %% Set the variance for builtin types + ets_insert(type_vars_variance, {"list", [covariant]}), + ets_insert(type_vars_variance, {"option", [covariant]}), + ets_insert(type_vars_variance, {"map", [covariant, covariant]}), + ets_insert(type_vars_variance, {"oracle", [contravariant, covariant]}), + ets_insert(type_vars_variance, {"oracle_query", [covariant, covariant]}), + ok. + %% Strip current namespace from error message for nicer printing. unqualify(#env{ namespace = NS }, {qid, Ann, Xs}) -> qid(Ann, unqualify1(NS, Xs)); @@ -4151,4 +4155,3 @@ updates_key(Name, Updates) -> indexed(I, Xs) -> lists:zip(lists:seq(I, I + length(Xs) - 1), Xs). - diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index d1d92cb..3c9f330 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -8,22 +8,17 @@ -export([string/1, string/2, string/3, - auto_imports/1, - hash_include/2, decl/0, type/0, body/0, maybe_block/1, - run_parser/2, - run_parser/3]). + run_parser/2]). -include("aeso_parse_lib.hrl"). -import(aeso_parse_lib, [current_file/0, set_current_file/1, current_include_type/0, set_current_include_type/1]). --type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none(). - --type include_hash() :: {string(), binary()}. +-type parse_result() :: aeso_syntax:ast() | none(). escape_errors({ok, Ok}) -> @@ -31,32 +26,21 @@ escape_errors({ok, Ok}) -> escape_errors({error, Err}) -> parse_error(Err). --spec string(string()) -> parse_result(). -string(String) -> - string(String, sets:new(), []). +-spec module(string()) -> parse_result(). +module(String) -> + module(String, []). --spec string(string(), aeso_compiler:options()) -> parse_result(). -string(String, Opts) -> - case lists:keyfind(src_file, 1, Opts) of - {src_file, File} -> string(String, sets:add_element(File, sets:new()), Opts); - false -> string(String, sets:new(), Opts) - end. - --spec string(string(), sets:set(include_hash()), aeso_compiler:options()) -> parse_result(). -string(String, Included, Opts) -> +-spec module(string(), aeso_compiler:options()) -> parse_result(). +module(String, Opts) -> AST = run_parser(file(), String, Opts), - case expand_includes(AST, Included, Opts) of - {ok, AST1} -> AST1; - {error, Err} -> parse_error(Err) - end. - + add_auto_imports(AST). run_parser(P, Inp) -> - escape_errors(parse_and_scan(P, Inp, [])). + escape_errors(scan_and_parse(P, Inp, [])). run_parser(P, Inp, Opts) -> - escape_errors(parse_and_scan(P, Inp, Opts)). + escape_errors(scan_and_parse(P, Inp, Opts)). -parse_and_scan(P, S, Opts) -> +scan_and_parse(P, S, Opts) -> set_current_file(proplists:get_value(src_file, Opts, no_file)), set_current_include_type(proplists:get_value(include_type, Opts, none)), case aeso_scan:scan(S) of @@ -560,6 +544,9 @@ pos_ann(Line, Col) -> , {line, Line} , {col, Col} ]. +top_ann() -> + pos_ann(0, 0). + ann_pos(Ann) -> {proplists:get_value(file, Ann), proplists:get_value(line, Ann), @@ -681,107 +668,13 @@ bad_expr_err(Reason, E) -> %% -- Helper functions ------------------------------------------------------- -expand_includes(AST, Included, Opts) -> +add_auto_imports(AST) -> Ann = [{origin, system}], - AST1 = [ {include, Ann, {string, Ann, File}} - || File <- lists:usort(auto_imports(AST)) ] ++ AST, - expand_includes(AST1, Included, [], Opts). + [ {using, Ann, {con, Ann, Import}} + || Import <- lists:usort(auto_imports(AST)) ] ++ AST. -expand_includes([], Included, Acc, Opts) -> - case lists:member(keep_included, Opts) of - false -> - {ok, lists:reverse(Acc)}; - true -> - {ok, {lists:reverse(Acc), Included}} - end; -expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) -> - case get_include_code(File, Ann, Opts) of - {ok, Code} -> - Hashed = hash_include(File, Code), - case sets:is_element(Hashed, Included) of - false -> - SrcFile = proplists:get_value(src_file, Opts, no_file), - IncludeType = case proplists:get_value(file, Ann) of - SrcFile -> direct; - _ -> indirect - end, - Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}), - Opts2 = lists:keystore(include_type, 1, Opts1, {include_type, IncludeType}), - Included1 = sets:add_element(Hashed, Included), - case parse_and_scan(file(), Code, Opts2) of - {ok, AST1} -> - expand_includes(AST1 ++ AST, Included1, Acc, Opts); - Err = {error, _} -> - Err - end; - true -> - expand_includes(AST, Included, Acc, Opts) - end; - Err = {error, _} -> - Err - end; -expand_includes([E | AST], Included, Acc, Opts) -> - expand_includes(AST, Included, [E | Acc], Opts). - -read_file(File, Opts) -> - case proplists:get_value(include, Opts, {explicit_files, #{}}) of - {file_system, Paths} -> - CandidateNames = [ filename:join(Dir, File) || Dir <- Paths ], - lists:foldr(fun(F, {error, _}) -> file:read_file(F); - (_F, OK) -> OK end, {error, not_found}, CandidateNames); - {explicit_files, Files} -> - case maps:get(binary_to_list(File), Files, not_found) of - not_found -> {error, not_found}; - Src -> {ok, Src} - end; - escript -> - try - Escript = escript:script_name(), - {ok, Sections} = escript:extract(Escript, []), - Archive = proplists:get_value(archive, Sections), - FileName = binary_to_list(filename:join([aesophia, priv, stdlib, File])), - case zip:extract(Archive, [{file_list, [FileName]}, memory]) of - {ok, [{_, Src}]} -> {ok, Src}; - _ -> {error, not_found} - end - catch _:_ -> - {error, not_found} - end - end. - -stdlib_options() -> - StdLibDir = aeso_stdlib:stdlib_include_path(), - case filelib:is_dir(StdLibDir) of - true -> [{include, {file_system, [StdLibDir]}}]; - false -> [{include, escript}] - end. - -get_include_code(File, Ann, Opts) -> - case {read_file(File, Opts), read_file(File, stdlib_options())} of - {{ok, Bin}, {ok, _}} -> - case filename:basename(File) == File of - true -> { error - , fail( ann_pos(Ann) - , "Illegal redefinition of standard library " ++ binary_to_list(File))}; - %% If a path is provided then the stdlib takes lower priority - false -> {ok, binary_to_list(Bin)} - end; - {_, {ok, Bin}} -> - {ok, binary_to_list(Bin)}; - {{ok, Bin}, _} -> - {ok, binary_to_list(Bin)}; - {_, _} -> - {error, {ann_pos(Ann), include_error, File}} - end. - --spec hash_include(string() | binary(), string()) -> include_hash(). -hash_include(File, Code) when is_binary(File) -> - hash_include(binary_to_list(File), Code); -hash_include(File, Code) when is_list(File) -> - {filename:basename(File), crypto:hash(sha256, Code)}. - -auto_imports({comprehension_bind, _, _}) -> [<<"ListInternal.aes">>]; -auto_imports({'..', _}) -> [<<"ListInternal.aes">>]; +auto_imports({comprehension_bind, _, _}) -> ["ListInternal"]; +auto_imports({'..', _}) -> ["ListInternal"]; auto_imports(L) when is_list(L) -> lists:flatmap(fun auto_imports/1, L); auto_imports(T) when is_tuple(T) -> diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index f1c7f99..91923fc 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -13,19 +13,20 @@ -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, pragma/0, fundecl/0]). +-export_type([top_decl/0, decl/0, letbind/0, typedef/0, pragma/0, fundecl/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]). --type ast() :: [decl()]. +-type ast() :: [top_decl()]. -type ann_line() :: integer(). -type ann_col() :: integer(). -type ann_origin() :: system | user. -type ann_format() :: '?:' | hex | infix | prefix | elif. --type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} +-type ann() :: [ {line, ann_line()} | {col, ann_col()} | {file, ann_file()} + | {format, ann_format()} | {origin, ann_origin()} | stateful | private | payable | main | interface | entrypoint]. -type name() :: string(). @@ -38,20 +39,31 @@ -type namespace_alias() :: none | con(). -type namespace_parts() :: none | {for, [id()]} | {hiding, [id()]}. --type decl() :: {contract_main, ann(), con(), [con()], [decl()]} - | {contract_child, ann(), con(), [con()], [decl()]} - | {contract_interface, ann(), con(), [con()], [decl()]} - | {namespace, ann(), con(), [decl()]} - | {include, ann(), {string, ann(), string()}} - | {pragma, ann(), pragma()} - | {type_decl, ann(), id(), [tvar()]} % Only for error msgs - | {type_def, ann(), id(), [tvar()], typedef()} - | {fun_clauses, ann(), id(), type(), [letfun() | fundecl()]} - | {block, ann(), [decl()]} - | {using, ann(), con(), namespace_alias(), namespace_parts()} - | fundecl() - | letfun() - | letval(). % Only for error msgs +% Can't be toplevel +-type scoped_decl() + :: {contract_decl, ann(), con()} + | {namespace_decl, ann(), con()} + | {type_decl, ann(), id(), [tvar()]} % Only for error msgs + | {type_def, ann(), id(), [tvar()], typedef()} + | {fun_clauses, ann(), id(), type(), [letfun() | fundecl()]} + | {block, ann(), [scoped_decl()]} + | fundecl() + | letfun() + | letval() % Only for error msgs + | decl(). + +% Toplevel, can be nested +-type decl() +:: {contract_main, ann(), con(), [con()], [scoped_decl()]} + | {contract_child, ann(), con(), [con()], [scoped_decl()]} + | {contract_interface, ann(), con(), [con()], [scoped_decl()]} + | {namespace, ann(), con(), [scoped_decl()]} + | {using, ann(), con(), namespace_alias(), namespace_parts()}. + +% Toplevel only +-type top_decl() + :: {pragma, ann(), pragma()} + | decl(). -type compiler_version() :: [non_neg_integer()].