all: initial implementation of compile-time types (#13549)

pull/13559/head
playX 2022-02-21 18:42:54 +03:00 committed by GitHub
parent 83cdd8bedd
commit b842e89acc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 290 additions and 22 deletions

View File

@ -24,6 +24,7 @@ pub type Expr = AnonFn
| Comment | Comment
| ComptimeCall | ComptimeCall
| ComptimeSelector | ComptimeSelector
| ComptimeType
| ConcatExpr | ConcatExpr
| DumpExpr | DumpExpr
| EmptyExpr | EmptyExpr
@ -114,6 +115,36 @@ pub:
pos token.Pos pos token.Pos
} }
pub enum ComptimeTypeKind {
map_
int
float
struct_
iface
array
sum_type
enum_
}
pub struct ComptimeType {
pub:
kind ComptimeTypeKind
pos token.Pos
}
pub fn (cty ComptimeType) str() string {
return match cty.kind {
.map_ { '\$Map' }
.int { '\$Int' }
.float { '\$Float' }
.struct_ { '\$Struct' }
.iface { '\$Interface' }
.array { '\$Array' }
.sum_type { '\$Sumtype' }
.enum_ { '\$Enum' }
}
}
pub struct EmptyExpr { pub struct EmptyExpr {
x int x int
} }
@ -1683,7 +1714,7 @@ pub fn (expr Expr) pos() token.Pos {
EnumVal, DumpExpr, FloatLiteral, GoExpr, Ident, IfExpr, IntegerLiteral, IsRefType, Likely, EnumVal, DumpExpr, FloatLiteral, GoExpr, Ident, IfExpr, IntegerLiteral, IsRefType, Likely,
LockExpr, MapInit, MatchExpr, None, OffsetOf, OrExpr, ParExpr, PostfixExpr, PrefixExpr, LockExpr, MapInit, MatchExpr, None, OffsetOf, OrExpr, ParExpr, PostfixExpr, PrefixExpr,
RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral,
StructInit, TypeNode, TypeOf, UnsafeExpr { StructInit, TypeNode, TypeOf, UnsafeExpr, ComptimeType {
return expr.pos return expr.pos
} }
IndexExpr { IndexExpr {

View File

@ -261,6 +261,9 @@ pub fn (x Expr) str() string {
AnonFn { AnonFn {
return 'anon_fn' return 'anon_fn'
} }
ComptimeType {
return x.str()
}
DumpExpr { DumpExpr {
return 'dump($x.expr.str())' return 'dump($x.expr.str())'
} }

View File

@ -2044,3 +2044,49 @@ pub fn (mut t Table) generic_insts_to_concrete() {
} }
} }
} }
pub fn (t &Table) is_comptime_type(x Type, y ComptimeType) bool {
x_kind := t.type_kind(x)
match y.kind {
.map_ {
return x_kind == .map
}
.int {
return x_kind in [
.i8,
.i16,
.int,
.i64,
.byte,
.u8,
.u16,
.u32,
.u64,
.usize,
.int_literal,
]
}
.float {
return x_kind in [
.f32,
.f64,
.float_literal,
]
}
.struct_ {
return x_kind == .struct_
}
.iface {
return x_kind == .interface_
}
.array {
return x_kind in [.array, .array_fixed]
}
.sum_type {
return x_kind == .sum_type
}
.enum_ {
return x_kind == .enum_
}
}
}

View File

@ -2384,6 +2384,9 @@ pub fn (mut c Checker) expr(node ast.Expr) ast.Type {
} }
match mut node { match mut node {
ast.NodeError {} ast.NodeError {}
ast.ComptimeType {
c.error('incorrect use of compile-time type', node.pos)
}
ast.EmptyExpr { ast.EmptyExpr {
c.error('checker.expr(): unhandled EmptyExpr', token.Pos{}) c.error('checker.expr(): unhandled EmptyExpr', token.Pos{})
} }

View File

@ -468,6 +468,10 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) bool {
// c.error('`$sym.name` is not an interface', cond.right.pos()) // c.error('`$sym.name` is not an interface', cond.right.pos())
} }
return false return false
} else if cond.left is ast.TypeNode && cond.right is ast.ComptimeType {
left := cond.left as ast.TypeNode
checked_type := c.unwrap_generic(left.typ)
return c.table.is_comptime_type(checked_type, cond.right)
} else if cond.left in [ast.SelectorExpr, ast.TypeNode] { } else if cond.left in [ast.SelectorExpr, ast.TypeNode] {
// `$if method.@type is string` // `$if method.@type is string`
c.expr(cond.left) c.expr(cond.left)

View File

@ -52,32 +52,39 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
mut comptime_field_name := '' mut comptime_field_name := ''
if mut branch.cond is ast.InfixExpr { if mut branch.cond is ast.InfixExpr {
if branch.cond.op == .key_is { if branch.cond.op == .key_is {
if branch.cond.right !is ast.TypeNode { if branch.cond.right !is ast.TypeNode && branch.cond.right !is ast.ComptimeType {
c.error('invalid `\$if` condition: expected a type', branch.cond.right.pos()) c.error('invalid `\$if` condition: expected a type', branch.cond.right.pos())
return 0 return 0
} }
got_type := c.unwrap_generic((branch.cond.right as ast.TypeNode).typ)
sym := c.table.sym(got_type)
if sym.kind == .placeholder || got_type.has_flag(.generic) {
c.error('unknown type `$sym.name`', branch.cond.right.pos())
}
left := branch.cond.left left := branch.cond.left
if left is ast.SelectorExpr { if branch.cond.right is ast.ComptimeType && left is ast.TypeNode {
comptime_field_name = left.expr.str()
c.comptime_fields_type[comptime_field_name] = got_type
is_comptime_type_is_expr = true is_comptime_type_is_expr = true
} else if branch.cond.right is ast.TypeNode && left is ast.TypeNode
&& sym.kind == .interface_ {
is_comptime_type_is_expr = true
// is interface
checked_type := c.unwrap_generic(left.typ) checked_type := c.unwrap_generic(left.typ)
should_skip = !c.table.does_type_implement_interface(checked_type, should_skip = !c.table.is_comptime_type(checked_type, branch.cond.right as ast.ComptimeType)
got_type) } else {
} else if left is ast.TypeNode { got_type := c.unwrap_generic((branch.cond.right as ast.TypeNode).typ)
is_comptime_type_is_expr = true sym := c.table.sym(got_type)
left_type := c.unwrap_generic(left.typ) if sym.kind == .placeholder || got_type.has_flag(.generic) {
if left_type != got_type { c.error('unknown type `$sym.name`', branch.cond.right.pos())
should_skip = true }
if left is ast.SelectorExpr {
comptime_field_name = left.expr.str()
c.comptime_fields_type[comptime_field_name] = got_type
is_comptime_type_is_expr = true
} else if branch.cond.right is ast.TypeNode && left is ast.TypeNode
&& sym.kind == .interface_ {
is_comptime_type_is_expr = true
// is interface
checked_type := c.unwrap_generic(left.typ)
should_skip = !c.table.does_type_implement_interface(checked_type,
got_type)
} else if left is ast.TypeNode {
is_comptime_type_is_expr = true
left_type := c.unwrap_generic(left.typ)
if left_type != got_type {
should_skip = true
}
} }
} }
} }

View File

@ -660,6 +660,18 @@ pub fn (mut f Fmt) expr(node ast.Expr) {
ast.UnsafeExpr { ast.UnsafeExpr {
f.unsafe_expr(node) f.unsafe_expr(node)
} }
ast.ComptimeType {
match node.kind {
.array { f.write('\$Array') }
.struct_ { f.write('\$Struct') }
.iface { f.write('\$Interface') }
.map_ { f.write('\$Map') }
.int { f.write('\$Int') }
.float { f.write('\$Float') }
.sum_type { f.write('\$Sumtype') }
.enum_ { f.write('\$Enum') }
}
}
} }
} }

View File

@ -2740,6 +2740,9 @@ fn (mut g Gen) expr(node ast.Expr) {
} }
// NB: please keep the type names in the match here in alphabetical order: // NB: please keep the type names in the match here in alphabetical order:
match mut node { match mut node {
ast.ComptimeType {
g.error('g.expr(): Unhandled ComptimeType', node.pos)
}
ast.EmptyExpr { ast.EmptyExpr {
g.error('g.expr(): unhandled EmptyExpr', token.Pos{}) g.error('g.expr(): unhandled EmptyExpr', token.Pos{})
} }

View File

@ -337,6 +337,25 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) bool {
.key_is, .not_is { .key_is, .not_is {
left := cond.left left := cond.left
mut name := '' mut name := ''
if left is ast.TypeNode && cond.right is ast.ComptimeType {
checked_type := g.unwrap_generic(left.typ)
is_true := g.table.is_comptime_type(checked_type, cond.right)
if cond.op == .key_is {
if is_true {
g.write('1')
} else {
g.write('0')
}
return is_true
} else {
if is_true {
g.write('0')
} else {
g.write('1')
}
return !is_true
}
}
mut exp_type := ast.Type(0) mut exp_type := ast.Type(0)
got_type := (cond.right as ast.TypeNode).typ got_type := (cond.right as ast.TypeNode).typ
// Handle `$if x is Interface {` // Handle `$if x is Interface {`

View File

@ -856,6 +856,9 @@ fn (mut g JsGen) stmt(node ast.Stmt) {
fn (mut g JsGen) expr(node ast.Expr) { fn (mut g JsGen) expr(node ast.Expr) {
// NB: please keep the type names in the match here in alphabetical order: // NB: please keep the type names in the match here in alphabetical order:
match mut node { match mut node {
ast.ComptimeType {
verror('not yet implemented')
}
ast.EmptyExpr {} ast.EmptyExpr {}
ast.AnonFn { ast.AnonFn {
g.gen_anon_fn(mut node) g.gen_anon_fn(mut node)

View File

@ -221,6 +221,7 @@ fn (mut w Walker) expr(node ast.Expr) {
// TODO make sure this doesn't happen // TODO make sure this doesn't happen
// panic('Walker: EmptyExpr') // panic('Walker: EmptyExpr')
} }
ast.ComptimeType {}
ast.AnonFn { ast.AnonFn {
w.fn_decl(mut node.decl) w.fn_decl(mut node.decl)
} }

View File

@ -10,8 +10,50 @@ import v.token
const ( const (
supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file', 'pkgconfig'] supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file', 'pkgconfig']
comptime_types = ['Map', 'Array', 'Int', 'Float', 'Struct', 'Interface', 'Enum',
'Sumtype']
) )
pub fn (mut p Parser) parse_comptime_type() ast.ComptimeType {
mut node := ast.ComptimeType{ast.ComptimeTypeKind.map_, p.tok.pos()}
p.check(.dollar)
name := p.check_name()
if name !in parser.comptime_types {
p.error('unsupported compile-time type `$name`: only $parser.comptime_types are supported')
}
mut cty := ast.ComptimeTypeKind.map_
match name {
'Map' {
cty = .map_
}
'Struct' {
cty = .struct_
}
'Interface' {
cty = .iface
}
'Int' {
cty = .int
}
'Float' {
cty = .float
}
'Array' {
cty = .array
}
'Enum' {
cty = .enum_
}
'Sumtype' {
cty = .sum_type
}
else {}
}
node = ast.ComptimeType{cty, node.pos}
return node
}
// // #include, #flag, #v // // #include, #flag, #v
fn (mut p Parser) hash() ast.HashStmt { fn (mut p Parser) hash() ast.HashStmt {
pos := p.tok.pos() pos := p.tok.pos()

View File

@ -78,7 +78,11 @@ pub fn (mut p Parser) check_expr(precedence int) ?ast.Expr {
.dollar { .dollar {
match p.peek_tok.kind { match p.peek_tok.kind {
.name { .name {
node = p.comptime_call() if p.peek_tok.lit in comptime_types {
node = p.parse_comptime_type()
} else {
node = p.comptime_call()
}
p.is_stmt_ident = is_stmt_ident p.is_stmt_ident = is_stmt_ident
} }
.key_if { .key_if {
@ -490,6 +494,7 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr {
if is_key_in { if is_key_in {
p.inside_in_array = true p.inside_in_array = true
} }
right = p.expr(precedence) right = p.expr(precedence)
if is_key_in { if is_key_in {
p.inside_in_array = false p.inside_in_array = false

View File

@ -2105,6 +2105,11 @@ pub fn (mut p Parser) name_expr() ast.Expr {
prev_tok_kind := p.prev_tok.kind prev_tok_kind := p.prev_tok.kind
mut node := ast.empty_expr() mut node := ast.empty_expr()
if p.expecting_type { if p.expecting_type {
if p.tok.kind == .dollar {
node = p.parse_comptime_type()
p.expecting_type = false
return node
}
p.expecting_type = false p.expecting_type = false
// get type position before moving to next // get type position before moving to next
type_pos := p.tok.pos() type_pos := p.tok.pos()

View File

@ -0,0 +1,84 @@
fn assert_map<T>() {
$if T is $Map {
assert true
} $else {
assert false
}
}
fn assert_array<T>() {
$if T is $Array {
assert true
} $else {
assert false
}
}
fn assert_struct<T>() {
$if T is $Struct {
assert true
} $else {
assert false
}
}
fn assert_not_struct<T>() {
$if T is $Struct {
assert false
} $else {
assert true
}
}
fn assert_not_map<T>() {
$if T is $Map {
assert false
} $else {
assert true
}
}
fn assert_not_array<T>() {
$if T is $Array {
assert false
} $else {
assert true
}
}
struct Abc {}
struct Bc {}
struct Cd {}
fn test_kind_map() {
assert_map<map[int]int>()
assert_map<map[string]int>()
assert_map<map[i64]i8>()
assert_not_map<Abc>()
assert_not_map<int>()
assert_not_map<[]int>()
}
fn test_kind_array() {
assert_array<[]int>()
assert_array<[]f32>()
assert_array<[]string>()
assert_not_array<Abc>()
assert_not_array<string>()
assert_not_array<int>()
assert_not_array<map[int]int>()
}
fn test_kind_struct() {
assert_struct<Abc>()
assert_struct<Bc>()
assert_struct<Cd>()
assert_not_struct<int>()
assert_not_struct<[]int>()
assert_not_struct<map[int]int>()
}