comptime: support type interpolation in the msg argument of $compile_warn(msg) and $compile_error(msg) (#24992)

This commit is contained in:
Krchi 2025-07-30 13:29:34 +08:00 committed by GitHub
parent 56b51b69d8
commit cd94cff219
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 123 additions and 6 deletions

View File

@ -9,16 +9,17 @@ import v.token
import v.util
import v.pkgconfig
import v.type_resolver
import strings
fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type {
if node.left !is ast.EmptyExpr {
node.left_type = c.expr(mut node.left)
}
if node.method_name == 'compile_error' {
c.error(node.args_var, node.pos)
c.error(c.comptime_call_msg(node), node.pos)
return ast.void_type
} else if node.method_name == 'compile_warn' {
c.warn(node.args_var, node.pos)
c.warn(c.comptime_call_msg(node), node.pos)
return ast.void_type
}
if node.is_env {
@ -203,6 +204,16 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type {
return f.return_type
}
fn (mut c Checker) comptime_call_msg(node ast.ComptimeCall) string {
return if node.args_var.len > 0 {
node.args_var
} else if value := c.eval_comptime_const_expr(node.args[0].expr, -1) {
value.string() or { '' }
} else {
''
}
}
fn (mut c Checker) comptime_selector(mut node ast.ComptimeSelector) ast.Type {
node.left_type = c.expr(mut node.left)
mut expr_type := c.unwrap_generic(c.expr(mut node.field_expr))
@ -415,6 +426,22 @@ fn (mut c Checker) eval_comptime_const_expr(expr ast.Expr, nlevel int) ?ast.Comp
ast.StringLiteral {
return util.smart_quote(expr.val, expr.is_raw)
}
ast.StringInterLiteral {
if nlevel < 0 {
mut sb := strings.new_builder(20)
for i, val in expr.vals {
sb.write_string(val)
if e := expr.exprs[i] {
if value := c.eval_comptime_const_expr(e, nlevel + 1) {
sb.write_string(value.string() or { '' })
} else {
c.error('unsupport expr `${e.str()}`', e.pos())
}
}
}
return sb.str()
}
}
ast.CharLiteral {
runes := expr.val.runes()
if runes.len > 0 {
@ -427,6 +454,28 @@ fn (mut c Checker) eval_comptime_const_expr(expr ast.Expr, nlevel int) ?ast.Comp
// an existing constant?
return c.eval_comptime_const_expr(expr.obj.expr, nlevel + 1)
}
idx := c.table.cur_fn.generic_names.index(expr.name)
if typ := c.table.cur_concrete_types[idx] {
sym := c.table.sym(typ)
return sym.str()
}
}
ast.SelectorExpr {
if expr.expr is ast.Ident {
idx := c.table.cur_fn.generic_names.index(expr.expr.name)
if typ := c.table.cur_concrete_types[idx] {
sym := c.table.sym(typ)
match expr.field_name {
'name' {
return sym.name
}
'idx' {
return i32(sym.idx)
}
else {}
}
}
}
}
ast.CastExpr {
cast_expr_value := c.eval_comptime_const_expr(expr.expr, nlevel + 1) or { return none }

View File

@ -2285,10 +2285,16 @@ pub fn (mut f Fmt) comptime_call(node ast.ComptimeCall) {
f.write("\$pkgconfig('${node.args_var}')")
}
node.method_name in ['compile_error', 'compile_warn'] {
if node.args_var.contains("'") {
f.write('\$${node.method_name}("${node.args_var}")')
if node.args.len == 0 {
if node.args_var.contains("'") {
f.write('\$${node.method_name}("${node.args_var}")')
} else {
f.write("\$${node.method_name}('${node.args_var}')")
}
} else {
f.write("\$${node.method_name}('${node.args_var}')")
f.write('\$${node.method_name}(')
f.expr(node.args[0].expr)
f.write(')')
}
}
node.method_name == 'd' {

View File

@ -159,7 +159,7 @@ fn (mut p Parser) comptime_call() ast.ComptimeCall {
is_html := method_name == 'html'
p.check(.lpar)
arg_pos := p.tok.pos()
if method_name in ['env', 'pkgconfig', 'compile_error', 'compile_warn'] {
if method_name in ['env', 'pkgconfig'] {
s := p.tok.lit
p.check(.string)
p.check(.rpar)
@ -172,6 +172,28 @@ fn (mut p Parser) comptime_call() ast.ComptimeCall {
env_pos: start_pos
pos: start_pos.extend(p.prev_tok.pos())
}
} else if method_name in ['compile_error', 'compile_warn'] {
mut s := ''
mut args := []ast.CallArg{}
if p.tok.kind == .string && p.peek_tok.kind == .rpar {
s = p.tok.lit
p.check(.string)
} else {
args << ast.CallArg{
expr: p.string_expr()
typ: ast.string_type
ct_expr: true
}
}
p.check(.rpar)
return ast.ComptimeCall{
scope: unsafe { nil }
method_name: method_name
args_var: s
env_pos: start_pos
pos: start_pos.extend(p.prev_tok.pos())
args: args
}
} else if method_name == 'res' {
mut has_args := false
mut type_index := ''

View File

@ -0,0 +1,40 @@
const msg = 'invalid type'
fn t[T]() int {
$if T is i8 {
assert typeof[T]().name == typeof[i8]().name
$compile_warn('invalid type ${T.name} ${T.idx}')
return 1
}
return 0
}
fn t2[T, R]() int {
$if T is i8 && R is i16 {
assert typeof[T]().name == typeof[i8]().name
assert typeof[R]().name == typeof[i16]().name
$compile_warn('invalid type ${T.name} ${T.idx}, ${R.name} ${R.idx}')
return 1
}
return 0
}
fn t3[T, R, E]() int {
$if T is i8 && R is i16 && E is i32 {
assert typeof[T]().name == typeof[i8]().name
assert typeof[R]().name == typeof[i16]().name
assert typeof[E]().name == typeof[i32]().name
$compile_warn('invalid type ${T.name} ${T.idx}, ${R.name} ${R.idx}, ${E.name} ${E.idx}')
return 1
}
return 0
}
fn test_main() {
assert t[i8]() == 1
assert t2[i8, i16]() == 1
assert t3[i8, i16, i32]() == 1
assert t[i16]() == 0
assert t2[i16, i16]() == 0
assert t3[i16, i16, i16]() == 0
}