mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Extension methods added. Some initial work on virtual.
This commit is contained in:
@@ -87,6 +87,8 @@ const char *decl_to_name(Decl *decl)
|
||||
return "function";
|
||||
case DECL_GENERIC:
|
||||
return "generic";
|
||||
case DECL_GENFUNC:
|
||||
TODO
|
||||
case DECL_INTERFACE:
|
||||
return "interface";
|
||||
case DECL_MACRO:
|
||||
@@ -178,6 +180,7 @@ Decl *decl_new_with_type(TokenId name, DeclKind decl_type, Visibility visibility
|
||||
case DECL_ARRAY_VALUE:
|
||||
case DECL_IMPORT:
|
||||
case DECL_MACRO:
|
||||
case DECL_GENFUNC:
|
||||
case DECL_GENERIC:
|
||||
case DECL_CT_IF:
|
||||
case DECL_CT_ELSE:
|
||||
@@ -864,6 +867,7 @@ void fprint_decl_recursive(Context *context, FILE *file, Decl *decl, int indent)
|
||||
{
|
||||
case DECL_INTERFACE:
|
||||
DUMPF("(interface %s", decl->name);
|
||||
DUMPDECLS(decl->interface_decl.members);
|
||||
DUMPDECLS(decl->interface_decl.functions);
|
||||
DUMPEND();
|
||||
case DECL_VAR:
|
||||
@@ -910,6 +914,16 @@ void fprint_decl_recursive(Context *context, FILE *file, Decl *decl, int indent)
|
||||
indent--;
|
||||
DUMPAST(decl->macro_decl.body);
|
||||
DUMPEND();
|
||||
case DECL_GENFUNC:
|
||||
DUMPF("(macro %s", decl->name);
|
||||
DUMPTI(decl->macro_decl.rtype);
|
||||
indent++;
|
||||
DUMP("(params");
|
||||
DUMPDECLS(decl->macro_decl.parameters);
|
||||
DUMPE();
|
||||
indent--;
|
||||
DUMPAST(decl->macro_decl.body);
|
||||
DUMPEND();
|
||||
case DECL_FUNC:
|
||||
DUMPF("(func %s", decl->name);
|
||||
if (decl->func_decl.type_parent)
|
||||
|
||||
@@ -183,6 +183,7 @@ static void register_generic_decls(Module *module, Decl **decls)
|
||||
case DECL_DISTINCT:
|
||||
case DECL_ENUM:
|
||||
case DECL_GENERIC:
|
||||
case DECL_GENFUNC:
|
||||
case DECL_INTERFACE:
|
||||
case DECL_ERR:
|
||||
case DECL_FUNC:
|
||||
|
||||
@@ -411,6 +411,7 @@ typedef struct
|
||||
typedef struct
|
||||
{
|
||||
Decl **functions;
|
||||
Decl **members;
|
||||
} InterfaceDecl;
|
||||
|
||||
typedef struct
|
||||
@@ -1234,7 +1235,7 @@ typedef struct Module_
|
||||
|
||||
Ast **files; // Asts
|
||||
|
||||
Decl** functions;
|
||||
Decl** method_extensions;
|
||||
STable symbols;
|
||||
STable public_symbols;
|
||||
struct Context_ **contexts;
|
||||
@@ -1762,6 +1763,7 @@ const char *resolve_status_to_string(ResolveStatus status);
|
||||
#define SEMA_TOKID_ERROR(_tok_id, ...) sema_error_range(source_span_from_token_id(_tok_id), __VA_ARGS__)
|
||||
#define SEMA_ERROR(_node, ...) sema_error_range((_node)->span, __VA_ARGS__)
|
||||
#define SEMA_PREV(_node, ...) sema_prev_at_range3((_node)->span, __VA_ARGS__)
|
||||
#define SEMA_TOKID_PREV(_tok_id, ...) sema_prev_at_range3(source_span_from_token_id(_tok_id), __VA_ARGS__)
|
||||
|
||||
void sema_analysis_pass_process_imports(Module *module);
|
||||
void sema_analysis_pass_register_globals(Module *module);
|
||||
@@ -1786,6 +1788,8 @@ bool sema_expr_analyse_assign_right_side(Context *context, Expr *expr, Type *lef
|
||||
|
||||
Decl *sema_resolve_symbol_in_current_dynamic_scope(Context *context, const char *symbol);
|
||||
Decl *sema_resolve_parameterized_symbol(Context *context, TokenId symbol, Path *path);
|
||||
Decl *sema_resolve_method(Context *context, Decl *type, const char *method_name, Decl **ambiguous_ref, Decl **private_ref);
|
||||
Decl *sema_find_extension_method_in_module(Module *module, Type *type, const char *method_name);
|
||||
Decl *sema_resolve_normal_symbol(Context *context, TokenId symbol, Path *path, bool handle_error);
|
||||
Decl *sema_resolve_string_symbol(Context *context, const char *symbol, SourceSpan span, Path *path);
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ void context_register_global_decl(Context *context, Decl *decl)
|
||||
vec_add(context->interfaces, decl);
|
||||
decl_set_external_name(decl);
|
||||
break;
|
||||
case DECL_GENFUNC:
|
||||
case DECL_GENERIC:
|
||||
vec_add(context->generics, decl);
|
||||
decl_set_external_name(decl);
|
||||
|
||||
@@ -498,6 +498,7 @@ Decl *copy_decl(Decl *decl)
|
||||
case DECL_INTERFACE:
|
||||
copy_decl_type(copy);
|
||||
MACRO_COPY_DECL_LIST(copy->interface_decl.functions);
|
||||
MACRO_COPY_DECL_LIST(copy->interface_decl.members);
|
||||
break;
|
||||
case DECL_FUNC:
|
||||
MACRO_COPY_TYPE(copy->func_decl.type_parent);
|
||||
@@ -560,6 +561,7 @@ Decl *copy_decl(Decl *decl)
|
||||
break;
|
||||
case DECL_ARRAY_VALUE:
|
||||
TODO
|
||||
case DECL_GENFUNC:
|
||||
case DECL_MACRO:
|
||||
MACRO_COPY_TYPE(decl->macro_decl.type_parent);
|
||||
MACRO_COPY_DECL_LIST(decl->macro_decl.parameters);
|
||||
|
||||
@@ -149,6 +149,7 @@ typedef enum
|
||||
DECL_INTERFACE,
|
||||
DECL_LABEL,
|
||||
DECL_MACRO,
|
||||
DECL_GENFUNC,
|
||||
DECL_STRUCT,
|
||||
DECL_TYPEDEF,
|
||||
DECL_UNION,
|
||||
@@ -158,7 +159,7 @@ typedef enum
|
||||
#define NON_TYPE_DECLS DECL_ARRAY_VALUE: case DECL_IMPORT: case DECL_MACRO: \
|
||||
case DECL_GENERIC: case DECL_CT_IF: case DECL_CT_ELSE: case DECL_CT_ELIF: \
|
||||
case DECL_CT_SWITCH: case DECL_CT_CASE: case DECL_ATTRIBUTE: case DECL_LABEL: \
|
||||
case DECL_DEFINE: case DECL_CT_ASSERT
|
||||
case DECL_DEFINE: case DECL_CT_ASSERT: case DECL_GENFUNC
|
||||
|
||||
typedef enum
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ static bool context_next_is_type_and_not_ident(Context *context)
|
||||
{
|
||||
if (context->tok.type == TOKEN_IDENT)
|
||||
{
|
||||
if (context->next_tok.type != TOKEN_COLON) return false;
|
||||
if (context->next_tok.type != TOKEN_SCOPE) return false;
|
||||
return context_next_is_type_with_path_prefix(context);
|
||||
}
|
||||
return token_is_any_type(context->tok.type);
|
||||
@@ -1948,7 +1948,7 @@ static inline Decl *parse_interface_declaration(Context *context, Visibility vis
|
||||
return poisoned_decl;
|
||||
}
|
||||
Decl *function = TRY_DECL_OR(parse_func_definition(context, visibility, true), poisoned_decl);
|
||||
vec_add(decl->interface_decl.functions, function);
|
||||
vec_add(decl->methods, function);
|
||||
}
|
||||
|
||||
CONSUME_OR(TOKEN_RBRACE, poisoned_decl);
|
||||
|
||||
@@ -464,6 +464,43 @@ bool cast_may_explicit(Type *from_type, Type *to_type)
|
||||
UNREACHABLE
|
||||
}
|
||||
|
||||
static bool may_cast_to_virtual(Type *virtual, Type *from)
|
||||
{
|
||||
assert(from->canonical == from);
|
||||
|
||||
// 1. We need a pointer, we can't cast from a non pointer.
|
||||
if (from->type_kind != TYPE_POINTER) return false;
|
||||
|
||||
// 2. Virtual* converts to anything, including ints
|
||||
if (virtual->type_kind == TYPE_VIRTUAL_ANY) return true;
|
||||
|
||||
// 3. Get the data.
|
||||
Decl *virtual_decl = virtual->decl;
|
||||
Decl **methods = virtual_decl->interface_decl.functions;
|
||||
|
||||
// 4. No variables nor members? Then this is essentially a virtual*
|
||||
if (!vec_size(methods) && !vec_size(virtual_decl->strukt.members)) return true;
|
||||
|
||||
// 5. Look at the pointer.
|
||||
Type *pointee = from->pointer;
|
||||
|
||||
// 6. Is this an array, if so it doesn't have any functions,
|
||||
// so we implicitly lower to the first element.
|
||||
if (pointee->type_kind == TYPE_ARRAY)
|
||||
{
|
||||
pointee = pointee->array.base;
|
||||
}
|
||||
|
||||
// Do this: create a function that returns a matching interface method.
|
||||
// store this decl.
|
||||
// Same with looking at members -> store the Decl.
|
||||
// Later, generating the table we provide the decl backend ref and the offset.
|
||||
// Note that matching types should take into account the first element.
|
||||
// Also go recursively into substructs structs
|
||||
// Note that this resolution cannot be cached completely due to the module import lookup
|
||||
|
||||
TODO;
|
||||
}
|
||||
/**
|
||||
* Can the conversion occur implicitly?
|
||||
*/
|
||||
@@ -581,8 +618,7 @@ bool cast_may_implicit(Type *from_type, Type *to_type)
|
||||
// 10. Virtual cast
|
||||
if (to->type_kind == TYPE_VIRTUAL)
|
||||
{
|
||||
TODO
|
||||
//
|
||||
return may_cast_to_virtual(to, from);
|
||||
}
|
||||
|
||||
// 11. Substruct cast, if the first member is inline, see if we can cast to this member.
|
||||
|
||||
@@ -602,7 +602,67 @@ static inline bool sema_analyse_enum(Context *context, Decl *decl)
|
||||
return success;
|
||||
}
|
||||
|
||||
static inline const char *name_by_decl(Decl *method_like)
|
||||
{
|
||||
switch (method_like->decl_kind)
|
||||
{
|
||||
case DECL_MACRO:
|
||||
return "macro method";
|
||||
case DECL_FUNC:
|
||||
return "method";
|
||||
case DECL_GENFUNC:
|
||||
return "generic method";
|
||||
default:
|
||||
UNREACHABLE
|
||||
}
|
||||
}
|
||||
static inline bool sema_add_method_like(Context *context, Type *parent_type, Decl *method_like)
|
||||
{
|
||||
assert(parent_type->canonical == parent_type);
|
||||
Decl *parent = parent_type->decl;
|
||||
const char *name = method_like->name;
|
||||
Decl *method = sema_find_extension_method_in_module(context->module, parent_type, name);
|
||||
if (method)
|
||||
{
|
||||
SEMA_TOKID_ERROR(method_like->name_token, "This %s is already defined in this module.", name_by_decl(method_like));
|
||||
SEMA_TOKID_PREV(method->name_token, "The previous definition was here.");
|
||||
return false;
|
||||
}
|
||||
Decl *ambiguous = NULL;
|
||||
Decl *private = NULL;
|
||||
method = sema_resolve_method(context, parent, name, &ambiguous, &private);
|
||||
if (method)
|
||||
{
|
||||
SEMA_TOKID_ERROR(method_like->name_token, "This %s is already defined for '%s'.", name_by_decl(method_like), parent_type->name);
|
||||
SEMA_TOKID_PREV(method->name_token, "The previous definition was here.");
|
||||
return false;
|
||||
}
|
||||
scratch_buffer_clear();
|
||||
if (method_like->visibility <= VISIBLE_MODULE)
|
||||
{
|
||||
scratch_buffer_append(parent->name);
|
||||
scratch_buffer_append_char('.');
|
||||
scratch_buffer_append(method_like->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
scratch_buffer_append(parent->external_name);
|
||||
scratch_buffer_append("__");
|
||||
scratch_buffer_append(method_like->name);
|
||||
}
|
||||
method_like->external_name = scratch_buffer_interned();
|
||||
DEBUG_LOG("Method-like '%s.%s' analysed.", parent->name, method_like->name);
|
||||
if (parent->module == context->module)
|
||||
{
|
||||
vec_add(parent->methods, method_like);
|
||||
}
|
||||
else
|
||||
{
|
||||
vec_add(context->module->method_extensions, method_like);
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
static inline bool sema_analyse_method(Context *context, Decl *decl)
|
||||
{
|
||||
@@ -615,35 +675,8 @@ static inline bool sema_analyse_method(Context *context, Decl *decl)
|
||||
type_to_error_string(decl->func_decl.type_parent->type));
|
||||
return false;
|
||||
}
|
||||
Decl *parent = parent_type->type->decl;
|
||||
VECEACH(parent->methods, i)
|
||||
{
|
||||
Decl *function = parent->methods[i];
|
||||
if (function->name == decl->name)
|
||||
{
|
||||
SEMA_ERROR(decl, "Duplicate name '%s' for method.", function->name);
|
||||
SEMA_PREV(function, "Previous definition here.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
scratch_buffer_clear();
|
||||
if (decl->visibility <= VISIBLE_MODULE)
|
||||
{
|
||||
scratch_buffer_append(parent->name);
|
||||
scratch_buffer_append_char('.');
|
||||
scratch_buffer_append(decl->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
scratch_buffer_append(parent->external_name);
|
||||
scratch_buffer_append("__");
|
||||
scratch_buffer_append(decl->name);
|
||||
}
|
||||
decl->external_name = scratch_buffer_interned();
|
||||
DEBUG_LOG("Method '%s.%s' analysed.", parent->name, decl->name);
|
||||
vec_add(parent->methods, decl);
|
||||
|
||||
return true;
|
||||
Type *type = parent_type->type->canonical;
|
||||
return sema_add_method_like(context, type, decl);
|
||||
}
|
||||
|
||||
static inline AttributeType attribute_by_name(Attr *attr)
|
||||
@@ -971,21 +1004,7 @@ static bool sema_analyse_macro_method(Context *context, Decl *decl)
|
||||
SEMA_ERROR(first_param, "The first parameter must be a regular value parameter.");
|
||||
return false;
|
||||
}
|
||||
Decl *parent = parent_type->decl;
|
||||
VECEACH(parent->methods, i)
|
||||
{
|
||||
Decl *function = parent->methods[i];
|
||||
if (function->name == decl->name)
|
||||
{
|
||||
SEMA_ERROR(decl, "Duplicate name '%s' for macro method.", function->name);
|
||||
SEMA_PREV(function, "Previous definition here.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
DEBUG_LOG("Macro method '%s.%s' analysed.", parent->name, decl->name);
|
||||
vec_add(parent->methods, decl);
|
||||
|
||||
return true;
|
||||
return sema_add_method_like(context, parent_type, decl);
|
||||
}
|
||||
|
||||
static inline bool sema_analyse_macro(Context *context, Decl *decl)
|
||||
@@ -1439,6 +1458,8 @@ bool sema_analyse_decl(Context *context, Decl *decl)
|
||||
case DECL_FUNC:
|
||||
if (!sema_analyse_func(context, decl)) return decl_poison(decl);
|
||||
break;
|
||||
case DECL_GENFUNC:
|
||||
TODO
|
||||
case DECL_MACRO:
|
||||
if (!sema_analyse_macro(context, decl)) return decl_poison(decl);
|
||||
break;
|
||||
|
||||
@@ -224,6 +224,7 @@ static inline bool sema_cast_ident_rvalue(Context *context, Type *to, Expr *expr
|
||||
UNREACHABLE
|
||||
case DECL_IMPORT:
|
||||
case DECL_GENERIC:
|
||||
case DECL_GENFUNC:
|
||||
case DECL_CT_IF:
|
||||
case DECL_CT_ELSE:
|
||||
case DECL_CT_ELIF:
|
||||
@@ -2380,6 +2381,16 @@ CHECK_DEEPER:
|
||||
member = sema_resolve_symbol_in_current_dynamic_scope(context, kw);
|
||||
SCOPE_END;
|
||||
|
||||
if (!member)
|
||||
{
|
||||
Decl *ambiguous = NULL;
|
||||
Decl *private = NULL;
|
||||
member = sema_resolve_method(context, decl, kw, &ambiguous, &private);
|
||||
if (member)
|
||||
{
|
||||
context_register_external_symbol(context, member);
|
||||
}
|
||||
}
|
||||
// 11. If we didn't find a match...
|
||||
if (!member)
|
||||
{
|
||||
|
||||
@@ -231,6 +231,72 @@ static Decl *sema_resolve_symbol(Context *context, const char *symbol_str, Sourc
|
||||
return decl;
|
||||
}
|
||||
|
||||
Decl *sema_find_extension_method_in_module(Module *module, Type *type, const char *method_name)
|
||||
{
|
||||
Decl **extensions = module->method_extensions;
|
||||
VECEACH(extensions, i)
|
||||
{
|
||||
Decl *extension = extensions[i];
|
||||
if (extension->name != method_name) continue;
|
||||
switch (extension->decl_kind)
|
||||
{
|
||||
case DECL_FUNC:
|
||||
if (extension->func_decl.type_parent->type == type) return extension;
|
||||
break;
|
||||
case DECL_MACRO:
|
||||
case DECL_GENFUNC:
|
||||
if (extension->macro_decl.type_parent->type == type) return extension;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Decl *sema_resolve_method(Context *context, Decl *type, const char *method_name, Decl **ambiguous_ref, Decl **private_ref)
|
||||
{
|
||||
// 1. Look at the previously defined ones.
|
||||
VECEACH(type->methods, i)
|
||||
{
|
||||
Decl *func = type->methods[i];
|
||||
if (method_name == func->name) return func;
|
||||
}
|
||||
// 2. Make a module lookup
|
||||
Decl *previously_found = NULL;
|
||||
Type *actual_type = type->type;
|
||||
Decl *private_type = NULL;
|
||||
Decl *result = NULL;
|
||||
VECEACH(context->imports, i)
|
||||
{
|
||||
Decl *import = context->imports[i];
|
||||
|
||||
if (import->module->is_generic) continue;
|
||||
|
||||
Decl *found = sema_find_extension_method_in_module(import->module, actual_type, method_name);
|
||||
if (!found) continue;
|
||||
if (found->visibility <= VISIBLE_MODULE && !import->import.private)
|
||||
{
|
||||
private_type = found;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result)
|
||||
{
|
||||
*ambiguous_ref = previously_found;
|
||||
*private_ref = NULL;
|
||||
return NULL;
|
||||
}
|
||||
result = found;
|
||||
}
|
||||
|
||||
if (result && private_type)
|
||||
{
|
||||
private_type = NULL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Decl *sema_resolve_parameterized_symbol(Context *context, TokenId symbol, Path *path)
|
||||
{
|
||||
Decl *ambiguous_other_decl = NULL;
|
||||
|
||||
@@ -119,6 +119,7 @@ static bool sema_resolve_type_identifier(Context *context, TypeInfo *type_info)
|
||||
case DECL_ARRAY_VALUE:
|
||||
case DECL_IMPORT:
|
||||
case DECL_MACRO:
|
||||
case DECL_GENFUNC:
|
||||
case DECL_GENERIC:
|
||||
case DECL_LABEL:
|
||||
SEMA_TOKID_ERROR(type_info->unresolved.name_loc, "This is not a type.");
|
||||
|
||||
31
test/test_suite/macro_methods/macro_methods_defined_twice.c3
Normal file
31
test/test_suite/macro_methods/macro_methods_defined_twice.c3
Normal file
@@ -0,0 +1,31 @@
|
||||
module foo;
|
||||
|
||||
struct Bar
|
||||
{
|
||||
int x;
|
||||
}
|
||||
|
||||
module baz;
|
||||
import foo;
|
||||
import std::io;
|
||||
|
||||
macro void foo::Bar.test(Bar *bar)
|
||||
{
|
||||
io::println("Inside of baz::Bar.test");
|
||||
}
|
||||
|
||||
macro void Bar.test(Bar *bar) // #error: This macro method is already defined in this module
|
||||
{
|
||||
io::println("Inside of baz::Bar.test");
|
||||
}
|
||||
|
||||
module abc;
|
||||
import foo;
|
||||
import baz;
|
||||
|
||||
func void main()
|
||||
{
|
||||
Bar bar;
|
||||
bar.test();
|
||||
}
|
||||
|
||||
37
test/test_suite/methods/extension_method.c3t
Normal file
37
test/test_suite/methods/extension_method.c3t
Normal file
@@ -0,0 +1,37 @@
|
||||
module foo;
|
||||
|
||||
struct Bar
|
||||
{
|
||||
int x;
|
||||
}
|
||||
|
||||
module baz;
|
||||
import foo;
|
||||
import std::io;
|
||||
|
||||
func void foo::Bar.test(Bar *bar)
|
||||
{
|
||||
io::println("Inside of baz::Bar.test");
|
||||
}
|
||||
|
||||
module abc;
|
||||
import foo;
|
||||
import baz;
|
||||
|
||||
func void main()
|
||||
{
|
||||
Bar bar;
|
||||
bar.test();
|
||||
}
|
||||
|
||||
// #expect: abc.ll
|
||||
|
||||
declare void @foo.Bar__test(%Bar*)
|
||||
|
||||
define void @main()
|
||||
entry:
|
||||
%bar = alloca %Bar, align 4
|
||||
%0 = bitcast %Bar* %bar to i8*
|
||||
call void @llvm.memset.p0i8.i64(i8* align 4 %0, i8 0, i64 4, i1 false)
|
||||
call void @foo.Bar__test(%Bar* %bar)
|
||||
ret void
|
||||
30
test/test_suite/methods/extension_method_already_exist.c3
Normal file
30
test/test_suite/methods/extension_method_already_exist.c3
Normal file
@@ -0,0 +1,30 @@
|
||||
module foo;
|
||||
|
||||
func void Bar.test(Bar *bar)
|
||||
{
|
||||
io::println("Inside of baz::Bar.test");
|
||||
}
|
||||
|
||||
struct Bar
|
||||
{
|
||||
int x;
|
||||
}
|
||||
|
||||
module baz;
|
||||
import foo;
|
||||
import std::io;
|
||||
|
||||
func void foo::Bar.test(Bar *bar) // #error: This method is already defined for 'Bar'
|
||||
{
|
||||
io::println("Inside of baz::Bar.test");
|
||||
}
|
||||
|
||||
module abc;
|
||||
import foo;
|
||||
import baz;
|
||||
|
||||
func void main()
|
||||
{
|
||||
Bar bar;
|
||||
bar.test();
|
||||
}
|
||||
31
test/test_suite/methods/methods_defined_twice.c3
Normal file
31
test/test_suite/methods/methods_defined_twice.c3
Normal file
@@ -0,0 +1,31 @@
|
||||
module foo;
|
||||
|
||||
struct Bar
|
||||
{
|
||||
int x;
|
||||
}
|
||||
|
||||
module baz;
|
||||
import foo;
|
||||
import std::io;
|
||||
|
||||
func void foo::Bar.test(Bar *bar)
|
||||
{
|
||||
io::println("Inside of baz::Bar.test");
|
||||
}
|
||||
|
||||
func void Bar.test(Bar *bar) // #error: This method is already defined in this module
|
||||
{
|
||||
io::println("Inside of baz::Bar.test");
|
||||
}
|
||||
|
||||
module abc;
|
||||
import foo;
|
||||
import baz;
|
||||
|
||||
func void main()
|
||||
{
|
||||
Bar bar;
|
||||
bar.test();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user