mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
SelectorExpr; receivers; struct field check; if expression
This commit is contained in:
parent
3c65af8b9a
commit
492dfebd15
2
v2.v
2
v2.v
@ -22,7 +22,7 @@ fn main() {
|
||||
println('V2 $path')
|
||||
table := table.new_table()
|
||||
program := parser.parse_file(path, table)
|
||||
res := gen.cgen([program])
|
||||
res := gen.cgen([program], table)
|
||||
mut out := os.create('out.c')?
|
||||
out.writeln(cdefs)
|
||||
out.writeln(res)
|
||||
|
@ -15,7 +15,7 @@ pub:
|
||||
element_size int
|
||||
}
|
||||
|
||||
// Private function, used by V (`nums := []int`)
|
||||
// Internal function, used by V (`nums := []int`)
|
||||
fn new_array(mylen int, cap int, elm_size int) array {
|
||||
cap_ := if cap == 0 { 1 } else { cap }
|
||||
arr := array{
|
||||
@ -28,7 +28,7 @@ fn new_array(mylen int, cap int, elm_size int) array {
|
||||
}
|
||||
|
||||
// TODO
|
||||
pub fn make(len, cap, elm_size int) array {
|
||||
pub fn make(len int, cap int, elm_size int) array {
|
||||
return new_array(len, cap, elm_size)
|
||||
}
|
||||
|
||||
@ -42,7 +42,9 @@ fn new_array_from_c_array(len, cap, elm_size int, c_array voidptr) array {
|
||||
data: calloc(cap_ * elm_size)
|
||||
}
|
||||
// TODO Write all memory functions (like memcpy) in V
|
||||
C.memcpy(arr.data, c_array, len * elm_size)
|
||||
C.memcpy(
|
||||
arr.data,
|
||||
c_array, len * elm_size)
|
||||
return arr
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
v.table
|
||||
v.parser
|
||||
v.gen
|
||||
time
|
||||
)
|
||||
|
||||
pub const (
|
||||
@ -397,7 +398,7 @@ pub fn (v mut V) compile2() {
|
||||
}
|
||||
table := table.new_table()
|
||||
files := parser.parse_files(v.files, table)
|
||||
c := gen.cgen(files)
|
||||
c := gen.cgen(files, table)
|
||||
println('out: $v.out_name_c')
|
||||
os.write_file(v.out_name_c, c)
|
||||
/*
|
||||
@ -423,9 +424,12 @@ pub fn (v mut V) compile_x64() {
|
||||
//v.files << v.v_files_from_dir(filepath.join(v.pref.vlib_path,'builtin','bare'))
|
||||
v.files << v.dir
|
||||
|
||||
table := &table.Table{}
|
||||
table := &table.new_table()
|
||||
ticks := time.ticks()
|
||||
files := parser.parse_files(v.files, table)
|
||||
println('PARSE: ${time.ticks() - ticks}ms')
|
||||
x64.gen(files, v.out_name)
|
||||
println('x64 GEN: ${time.ticks() - ticks}ms')
|
||||
/*
|
||||
for f in v.files {
|
||||
v.parse(f, .decl)
|
||||
|
@ -8,15 +8,16 @@ import (
|
||||
v.types
|
||||
)
|
||||
|
||||
pub type Expr = BinaryExpr | UnaryExpr | IfExpr | StringLiteral | IntegerLiteral |
|
||||
FloatLiteral | Ident | CallExpr | BoolLiteral | StructInit | ArrayInit
|
||||
pub type Expr = BinaryExpr | UnaryExpr | IfExpr | StringLiteral | IntegerLiteral |
|
||||
FloatLiteral | Ident | CallExpr | BoolLiteral | StructInit | ArrayInit | SelectorExpr
|
||||
|
||||
pub type Stmt = VarDecl | FnDecl | Return | Module | Import | ExprStmt | AssignStmt |
|
||||
pub type Stmt = VarDecl | FnDecl | Return | Module | Import | ExprStmt | AssignStmt |
|
||||
ForStmt | StructDecl
|
||||
// Stand-alone expression in a statement list.
|
||||
pub struct ExprStmt {
|
||||
pub:
|
||||
expr Expr
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
pub struct IntegerLiteral {
|
||||
@ -40,6 +41,13 @@ pub:
|
||||
val bool
|
||||
}
|
||||
|
||||
// `foo.bar`
|
||||
pub struct SelectorExpr {
|
||||
pub:
|
||||
expr Expr
|
||||
field string
|
||||
}
|
||||
|
||||
// module declaration
|
||||
pub struct Module {
|
||||
pub:
|
||||
@ -84,10 +92,12 @@ pub:
|
||||
|
||||
pub struct FnDecl {
|
||||
pub:
|
||||
name string
|
||||
stmts []Stmt
|
||||
typ types.Type
|
||||
args []Arg
|
||||
name string
|
||||
stmts []Stmt
|
||||
typ types.Type
|
||||
args []Arg
|
||||
is_pub bool
|
||||
receiver Field
|
||||
}
|
||||
|
||||
pub struct CallExpr {
|
||||
@ -164,6 +174,8 @@ pub:
|
||||
cond Expr
|
||||
stmts []Stmt
|
||||
else_stmts []Stmt
|
||||
typ types.Type
|
||||
left Expr // `a` in `a := if ...`
|
||||
}
|
||||
|
||||
pub struct ForStmt {
|
||||
|
@ -3,18 +3,22 @@ module gen
|
||||
import (
|
||||
strings
|
||||
v.ast
|
||||
v.table
|
||||
v.types
|
||||
term
|
||||
)
|
||||
|
||||
struct Gen {
|
||||
out strings.Builder
|
||||
definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file)
|
||||
table &table.Table
|
||||
}
|
||||
|
||||
pub fn cgen(files []ast.File) string {
|
||||
pub fn cgen(files []ast.File, table &table.Table) string {
|
||||
mut g := Gen{
|
||||
out: strings.new_builder(100)
|
||||
definitions: strings.new_builder(100)
|
||||
table: table
|
||||
}
|
||||
for file in files {
|
||||
for stmt in file.stmts {
|
||||
@ -139,6 +143,9 @@ fn (g mut Gen) expr(node ast.Expr) {
|
||||
}
|
||||
ast.BinaryExpr {
|
||||
g.expr(it.left)
|
||||
if it.op == .dot {
|
||||
println('!! dot')
|
||||
}
|
||||
g.write(' $it.op.str() ')
|
||||
g.expr(it.right)
|
||||
// if typ.name != typ2.name {
|
||||
@ -184,11 +191,28 @@ fn (g mut Gen) expr(node ast.Expr) {
|
||||
g.write('false')
|
||||
}
|
||||
}
|
||||
ast.SelectorExpr {
|
||||
g.expr(it.expr)
|
||||
g.write('.')
|
||||
g.write(it.field)
|
||||
}
|
||||
ast.IfExpr {
|
||||
// If expression? Assign the value to a temp var.
|
||||
// Previously ?: was used, but it's too unreliable.
|
||||
mut tmp := ''
|
||||
if it.typ.idx != types.void_type.idx {
|
||||
tmp = g.table.new_tmp_var()
|
||||
// g.writeln('$it.typ.name $tmp;')
|
||||
}
|
||||
g.write('if (')
|
||||
g.expr(it.cond)
|
||||
g.writeln(') {')
|
||||
for stmt in it.stmts {
|
||||
for i, stmt in it.stmts {
|
||||
// Assign ret value
|
||||
if i == it.stmts.len - 1 && it.typ.idx != types.void_type.idx {
|
||||
// g.writeln('$tmp =')
|
||||
println(1)
|
||||
}
|
||||
g.stmt(stmt)
|
||||
}
|
||||
g.writeln('}')
|
||||
|
@ -23,13 +23,14 @@ fn test_c_files() {
|
||||
}
|
||||
table := &table.new_table()
|
||||
program := parser.parse_file(path, table)
|
||||
res := gen.cgen([program])
|
||||
res := gen.cgen([program], table)
|
||||
if compare_texts(res, ctext) {
|
||||
eprintln('${i}... ' + term.green('OK'))
|
||||
}
|
||||
else {
|
||||
eprintln('${i}... ' + term.red('FAIL'))
|
||||
eprintln('expected:\n$ctext\ngot:\n$res')
|
||||
eprintln(path)
|
||||
eprintln('got:\n$res')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,11 @@
|
||||
void foo(int a);
|
||||
int get_int(string a);
|
||||
int get_int2();
|
||||
void myuser();
|
||||
|
||||
typedef struct {
|
||||
int age;
|
||||
} User;
|
||||
|
||||
int main() {
|
||||
int a = 10;
|
||||
@ -10,5 +17,22 @@ return 0;
|
||||
}
|
||||
|
||||
void foo(int a) {
|
||||
|
||||
void n = get_int2();
|
||||
}
|
||||
|
||||
int get_int(string a) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
int get_int2() {
|
||||
string a = tos3("hello");
|
||||
return get_int(a);
|
||||
}
|
||||
|
||||
void myuser() {
|
||||
User user = (User){
|
||||
.age = 10,
|
||||
};
|
||||
User age = user.age;
|
||||
bool b = age > 0;
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
struct User {
|
||||
age int
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := 10
|
||||
a++
|
||||
@ -7,5 +11,21 @@ fn main() {
|
||||
}
|
||||
|
||||
fn foo(a int) {
|
||||
|
||||
n := get_int2()
|
||||
}
|
||||
|
||||
fn get_int(a string) int {
|
||||
return 10
|
||||
}
|
||||
|
||||
fn get_int2() int {
|
||||
a := 'hello'
|
||||
return get_int(a)
|
||||
}
|
||||
|
||||
fn myuser() {
|
||||
user := User{age:10}
|
||||
age := user.age
|
||||
b := age > 0
|
||||
//b2 := user.age > 0
|
||||
}
|
||||
|
5
vlib/v/gen/tests/if_expr.vv
Normal file
5
vlib/v/gen/tests/if_expr.vv
Normal file
@ -0,0 +1,5 @@
|
||||
fn foo() {
|
||||
a := if true { 1 } else { 2 }
|
||||
|
||||
|
||||
}
|
@ -20,7 +20,9 @@ pub fn (p mut Parser) call_expr() (ast.CallExpr,types.Type) {
|
||||
p.check(.lpar)
|
||||
mut is_unknown := false
|
||||
mut args := []ast.Expr
|
||||
mut return_type := types.void_type
|
||||
if f := p.table.find_fn(fn_name) {
|
||||
return_type = f.return_type
|
||||
for i, arg in f.args {
|
||||
e,typ := p.expr(0)
|
||||
if !types.check(arg.typ, typ) {
|
||||
@ -51,16 +53,38 @@ pub fn (p mut Parser) call_expr() (ast.CallExpr,types.Type) {
|
||||
args: args
|
||||
is_unknown: is_unknown
|
||||
tok: tok
|
||||
// typ: return_type
|
||||
|
||||
}
|
||||
if is_unknown {
|
||||
p.table.unknown_calls << node
|
||||
}
|
||||
return node,types.int_type
|
||||
return node,return_type
|
||||
}
|
||||
|
||||
fn (p mut Parser) fn_decl() ast.FnDecl {
|
||||
is_pub := p.tok.kind == .key_pub
|
||||
if is_pub {
|
||||
p.next()
|
||||
}
|
||||
p.table.clear_vars()
|
||||
p.check(.key_fn)
|
||||
// Receiver?
|
||||
mut rec_name := ''
|
||||
mut rec_type := types.void_type
|
||||
if p.tok.kind == .lpar {
|
||||
p.next()
|
||||
rec_name = p.check_name()
|
||||
if p.tok.kind == .key_mut {
|
||||
p.next()
|
||||
}
|
||||
rec_type = p.parse_type()
|
||||
p.table.register_var(table.Var{
|
||||
name: rec_name
|
||||
typ: rec_type
|
||||
})
|
||||
p.check(.rpar)
|
||||
}
|
||||
name := p.check_name()
|
||||
// println('fn decl $name')
|
||||
p.check(.lpar)
|
||||
@ -68,17 +92,24 @@ fn (p mut Parser) fn_decl() ast.FnDecl {
|
||||
mut args := []table.Var
|
||||
mut ast_args := []ast.Arg
|
||||
for p.tok.kind != .rpar {
|
||||
arg_name := p.check_name()
|
||||
typ := p.parse_type()
|
||||
arg := table.Var{
|
||||
name: arg_name
|
||||
typ: typ
|
||||
mut arg_names := [p.check_name()]
|
||||
// `a, b, c int`
|
||||
for p.tok.kind == .comma {
|
||||
p.check(.comma)
|
||||
arg_names << p.check_name()
|
||||
}
|
||||
args << arg
|
||||
p.table.register_var(arg)
|
||||
ast_args << ast.Arg{
|
||||
typ: typ
|
||||
name: arg_name
|
||||
typ := p.parse_type()
|
||||
for arg_name in arg_names {
|
||||
arg := table.Var{
|
||||
name: arg_name
|
||||
typ: typ
|
||||
}
|
||||
args << arg
|
||||
p.table.register_var(arg)
|
||||
ast_args << ast.Arg{
|
||||
typ: typ
|
||||
name: arg_name
|
||||
}
|
||||
}
|
||||
if p.tok.kind != .rpar {
|
||||
p.check(.comma)
|
||||
@ -94,6 +125,7 @@ fn (p mut Parser) fn_decl() ast.FnDecl {
|
||||
p.table.register_fn(table.Fn{
|
||||
name: name
|
||||
args: args
|
||||
return_type: typ
|
||||
})
|
||||
stmts := p.parse_block()
|
||||
return ast.FnDecl{
|
||||
@ -101,16 +133,23 @@ fn (p mut Parser) fn_decl() ast.FnDecl {
|
||||
stmts: stmts
|
||||
typ: typ
|
||||
args: ast_args
|
||||
is_pub: is_pub
|
||||
receiver: ast.Field{
|
||||
name: rec_name
|
||||
typ: rec_type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (p &Parser) check_fn_calls() {
|
||||
println('check fn calls')
|
||||
println('check fn calls2')
|
||||
for call in p.table.unknown_calls {
|
||||
f := p.table.find_fn(call.name) or {
|
||||
p.error_at_line('unknown function `$call.name`', call.tok.line_nr)
|
||||
return
|
||||
}
|
||||
println(f.name)
|
||||
// println(f.return_type.name)
|
||||
// println('IN AST typ=' + call.typ.name)
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ mut:
|
||||
// vars []string
|
||||
table &table.Table
|
||||
return_type types.Type
|
||||
is_c bool
|
||||
}
|
||||
|
||||
pub fn parse_stmt(text string, table &table.Table) ast.Stmt {
|
||||
@ -74,9 +75,11 @@ pub fn parse_files(paths []string, table &table.Table) []ast.File {
|
||||
|
||||
// former get_type()
|
||||
pub fn (p mut Parser) parse_type() types.Type {
|
||||
typ := p.table.types[p.tok.lit]
|
||||
if isnil(typ.name.str) || typ.name == '' {
|
||||
typ := p.table.find_type(p.tok.lit) or {
|
||||
// typ := p.table.types[p.tok.lit]
|
||||
// if isnil(typ.name.str) || typ.name == '' {
|
||||
p.error('undefined type `$p.tok.lit`')
|
||||
exit(0)
|
||||
}
|
||||
p.next()
|
||||
return typ
|
||||
@ -181,9 +184,10 @@ pub fn (p mut Parser) stmt() ast.Stmt {
|
||||
return p.for_statement()
|
||||
}
|
||||
else {
|
||||
expr,_ := p.expr(0)
|
||||
expr,typ := p.expr(0)
|
||||
return ast.ExprStmt{
|
||||
expr: expr
|
||||
typ: typ
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -242,7 +246,12 @@ pub fn (p mut Parser) expr(rbp int) (ast.Expr,types.Type) {
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
if p.tok.lit == 'C' {
|
||||
p.is_c = true
|
||||
println('is c')
|
||||
p.next()
|
||||
p.check(.dot)
|
||||
}
|
||||
// fn call
|
||||
if p.peek_tok.kind == .lpar {
|
||||
x,typ2 := p.call_expr() // TODO `node,typ :=` should work
|
||||
@ -330,6 +339,25 @@ pub fn (p mut Parser) expr(rbp int) (ast.Expr,types.Type) {
|
||||
// left binding power
|
||||
for rbp < p.tok.precedence() {
|
||||
prev_tok := p.tok
|
||||
if prev_tok.kind == .dot {
|
||||
p.warn('dot prev_tok = $prev_tok.str() typ=$typ.name')
|
||||
p.next()
|
||||
field := p.check_name()
|
||||
mut ok := false
|
||||
for f in typ.fields {
|
||||
if f.name == field {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
p.error('unknown field `${typ.name}.$field`')
|
||||
}
|
||||
node = ast.SelectorExpr{
|
||||
expr: node
|
||||
field: field
|
||||
}
|
||||
return node,typ
|
||||
}
|
||||
p.next()
|
||||
mut t2 := types.Type{}
|
||||
// left denotation (infix / postfix)
|
||||
@ -410,23 +438,36 @@ fn (p mut Parser) for_statement() ast.ForStmt {
|
||||
fn (p mut Parser) if_expr() (ast.Expr,types.Type) {
|
||||
mut node := ast.Expr{}
|
||||
p.check(.key_if)
|
||||
cond,typ := p.expr(0)
|
||||
if !types.check(types.bool_type, typ) {
|
||||
cond,cond_type := p.expr(0)
|
||||
if !types.check(types.bool_type, cond_type) {
|
||||
p.error('non-bool used as if condition')
|
||||
}
|
||||
stmts := p.parse_block()
|
||||
mut else_stmts := []ast.Stmt
|
||||
if p.tok.kind == .key_else {
|
||||
println('GOT ELSE')
|
||||
// println('GOT ELSE')
|
||||
p.check(.key_else)
|
||||
else_stmts = p.parse_block()
|
||||
}
|
||||
mut typ := types.void_type
|
||||
// mut left := ast.Expr{}
|
||||
match stmts[stmts.len - 1] {
|
||||
ast.ExprStmt {
|
||||
typ = it.typ
|
||||
// return node,it.typ
|
||||
// left =
|
||||
}
|
||||
else {}
|
||||
}
|
||||
node = ast.IfExpr{
|
||||
cond: cond
|
||||
stmts: stmts
|
||||
else_stmts: else_stmts
|
||||
typ: typ
|
||||
// left: left
|
||||
|
||||
}
|
||||
return node,types.void_type
|
||||
return node,typ
|
||||
}
|
||||
|
||||
fn (p mut Parser) parse_string_literal() (ast.Expr,types.Type) {
|
||||
@ -510,7 +551,8 @@ fn (p mut Parser) struct_decl() ast.StructDecl {
|
||||
p.check(.key_struct)
|
||||
name := p.check_name()
|
||||
p.check(.lcbr)
|
||||
mut fields := []ast.Field
|
||||
mut ast_fields := []ast.Field
|
||||
mut fields := []types.Field
|
||||
for p.tok.kind != .rcbr {
|
||||
if p.tok.kind == .key_pub {
|
||||
p.check(.key_pub)
|
||||
@ -518,19 +560,24 @@ fn (p mut Parser) struct_decl() ast.StructDecl {
|
||||
}
|
||||
field_name := p.check_name()
|
||||
typ := p.parse_type()
|
||||
fields << ast.Field{
|
||||
ast_fields << ast.Field{
|
||||
name: field_name
|
||||
typ: typ
|
||||
}
|
||||
fields << types.Field{
|
||||
name: field_name
|
||||
type_idx: typ.idx
|
||||
}
|
||||
}
|
||||
p.check(.rcbr)
|
||||
p.table.register_type(types.Type{
|
||||
name: name
|
||||
fields: fields
|
||||
})
|
||||
return ast.StructDecl{
|
||||
name: name
|
||||
is_pub: is_pub
|
||||
fields: fields
|
||||
fields: ast_fields
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ x := 10
|
||||
'
|
||||
table := &table.Table{}
|
||||
prog := parse_file(s, table)
|
||||
res := gen.cgen([prog])
|
||||
res := gen.cgen([prog], table)
|
||||
println(res)
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ fn test_parse_expr() {
|
||||
program := ast.File{
|
||||
stmts: e
|
||||
}
|
||||
res := gen.cgen([program])
|
||||
res := gen.cgen([program], table)
|
||||
println('========')
|
||||
println(res)
|
||||
println('========')
|
||||
|
@ -6,13 +6,15 @@ import (
|
||||
)
|
||||
|
||||
pub struct Table {
|
||||
// struct_fields map[string][]string
|
||||
pub mut:
|
||||
types map[string]types.Type
|
||||
local_vars []Var
|
||||
// fns Hashmap
|
||||
fns map[string]Fn
|
||||
types map[string]types.Type
|
||||
//
|
||||
unknown_calls []ast.CallExpr
|
||||
tmp_cnt int
|
||||
}
|
||||
|
||||
pub struct Var {
|
||||
@ -24,8 +26,9 @@ pub:
|
||||
|
||||
pub struct Fn {
|
||||
pub:
|
||||
name string
|
||||
args []Var
|
||||
name string
|
||||
args []Var
|
||||
return_type types.Type
|
||||
}
|
||||
|
||||
pub fn new_table() &Table {
|
||||
@ -109,3 +112,16 @@ pub fn (t mut Table) register_fn(new_fn Fn) {
|
||||
pub fn (t mut Table) register_type(typ types.Type) {
|
||||
t.types[typ.name] = typ
|
||||
}
|
||||
|
||||
pub fn (t &Table) find_type(name string) ?types.Type {
|
||||
typ := t.types[name]
|
||||
if isnil(typ.name.str) || typ.name == '' {
|
||||
return none
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
pub fn (t mut Table) new_tmp_var() string {
|
||||
t.tmp_cnt++
|
||||
return 'tmp$t.tmp_cnt'
|
||||
}
|
||||
|
@ -311,6 +311,9 @@ pub const (
|
||||
// Precedence returns a tokens precedence if defined, otherwise lowest_prec
|
||||
pub fn (tok Token) precedence() int {
|
||||
match tok.kind {
|
||||
.dot {
|
||||
return 8
|
||||
}
|
||||
// `++` | `--`
|
||||
.inc, .dec {
|
||||
return 7
|
||||
|
@ -3,25 +3,51 @@
|
||||
// that can be found in the LICENSE file.
|
||||
module types
|
||||
|
||||
pub enum Kind {
|
||||
struct_
|
||||
builtin
|
||||
enum_
|
||||
}
|
||||
|
||||
pub struct Type {
|
||||
pub:
|
||||
name string
|
||||
idx int
|
||||
name string
|
||||
idx int
|
||||
// kind Kind
|
||||
fields []Field
|
||||
}
|
||||
|
||||
pub struct Field {
|
||||
pub:
|
||||
name string
|
||||
type_idx int
|
||||
}
|
||||
|
||||
pub const (
|
||||
void_type = Type{
|
||||
'void',0}
|
||||
name: 'void'
|
||||
idx: 0
|
||||
}
|
||||
int_type = Type{
|
||||
'int',1}
|
||||
name: 'int'
|
||||
idx: 1
|
||||
}
|
||||
string_type = Type{
|
||||
'string',2}
|
||||
name: 'string'
|
||||
idx: 2
|
||||
}
|
||||
f64_type = Type{
|
||||
'f64',3}
|
||||
name: 'f64'
|
||||
idx: 3
|
||||
}
|
||||
bool_type = Type{
|
||||
'bool',4}
|
||||
name: 'bool'
|
||||
idx: 4
|
||||
}
|
||||
voidptr_type = Type{
|
||||
'voidptr',5}
|
||||
name: 'voidptr'
|
||||
idx: 5
|
||||
}
|
||||
)
|
||||
|
||||
pub fn check(got, expected &Type) bool {
|
||||
|
Loading…
Reference in New Issue
Block a user