all: import individual symbols feature (#5872)

pull/5883/head
Ryan Willis 2020-07-18 12:34:38 -07:00 committed by GitHub
parent b3011b4f19
commit 1114fd28d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 256 additions and 37 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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
| ~~~~

View File

@ -0,0 +1,3 @@
vlib/v/checker/tests/import_symbol_empty.v:1:12: error: empty `os` import set, remove `{}`
1 | import os {}
| ^

View File

@ -0,0 +1 @@
import os {}

View File

@ -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 | }

View File

@ -0,0 +1,4 @@
import crypto { userper }
fn main() {
usurper()
}

View File

@ -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 }
| ^

View File

@ -0,0 +1 @@
import crypto { *_v }

View File

@ -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 | }

View File

@ -0,0 +1,4 @@
import crypto { Coin }
fn main() {
println(Coin{})
}

View File

@ -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 ]
| ^

View File

@ -0,0 +1 @@
import crypto.sha256 { sum ]

View File

@ -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() {

View File

@ -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('<')

View File

@ -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{

View File

@ -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()

View File

@ -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 {

View File

@ -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() {