Implement loading namespaces with the using
keyword (#338)
* Add using namespace as to scanner and parser * Change the alias from id() to con() * Add using namespace to AST type inference * Allow using namespace to appear in the top level * Allow using namespace to appear inside functions * Add a compiler test for using namespace * Handle name collisions * Implement mk_error for ambiguous_name * Add failing test for ambiguous names * Limit the scope of the used namespaces * Add test for wrong scope of using namespace * Use a single using declaration * Split long line * Forbid using undefined namespaces * Add a test for using undefined namespaces * Change the type of used_namespaces * Add using namespace parts to scanner and parser * Add using namespace parts to ast type inference * Add tests for using namespace parts * Update CHANGELOG.md * Code cleaning * Update the docs * Update the docs about the same alias for multiple namespaces
This commit is contained in:
parent
262452fb70
commit
a7b7aafced
@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
- `Set` stdlib
|
||||
- `Option.force_msg`
|
||||
- Loading namespaces into the current scope (e.g. `using Pair`)
|
||||
### Changed
|
||||
### Removed
|
||||
|
||||
|
@ -248,6 +248,66 @@ Functions in namespaces have access to the same environment (including the
|
||||
with the exception of `state`, `put` and `Chain.event` since these are
|
||||
dependent on the specific state and event types of the contract.
|
||||
|
||||
To avoid mentioning the namespace every time it is used, Sophia allows
|
||||
including the namespace in the current scope with the `using` keyword:
|
||||
```
|
||||
include "Pair.aes"
|
||||
using Pair
|
||||
contract C =
|
||||
type state = int
|
||||
entrypoint init() =
|
||||
let p = (1, 2)
|
||||
fst(p) // this is the same as Pair.fst(p)
|
||||
```
|
||||
|
||||
It is also possible to make an alias for the namespace with the `as` keyword:
|
||||
```
|
||||
include "Pair.aes"
|
||||
contract C =
|
||||
using Pair as P
|
||||
type state = int
|
||||
entrypoint init() =
|
||||
let p = (1, 2)
|
||||
P.fst(p) // this is the same as Pair.fst(p)
|
||||
```
|
||||
|
||||
Having the same alias for multiple namespaces is possible and it allows
|
||||
referening functions that are defined in different namespaces and have
|
||||
different names with the same alias:
|
||||
```
|
||||
namespace Xa = function f() = 1
|
||||
namespace Xb = function g() = 2
|
||||
contract Cntr =
|
||||
using Xa as A
|
||||
using Xb as A
|
||||
type state = int
|
||||
entrypoint init() = A.f() + A.g()
|
||||
```
|
||||
|
||||
Note that using functions with the same name would result in an ambiguous name
|
||||
error:
|
||||
```
|
||||
namespace Xa = function f() = 1
|
||||
namespace Xb = function f() = 2
|
||||
contract Cntr =
|
||||
using Xa as A
|
||||
using Xb as A
|
||||
type state = int
|
||||
|
||||
// the next line has an error because f is defined in both Xa and Xb
|
||||
entrypoint init() = A.f()
|
||||
```
|
||||
|
||||
Importing specific parts of a namespace or hiding these parts can also be
|
||||
done like this:
|
||||
```
|
||||
using Pair for [fst, snd] // this will only import fst and snd
|
||||
using Triple hiding [fst, snd] // this will import everything except for fst and snd
|
||||
```
|
||||
|
||||
Note that it is possible to use a namespace in the top level of the file, in the
|
||||
contract level, namespace level, or in the function level.
|
||||
|
||||
## Splitting code over multiple files
|
||||
|
||||
Code from another file can be included in a contract using an `include`
|
||||
|
@ -102,6 +102,10 @@
|
||||
-type qname() :: [string()].
|
||||
-type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}.
|
||||
|
||||
-type namespace_alias() :: none | name().
|
||||
-type namespace_parts() :: none | {for, [name()]} | {hiding, [name()]}.
|
||||
-type used_namespaces() :: [{qname(), namespace_alias(), namespace_parts()}].
|
||||
|
||||
-type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract | bytecode_hash.
|
||||
|
||||
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
|
||||
@ -126,6 +130,7 @@
|
||||
, typevars = unrestricted :: unrestricted | [name()]
|
||||
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
|
||||
, namespace = [] :: qname()
|
||||
, used_namespaces = [] :: used_namespaces()
|
||||
, in_pattern = false :: boolean()
|
||||
, stateful = false :: boolean()
|
||||
, current_function = none :: none | aeso_syntax:id()
|
||||
@ -312,9 +317,38 @@ bind_contract({Contract, Ann, Id, Contents}, Env)
|
||||
|
||||
%% What scopes could a given name come from?
|
||||
-spec possible_scopes(env(), qname()) -> [qname()].
|
||||
possible_scopes(#env{ namespace = Current}, Name) ->
|
||||
possible_scopes(#env{ namespace = Current, used_namespaces = UsedNamespaces }, Name) ->
|
||||
Qual = lists:droplast(Name),
|
||||
[ lists:sublist(Current, I) ++ Qual || I <- lists:seq(0, length(Current)) ].
|
||||
NewQuals = case lists:filter(fun(X) -> element(2, X) == Qual end, UsedNamespaces) of
|
||||
[] ->
|
||||
[Qual];
|
||||
Namespaces ->
|
||||
lists:map(fun(X) -> element(1, X) end, Namespaces)
|
||||
end,
|
||||
Ret1 = [ lists:sublist(Current, I) ++ Q || I <- lists:seq(0, length(Current)), Q <- NewQuals ],
|
||||
Ret2 = [ Namespace ++ Q || {Namespace, none, _} <- UsedNamespaces, Q <- NewQuals ],
|
||||
lists:usort(Ret1 ++ Ret2).
|
||||
|
||||
-spec visible_in_used_namespaces(used_namespaces(), qname()) -> boolean().
|
||||
visible_in_used_namespaces(UsedNamespaces, QName) ->
|
||||
Qual = lists:droplast(QName),
|
||||
Name = lists:last(QName),
|
||||
case lists:filter(fun({Ns, _, _}) -> Qual == Ns end, UsedNamespaces) of
|
||||
[] ->
|
||||
true;
|
||||
Namespaces ->
|
||||
IsVisible = fun(Namespace) ->
|
||||
case Namespace of
|
||||
{_, _, {for, Names}} ->
|
||||
lists:member(Name, Names);
|
||||
{_, _, {hiding, Names}} ->
|
||||
not lists:member(Name, Names);
|
||||
_ ->
|
||||
true
|
||||
end
|
||||
end,
|
||||
lists:any(IsVisible, Namespaces)
|
||||
end.
|
||||
|
||||
-spec lookup_type(env(), type_id()) -> false | {qname(), type_info()}.
|
||||
lookup_type(Env, Id) ->
|
||||
@ -341,7 +375,7 @@ lookup_env(Env, Kind, Ann, Name) ->
|
||||
end.
|
||||
|
||||
-spec lookup_env1(env(), type | term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info()}.
|
||||
lookup_env1(#env{ namespace = Current, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
Qual = lists:droplast(QName),
|
||||
Name = lists:last(QName),
|
||||
AllowPrivate = lists:prefix(Qual, Current),
|
||||
@ -365,8 +399,12 @@ lookup_env1(#env{ namespace = Current, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
{Ann1, _} = E ->
|
||||
%% Check that it's not private (or we can see private funs)
|
||||
case not is_private(Ann1) orelse AllowPrivate of
|
||||
true ->
|
||||
case visible_in_used_namespaces(UsedNamespaces, QName) of
|
||||
true -> {QName, E};
|
||||
false -> false
|
||||
end;
|
||||
false -> false
|
||||
end
|
||||
end
|
||||
end.
|
||||
@ -803,6 +841,8 @@ infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) ->
|
||||
{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(Env, [Using = {using, _, _, _, _} | Rest], Acc, Options) ->
|
||||
infer1(check_usings(Env, Using), Rest, Acc, Options);
|
||||
infer1(Env, [{pragma, _, _} | Rest], Acc, Options) ->
|
||||
%% Pragmas are checked in check_modifiers
|
||||
infer1(Env, Rest, Acc, Options).
|
||||
@ -859,10 +899,13 @@ infer_contract(Env0, What, Defs0, Options) ->
|
||||
({letfun, _, _, _, _, _}) -> function;
|
||||
({fun_clauses, _, _, _, _}) -> function;
|
||||
({fun_decl, _, _, _}) -> prototype;
|
||||
({using, _, _, _, _}) -> using;
|
||||
(_) -> unexpected
|
||||
end,
|
||||
Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end,
|
||||
{Env1, TypeDefs} = check_typedefs(Env, Get(type, Defs)),
|
||||
OldUsedNamespaces = Env#env.used_namespaces,
|
||||
Env01 = check_usings(Env, Get(using, Defs)),
|
||||
{Env1, TypeDefs} = check_typedefs(Env01, Get(type, Defs)),
|
||||
create_type_errors(),
|
||||
check_unexpected(Get(unexpected, Defs)),
|
||||
Env2 =
|
||||
@ -884,11 +927,13 @@ infer_contract(Env0, What, Defs0, Options) ->
|
||||
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Def) end, FunMap),
|
||||
SCCs = aeso_utils:scc(DepGraph),
|
||||
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
|
||||
%% Remove namespaces used in the current namespace
|
||||
Env5 = Env4#env{ used_namespaces = OldUsedNamespaces },
|
||||
%% Check that `init` doesn't read or write the state
|
||||
check_state_dependencies(Env4, Defs1),
|
||||
destroy_and_report_type_errors(Env4),
|
||||
%% Add inferred types of definitions
|
||||
{Env4, TypeDefs ++ Decls ++ Defs1}.
|
||||
{Env5, TypeDefs ++ Decls ++ Defs1}.
|
||||
|
||||
%% Restructure blocks into multi-clause fundefs (`fun_clauses`).
|
||||
-spec process_blocks([aeso_syntax:decl()]) -> [aeso_syntax:decl()].
|
||||
@ -988,6 +1033,43 @@ check_typedef(Env, {variant_t, Cons}) ->
|
||||
{variant_t, [ {constr_t, Ann, Con, [ check_type(Env, Arg) || Arg <- Args ]}
|
||||
|| {constr_t, Ann, Con, Args} <- Cons ]}.
|
||||
|
||||
check_usings(Env, []) ->
|
||||
Env;
|
||||
check_usings(Env = #env{ used_namespaces = UsedNamespaces }, [{using, Ann, Con, Alias, Parts} | Rest]) ->
|
||||
AliasName = case Alias of
|
||||
none ->
|
||||
none;
|
||||
_ ->
|
||||
qname(Alias)
|
||||
end,
|
||||
case get_scope(Env, qname(Con)) of
|
||||
false ->
|
||||
create_type_errors(),
|
||||
type_error({using_undefined_namespace, Ann, qname(Con)}),
|
||||
destroy_and_report_type_errors(Env);
|
||||
Scope ->
|
||||
Nsp = case Parts of
|
||||
none ->
|
||||
{qname(Con), AliasName, none};
|
||||
{ForOrHiding, Ids} ->
|
||||
IsUndefined = fun(Id) ->
|
||||
proplists:lookup(name(Id), Scope#scope.funs) == none
|
||||
end,
|
||||
UndefinedIds = lists:filter(IsUndefined, Ids),
|
||||
case UndefinedIds of
|
||||
[] ->
|
||||
{qname(Con), AliasName, {ForOrHiding, lists:map(fun name/1, Ids)}};
|
||||
_ ->
|
||||
create_type_errors(),
|
||||
type_error({using_undefined_namespace_parts, Ann, qname(Con), lists:map(fun qname/1, UndefinedIds)}),
|
||||
destroy_and_report_type_errors(Env)
|
||||
end
|
||||
end,
|
||||
check_usings(Env#env{ used_namespaces = UsedNamespaces ++ [Nsp] }, Rest)
|
||||
end;
|
||||
check_usings(Env, Using = {using, _, _, _, _}) ->
|
||||
check_usings(Env, [Using]).
|
||||
|
||||
check_unexpected(Xs) ->
|
||||
[ type_error(X) || X <- Xs ].
|
||||
|
||||
@ -1017,6 +1099,8 @@ check_modifiers_(Env, [{namespace, _, _, Decls} | Rest]) ->
|
||||
check_modifiers_(Env, [{pragma, Ann, Pragma} | Rest]) ->
|
||||
check_pragma(Env, Ann, Pragma),
|
||||
check_modifiers_(Env, Rest);
|
||||
check_modifiers_(Env, [{using, _, _, _, _} | Rest]) ->
|
||||
check_modifiers_(Env, Rest);
|
||||
check_modifiers_(Env, [Decl | Rest]) ->
|
||||
type_error({bad_top_level_decl, Decl}),
|
||||
check_modifiers_(Env, Rest);
|
||||
@ -1770,6 +1854,8 @@ infer_block(Env, _, [{letval, Attrs, Pattern, E}|Rest], BlockType) ->
|
||||
{'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} =
|
||||
infer_case(Env, Attrs, Pattern, PatType, {block, Attrs, Rest}, BlockType),
|
||||
[{letval, Attrs, NewPattern, NewE}|NewRest];
|
||||
infer_block(Env, Attrs, [Using = {using, _, _, _, _} | Rest], BlockType) ->
|
||||
infer_block(check_usings(Env, Using), Attrs, Rest, BlockType);
|
||||
infer_block(Env, Attrs, [E|Rest], BlockType) ->
|
||||
[infer_expr(Env, E)|infer_block(Env, Attrs, Rest, BlockType)].
|
||||
|
||||
@ -2987,6 +3073,17 @@ mk_error({contract_lacks_definition, Type, When}) ->
|
||||
),
|
||||
{Pos, Ctxt} = pp_when(When),
|
||||
mk_t_err(Pos, Msg, Ctxt);
|
||||
mk_error({ambiguous_name, QIds = [{qid, Ann, _} | _]}) ->
|
||||
Names = lists:map(fun(QId) -> io_lib:format("~s at ~s\n", [pp(QId), pp_loc(QId)]) end, QIds),
|
||||
Msg = "Ambiguous name: " ++ lists:concat(Names),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({using_undefined_namespace, Ann, Namespace}) ->
|
||||
Msg = io_lib:format("Cannot use undefined namespace ~s", [Namespace]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({using_undefined_namespace_parts, Ann, Namespace, Parts}) ->
|
||||
PartsStr = lists:concat(lists:join(", ", Parts)),
|
||||
Msg = io_lib:format("The namespace ~s does not define the following names: ~s", [Namespace, PartsStr]),
|
||||
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).
|
||||
|
@ -109,6 +109,7 @@ decl() ->
|
||||
|
||||
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
|
||||
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
|
||||
, using()
|
||||
, pragma()
|
||||
|
||||
%% Type declarations TODO: format annotation for "type bla" vs "type bla()"
|
||||
@ -135,6 +136,21 @@ fundef_or_decl() ->
|
||||
choice([?RULE(id(), tok(':'), type(), {fun_decl, get_ann(_1), _1, _3}),
|
||||
fundef()]).
|
||||
|
||||
using() ->
|
||||
Alias = {keyword(as), con()},
|
||||
For = ?RULE(keyword(for), bracket_list(id()), {for, _2}),
|
||||
Hiding = ?RULE(keyword(hiding), bracket_list(id()), {hiding, _2}),
|
||||
?RULE(keyword(using), con(), optional(Alias), optional(choice(For, Hiding)), using(get_ann(_1), _2, _3, _4)).
|
||||
|
||||
using(Ann, Con, none, none) ->
|
||||
{using, Ann, Con, none, none};
|
||||
using(Ann, Con, {ok, {_, Alias}}, none) ->
|
||||
{using, Ann, Con, Alias, none};
|
||||
using(Ann, Con, none, {ok, List}) ->
|
||||
{using, Ann, Con, none, List};
|
||||
using(Ann, Con, {ok, {_, Alias}}, {ok, List}) ->
|
||||
{using, Ann, Con, Alias, List}.
|
||||
|
||||
pragma() ->
|
||||
Op = choice([token(T) || T <- ['<', '=<', '==', '>=', '>']]),
|
||||
?RULE(tok('@'), id("compiler"), Op, version(), {pragma, get_ann(_1), {compiler, element(1, _3), _4}}).
|
||||
@ -254,7 +270,8 @@ body() ->
|
||||
|
||||
stmt() ->
|
||||
?LAZY_P(choice(
|
||||
[ expr()
|
||||
[ using()
|
||||
, expr()
|
||||
, letdecl()
|
||||
, {switch, keyword(switch), parens(expr()), maybe_block(branch())}
|
||||
, {'if', keyword('if'), parens(expr()), body()}
|
||||
|
@ -45,7 +45,7 @@ lexer() ->
|
||||
|
||||
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
||||
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace",
|
||||
"interface", "main"
|
||||
"interface", "main", "using", "as", "for", "hiding"
|
||||
],
|
||||
KW = string:join(Keywords, "|"),
|
||||
|
||||
|
@ -35,6 +35,9 @@
|
||||
-type qcon() :: {qcon, ann(), [name()]}.
|
||||
-type tvar() :: {tvar, ann(), name()}.
|
||||
|
||||
-type namespace_alias() :: none | con().
|
||||
-type namespace_parts() :: none | {for, [id()]} | {hiding, [id()]}.
|
||||
|
||||
-type decl() :: {contract_main, ann(), con(), [decl()]}
|
||||
| {contract_child, ann(), con(), [decl()]}
|
||||
| {contract_interface, ann(), con(), [decl()]}
|
||||
@ -44,6 +47,7 @@
|
||||
| {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
|
||||
@ -52,7 +56,6 @@
|
||||
|
||||
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
|
||||
|
||||
|
||||
-type letval() :: {letval, ann(), pat(), expr()}.
|
||||
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
|
||||
-type fundecl() :: {fun_decl, ann(), id(), type()}.
|
||||
|
@ -200,6 +200,7 @@ compilable_contracts() ->
|
||||
"clone_simple",
|
||||
"create",
|
||||
"child_contract_init_bug",
|
||||
"using_namespace",
|
||||
"test" % Custom general-purpose test file. Keep it last on the list.
|
||||
].
|
||||
|
||||
@ -781,6 +782,30 @@ failing_contracts() ->
|
||||
[<<?Pos(1,6)
|
||||
"Only one main contract can be defined.">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_ambiguous_name,
|
||||
[ <<?Pos(2,3)
|
||||
"Ambiguous name: Xa.f at line 2, column 3\nXb.f at line 5, column 3">>
|
||||
, <<?Pos(13,23)
|
||||
"Unbound variable A.f at line 13, column 23">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_wrong_scope,
|
||||
[ <<?Pos(19,5)
|
||||
"Unbound variable f at line 19, column 5">>
|
||||
, <<?Pos(21,23)
|
||||
"Unbound variable f at line 21, column 23">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_undefined,
|
||||
[<<?Pos(2,3)
|
||||
"Cannot use undefined namespace MyUndefinedNamespace">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_undefined_parts,
|
||||
[<<?Pos(5,3)
|
||||
"The namespace Nsp does not define the following names: a">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_hidden_parts,
|
||||
[<<?Pos(8,23)
|
||||
"Unbound variable g at line 8, column 23">>
|
||||
])
|
||||
].
|
||||
|
||||
-define(Path(File), "code_errors/" ??File).
|
||||
|
36
test/contracts/using_namespace.aes
Normal file
36
test/contracts/using_namespace.aes
Normal file
@ -0,0 +1,36 @@
|
||||
include "Option.aes"
|
||||
include "Pair.aes"
|
||||
include "String.aes"
|
||||
include "Triple.aes"
|
||||
|
||||
using Pair
|
||||
using Triple hiding [fst, snd]
|
||||
|
||||
namespace Nsp =
|
||||
using Option
|
||||
|
||||
function h() =
|
||||
let op = Some((2, 3, 4))
|
||||
if (is_some(op))
|
||||
thd(force(op)) == 4
|
||||
else
|
||||
false
|
||||
|
||||
contract Cntr =
|
||||
using Nsp
|
||||
|
||||
entrypoint init() = ()
|
||||
|
||||
function f() =
|
||||
let p = (1, 2)
|
||||
if (h())
|
||||
fst(p)
|
||||
else
|
||||
snd(p)
|
||||
|
||||
function g() =
|
||||
using String for [concat]
|
||||
|
||||
let s1 = "abc"
|
||||
let s2 = "def"
|
||||
concat(s1, s2)
|
13
test/contracts/using_namespace_ambiguous_name.aes
Normal file
13
test/contracts/using_namespace_ambiguous_name.aes
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Xa =
|
||||
function f() = 1
|
||||
|
||||
namespace Xb =
|
||||
function f() = 2
|
||||
|
||||
contract Cntr =
|
||||
using Xa as A
|
||||
using Xb as A
|
||||
|
||||
type state = int
|
||||
|
||||
entrypoint init() = A.f()
|
8
test/contracts/using_namespace_hidden_parts.aes
Normal file
8
test/contracts/using_namespace_hidden_parts.aes
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Nsp =
|
||||
function f() = 1
|
||||
function g() = 2
|
||||
|
||||
contract Cntr =
|
||||
using Nsp for [f]
|
||||
|
||||
entrypoint init() = g()
|
4
test/contracts/using_namespace_undefined.aes
Normal file
4
test/contracts/using_namespace_undefined.aes
Normal file
@ -0,0 +1,4 @@
|
||||
contract C =
|
||||
using MyUndefinedNamespace
|
||||
|
||||
entrypoint init() = ()
|
7
test/contracts/using_namespace_undefined_parts.aes
Normal file
7
test/contracts/using_namespace_undefined_parts.aes
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Nsp =
|
||||
function f() = 1
|
||||
|
||||
contract Cntr =
|
||||
using Nsp for [a]
|
||||
|
||||
entrypoint init() = f()
|
21
test/contracts/using_namespace_wrong_scope.aes
Normal file
21
test/contracts/using_namespace_wrong_scope.aes
Normal file
@ -0,0 +1,21 @@
|
||||
namespace Nsp1 =
|
||||
function f() = 1
|
||||
|
||||
namespace Nsp2 =
|
||||
using Nsp1
|
||||
|
||||
function g() = 1
|
||||
|
||||
contract Cntr =
|
||||
using Nsp2
|
||||
|
||||
type state = int
|
||||
|
||||
function x() =
|
||||
using Nsp1
|
||||
f()
|
||||
|
||||
function y() =
|
||||
f()
|
||||
|
||||
entrypoint init() = f()
|
Loading…
x
Reference in New Issue
Block a user