diff --git a/doc/docs.md b/doc/docs.md index f49bba36be..b58d30105a 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -29,7 +29,7 @@ you can do in V. * [Numbers](#numbers) * [Arrays](#arrays) * [Maps](#maps) -* [Imports](#imports) +* [Module Imports](#module-imports) * [Statements & Expressions](#statements--expressions) * [If](#if) * [In Operator](#in-operator) @@ -547,7 +547,13 @@ numbers := { } ``` -## Imports +## Module Imports + +For information about creating a module, see [Modules](#modules) + +### Importing a Module + +Modules can be imported using keyword `import`. ```v import os @@ -558,7 +564,48 @@ fn main() { } ``` -Modules can be imported using keyword `import`. When using types, functions, and constants from other modules, the full path must be specified. In the example above, `name := input()` wouldn't work. That means that it's always clear from which module a function is called. +When using constants from other modules, the module name must be prefixed. However, +you can import functions and types from other modules directly: + +```v +import os { input } +import crypto.sha256 { sum } +import time { Time } +``` + +### Module Import Aliasing + +Any imported module name can be aliased using the `as` keyword: + +NOTE: this example will not compile unless you have created `mymod/sha256.v` +```v +import crypto.sha256 +import mymod.sha256 as mysha256 + +fn main() { + v_hash := sha256.sum('hi'.bytes()).hex() + my_hash := mysha256.sum('hi'.bytes()).hex() + assert my_hash == v_hash +} +``` + +You cannot alias an imported function or type. +However, you _can_ redeclare a type. + +```v +import time + +type MyTime time.Time + +fn main() { + my_time := MyTime{ + year: 2020, + month: 12, + day: 25 + } + println(my_time.unix_time()) +} +``` ## Statements & Expressions diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index aac07ab0c0..4383238783 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -211,6 +211,20 @@ pub: pos token.Position mod string alias string +pub mut: + syms []ImportSymbol +} + +pub enum ImportSymbolKind { + fn_ + type_ +} + +pub struct ImportSymbol { +pub: + pos token.Position + name string + kind ImportSymbolKind } pub struct AnonFn { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 37f00b651f..5f8bf601eb 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -382,6 +382,10 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type { if type_sym.kind == .alias { info_t := type_sym.info as table.Alias sym := c.table.get_type_symbol(info_t.parent_type) + if sym.kind == .placeholder { // pending import symbol did not resolve + c.error('unknown struct: $type_sym.name', struct_init.pos) + return table.void_type + } if sym.kind != .struct_ { c.error('alias type name: $sym.name is not struct type', struct_init.pos) } @@ -2055,7 +2059,9 @@ fn (mut c Checker) stmt(node ast.Stmt) { ast.GotoLabel {} ast.GotoStmt {} ast.HashStmt {} - ast.Import {} + ast.Import { + c.import_stmt(node) + } ast.InterfaceDecl { c.interface_decl(node) } @@ -2087,6 +2093,26 @@ fn (mut c Checker) stmt(node ast.Stmt) { } } +fn (mut c Checker) import_stmt(imp ast.Import) { + for sym in imp.syms { + name := '$imp.mod\.$sym.name' + if sym.kind == .fn_ { + c.table.find_fn(name) or { + c.error('module `$imp.mod` has no public fn named `$sym.name\()`', sym.pos) + } + } + if sym.kind == .type_ { + if type_sym := c.table.find_type(name) { + if type_sym.kind == .placeholder { + c.error('module `$imp.mod` has no public type `$sym.name\{}`', sym.pos) + } + } else { + c.error('module `$imp.mod` has no public type `$sym.name\{}`', sym.pos) + } + } + } +} + fn (mut c Checker) stmts(stmts []ast.Stmt) { mut unreachable := token.Position{ line_nr: -1 @@ -2919,7 +2945,6 @@ pub fn (mut c Checker) postfix_expr(mut node ast.PostfixExpr) table.Type { typ_sym := c.table.get_type_symbol(typ) // if !typ.is_number() { if !typ_sym.is_number() { - println(typ_sym.kind.str()) c.error('invalid operation: $node.op.str() (non-numeric type `$typ_sym.name`)', node.pos) } else { @@ -3232,7 +3257,6 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { mut idx := 0 for i, m in sym.methods { if m.name == node.name { - println('got it') idx = i break } diff --git a/vlib/v/checker/tests/import_not_same_line_err.out b/vlib/v/checker/tests/import_not_same_line_err.out index 4be8327a2f..8152295549 100644 --- a/vlib/v/checker/tests/import_not_same_line_err.out +++ b/vlib/v/checker/tests/import_not_same_line_err.out @@ -1,4 +1,4 @@ -vlib/v/checker/tests/import_not_same_line_err.v:2:2: error: `import` and `module` must be at same line +vlib/v/checker/tests/import_not_same_line_err.v:2:2: error: `import` statements must be a single line 1 | import 2 | time | ~~~~ diff --git a/vlib/v/checker/tests/import_symbol_empty.out b/vlib/v/checker/tests/import_symbol_empty.out new file mode 100644 index 0000000000..86b42c37cd --- /dev/null +++ b/vlib/v/checker/tests/import_symbol_empty.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/import_symbol_empty.v:1:12: error: empty `os` import set, remove `{}` + 1 | import os {} + | ^ diff --git a/vlib/v/checker/tests/import_symbol_empty.vv b/vlib/v/checker/tests/import_symbol_empty.vv new file mode 100644 index 0000000000..252aceb357 --- /dev/null +++ b/vlib/v/checker/tests/import_symbol_empty.vv @@ -0,0 +1 @@ +import os {} diff --git a/vlib/v/checker/tests/import_symbol_fn_err.out b/vlib/v/checker/tests/import_symbol_fn_err.out new file mode 100644 index 0000000000..e8038fbdc2 --- /dev/null +++ b/vlib/v/checker/tests/import_symbol_fn_err.out @@ -0,0 +1,11 @@ +vlib/v/checker/tests/import_symbol_fn_err.v:1:17: error: module `crypto` has no public fn named `userper()` + 1 | import crypto { userper } + | ~~~~~~~ + 2 | fn main() { + 3 | usurper() +vlib/v/checker/tests/import_symbol_fn_err.v:3:3: error: unknown function: usurper + 1 | import crypto { userper } + 2 | fn main() { + 3 | usurper() + | ~~~~~~~~~ + 4 | } diff --git a/vlib/v/checker/tests/import_symbol_fn_err.vv b/vlib/v/checker/tests/import_symbol_fn_err.vv new file mode 100644 index 0000000000..04ba99b789 --- /dev/null +++ b/vlib/v/checker/tests/import_symbol_fn_err.vv @@ -0,0 +1,4 @@ +import crypto { userper } +fn main() { + usurper() +} diff --git a/vlib/v/checker/tests/import_symbol_invalid.out b/vlib/v/checker/tests/import_symbol_invalid.out new file mode 100644 index 0000000000..33eb2d6351 --- /dev/null +++ b/vlib/v/checker/tests/import_symbol_invalid.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/import_symbol_invalid.v:1:17: error: import syntax error, please specify a valid fn or type name + 1 | import crypto { *_v } + | ^ diff --git a/vlib/v/checker/tests/import_symbol_invalid.vv b/vlib/v/checker/tests/import_symbol_invalid.vv new file mode 100644 index 0000000000..485c15132f --- /dev/null +++ b/vlib/v/checker/tests/import_symbol_invalid.vv @@ -0,0 +1 @@ +import crypto { *_v } diff --git a/vlib/v/checker/tests/import_symbol_type_err.out b/vlib/v/checker/tests/import_symbol_type_err.out new file mode 100644 index 0000000000..3a9f874b6f --- /dev/null +++ b/vlib/v/checker/tests/import_symbol_type_err.out @@ -0,0 +1,11 @@ +vlib/v/checker/tests/import_symbol_type_err.v:1:17: error: module `crypto` has no public type `Coin{}` + 1 | import crypto { Coin } + | ~~~~ + 2 | fn main() { + 3 | println(Coin{}) +vlib/v/checker/tests/import_symbol_type_err.v:3:11: error: unknown struct: Coin + 1 | import crypto { Coin } + 2 | fn main() { + 3 | println(Coin{}) + | ~~~~~~ + 4 | } diff --git a/vlib/v/checker/tests/import_symbol_type_err.vv b/vlib/v/checker/tests/import_symbol_type_err.vv new file mode 100644 index 0000000000..7eccd97139 --- /dev/null +++ b/vlib/v/checker/tests/import_symbol_type_err.vv @@ -0,0 +1,4 @@ +import crypto { Coin } +fn main() { + println(Coin{}) +} diff --git a/vlib/v/checker/tests/import_symbol_unclosed.out b/vlib/v/checker/tests/import_symbol_unclosed.out new file mode 100644 index 0000000000..6bc027cd45 --- /dev/null +++ b/vlib/v/checker/tests/import_symbol_unclosed.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/import_symbol_unclosed.v:1:28: error: import syntax error, no closing `}` + 1 | import crypto.sha256 { sum ] + | ^ diff --git a/vlib/v/checker/tests/import_symbol_unclosed.vv b/vlib/v/checker/tests/import_symbol_unclosed.vv new file mode 100644 index 0000000000..799a636f27 --- /dev/null +++ b/vlib/v/checker/tests/import_symbol_unclosed.vv @@ -0,0 +1 @@ +import crypto.sha256 { sum ] diff --git a/vlib/v/checker/tests/import_syntax_err.out b/vlib/v/checker/tests/import_syntax_err.out index 6d53400d04..5dca97136a 100644 --- a/vlib/v/checker/tests/import_syntax_err.out +++ b/vlib/v/checker/tests/import_syntax_err.out @@ -1,4 +1,4 @@ -vlib/v/checker/tests/import_syntax_err.v:1:12: error: module syntax error, please use `x.y.z` +vlib/v/checker/tests/import_syntax_err.v:1:12: error: cannot import multiple modules at a time 1 | import time, os | ^ 2 | fn main() { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index e481d0c137..d32f57901a 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -77,6 +77,10 @@ pub fn fmt(file ast.File, table &table.Table, is_debug bool) string { pub fn (mut f Fmt) process_file_imports(file &ast.File) { for imp in file.imports { f.mod2alias[imp.mod.all_after_last('.')] = imp.alias + for sym in imp.syms { + f.mod2alias['$imp.mod\.$sym.name'] = sym.name + f.mod2alias[sym.name] = sym.name + } } } @@ -228,7 +232,10 @@ pub fn (mut f Fmt) imports(imports []ast.Import) { pub fn (f Fmt) imp_stmt_str(imp ast.Import) string { is_diff := imp.alias != imp.mod && !imp.mod.ends_with('.' + imp.alias) - imp_alias_suffix := if is_diff { ' as $imp.alias' } else { '' } + mut imp_alias_suffix := if is_diff { ' as $imp.alias' } else { '' } + if imp.syms.len > 0 { + imp_alias_suffix += ' { ' + imp.syms.map(it.name).join(', ') + ' }' + } return '$imp.mod$imp_alias_suffix' } @@ -1338,8 +1345,11 @@ pub fn (mut f Fmt) call_expr(node ast.CallExpr) { f.or_expr(node.or_block) } else { f.write_language_prefix(node.language) - name := f.short_module(node.name) + mut name := f.short_module(node.name) f.mark_module_as_used(name) + if node.name in f.mod2alias { + name = f.mod2alias[node.name] + } f.write('$name') if node.generic_type != 0 && node.generic_type != table.void_type { f.write('<') diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 2b7954abdc..033b3f6229 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -10,7 +10,7 @@ import v.util pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExpr { first_pos := p.tok.position() - fn_name := if language == .c { + mut fn_name := if language == .c { 'C.$p.check_name()' } else if language == .js { 'JS.$p.check_js_name()' @@ -81,10 +81,17 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp p.next() or_kind = .propagate } + mut fn_mod := p.mod + if registered := p.table.find_fn(fn_name) { + if registered.is_placeholder { + fn_mod = registered.mod + fn_name = registered.name + } + } node := ast.CallExpr{ name: fn_name args: args - mod: p.mod + mod: fn_mod pos: pos language: language or_block: ast.OrExpr{ diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 86a97a76b4..52d894b595 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1331,7 +1331,7 @@ fn (mut p Parser) import_stmt() ast.Import { } mut mod_name := p.check_name() if import_pos.line_nr != pos.line_nr { - p.error_with_pos('`import` and `module` must be at same line', pos) + p.error_with_pos('`import` statements must be a single line', pos) } mut mod_alias := mod_name for p.tok.kind == .dot { @@ -1351,25 +1351,90 @@ fn (mut p Parser) import_stmt() ast.Import { p.next() mod_alias = p.check_name() } + node := ast.Import{ + pos: pos, + mod: mod_name, + alias: mod_alias, + } + if p.tok.kind == .lcbr { // import module { fn1, Type2 } syntax + p.import_syms(node) + p.register_used_import(mod_name) // no `unused import` msg for parent + } pos_t := p.tok.position() if import_pos.line_nr == pos_t.line_nr { - if p.tok.kind != .name { - p.error_with_pos('module syntax error, please use `x.y.z`', pos_t) - } else { + if p.tok.kind != .lcbr { p.error_with_pos('cannot import multiple modules at a time', pos_t) } } p.imports[mod_alias] = mod_name p.table.imports << mod_name - node := ast.Import{ - mod: mod_name - alias: mod_alias - pos: pos - } p.ast_imports << node return node } +// import_syms parses the inner part of `import module { submod1, submod2 }` +fn (mut p Parser) import_syms(mut parent ast.Import) { + p.next() + pos_t := p.tok.position() + if p.tok.kind == .rcbr { // closed too early + p.error_with_pos('empty `$parent.mod` import set, remove `{}`', pos_t) + } + if p.tok.kind != .name { // not a valid inner name + p.error_with_pos('import syntax error, please specify a valid fn or type name', pos_t) + } + for p.tok.kind == .name { + pos := p.tok.position() + alias := p.check_name() + name := '$parent.mod\.$alias' + if alias[0].is_capital() { + idx := p.table.add_placeholder_type(name) + typ := table.new_type(idx) + p.table.register_type_symbol({ + kind: .alias + name: p.prepend_mod(alias) + parent_idx: idx + mod: p.mod + info: table.Alias{ + parent_type: typ + language: table.Language.v + } + is_public: false + }) + // so we can work with the fully declared type in fmt+checker + parent.syms << ast.ImportSymbol{ + pos: pos + name: alias + kind: .type_ + } + } else { + if !p.table.known_fn(name) { + p.table.fns[alias] = table.Fn{ + is_placeholder: true + mod: parent.mod + name: name + } + } + // so we can work with this in fmt+checker + parent.syms << ast.ImportSymbol{ + pos: pos + name: alias + kind: .fn_ + } + } + if p.tok.kind == .comma { // go again if more than one + p.next() + continue + } + if p.tok.kind == .rcbr { // finish if closing `}` is seen + break + } + } + if p.tok.kind != .rcbr { + p.error_with_pos('import syntax error, no closing `}`', p.tok.position()) + } + p.next() +} + fn (mut p Parser) const_decl() ast.ConstDecl { p.top_level_statement_start() start_pos := p.tok.position() diff --git a/vlib/v/table/table.v b/vlib/v/table/table.v index 3f3dbe1eb7..ca76155cc2 100644 --- a/vlib/v/table/table.v +++ b/vlib/v/table/table.v @@ -22,19 +22,20 @@ pub mut: pub struct Fn { pub: - args []Arg - return_type Type - is_variadic bool - language Language - is_generic bool - is_pub bool - is_deprecated bool - is_unsafe bool - mod string - ctdefine string // compile time define. myflag, when [if myflag] tag - attrs []string + args []Arg + return_type Type + is_variadic bool + language Language + is_generic bool + is_pub bool + is_deprecated bool + is_unsafe bool + is_placeholder bool + mod string + ctdefine string // compile time define. myflag, when [if myflag] tag + attrs []string pub mut: - name string + name string } pub struct Arg { diff --git a/vlib/v/tests/module_test.v b/vlib/v/tests/module_test.v index 12c597db10..7e732081fc 100644 --- a/vlib/v/tests/module_test.v +++ b/vlib/v/tests/module_test.v @@ -1,8 +1,10 @@ import os -import time as t import crypto.sha256 -import math +import term { white } +import crypto.md5 { sum } import log as l +import time as t { now, utc, Time } +import math import crypto.sha512 struct TestAliasInStruct { @@ -11,8 +13,15 @@ struct TestAliasInStruct { fn test_import() { info := l.Level.info - assert os.o_rdonly == os.o_rdonly && t.month_days[0] == t.month_days[0] && sha256.size == - sha256.size && math.pi == math.pi && info == .info && sha512.size == sha512.size + assert info == .info + assert term.white('INFO') == white('INFO') + assert os.o_rdonly == os.o_rdonly + assert t.month_days[0] == t.month_days[0] + assert sha256.size == sha256.size + assert math.pi == math.pi + assert sha512.size == sha512.size + assert md5.sum('module'.bytes()).hex() == sum('module'.bytes()).hex() + assert t.utc().unix_time() == utc().unix_time() } fn test_alias_in_struct_field() {