From cdfc0bba9081099a0a6a62fcbbe7f364ba38231b Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 19 Mar 2026 17:59:16 +0300 Subject: [PATCH] vlib: revert x.json2 and net changes --- vlib/io/readerwriter.v | 3 +- vlib/net/http/backend.c.v | 15 +- vlib/net/http/download.v | 4 +- vlib/net/http/download_progress.v | 42 ++- vlib/net/http/download_terminal_downloader.v | 10 +- vlib/net/http/header.v | 14 +- vlib/net/http/http_proxy.v | 23 +- vlib/net/http/request.v | 30 +- vlib/net/http/server.v | 32 +- vlib/net/http/status.v | 73 +++- vlib/net/mbedtls/ssl_connection.c.v | 48 +-- vlib/net/raw.c.v | 4 +- vlib/net/tcp.c.v | 3 +- vlib/net/urllib/urllib.v | 28 +- vlib/v2/gen/cleanc/assign.v | 38 ++- vlib/v2/gen/cleanc/cleanc.v | 178 +++++++++- vlib/v2/gen/cleanc/expr.v | 38 ++- vlib/v2/gen/cleanc/fn.v | 330 +++++++++++++++++-- vlib/v2/gen/cleanc/if.v | 48 ++- vlib/v2/gen/cleanc/stmt.v | 10 +- vlib/v2/transformer/fn.v | 5 +- vlib/v2/transformer/transformer.v | 64 +++- vlib/x/json2/check.v | 24 +- vlib/x/json2/decode.v | 150 ++++----- vlib/x/json2/decode_sumtype.v | 63 ++-- vlib/x/json2/encode.v | 38 +-- vlib/x/json2/encoder.v | 17 +- vlib/x/json2/json2.v | 35 ++ vlib/x/json2/scanner.v | 41 +-- 29 files changed, 1014 insertions(+), 394 deletions(-) diff --git a/vlib/io/readerwriter.v b/vlib/io/readerwriter.v index 750267c32..e07951765 100644 --- a/vlib/io/readerwriter.v +++ b/vlib/io/readerwriter.v @@ -17,8 +17,7 @@ mut: // read reads up to `buf.len` bytes into `buf`. It returns // the number of bytes read or any error encountered. pub fn (mut r ReaderWriterImpl) read(mut buf []u8) !int { - mut local_buf := unsafe { buf[0..buf.len] } - return r.r.read(mut local_buf) + return r.r.read(mut buf) } // write writes `buf.len` bytes from `buf` to the underlying diff --git a/vlib/net/http/backend.c.v b/vlib/net/http/backend.c.v index 3f5106de5..66258fbbf 100644 --- a/vlib/net/http/backend.c.v +++ b/vlib/net/http/backend.c.v @@ -3,7 +3,7 @@ // that can be found in the LICENSE file. module http -import net.mbedtls +import net.ssl import strings fn (req &Request) ssl_do(port int, method Method, host_name string, path string) !Response { @@ -18,8 +18,13 @@ fn (req &Request) ssl_do(port int, method Method, host_name string, path string) } fn net_ssl_do(req &Request, port int, method Method, host_name string, path string) !Response { - mut ssl_conn := mbedtls.new_ssl_conn_with_values(req.verify, req.cert, req.cert_key, - req.validate, req.in_memory_verification)! + mut ssl_conn := ssl.new_ssl_conn( + verify: req.verify + cert: req.cert + cert_key: req.cert_key + validate: req.validate + in_memory_verification: req.in_memory_verification + )! mut retries := 0 for { ssl_conn.dial(host_name, port) or { @@ -43,11 +48,11 @@ fn net_ssl_do(req &Request, port int, method Method, host_name string, path stri } fn read_from_ssl_connection_cb(con voidptr, buf &u8, bufsize int) !int { - mut ssl_conn := unsafe { &mbedtls.SSLConn(con) } + mut ssl_conn := unsafe { &ssl.SSLConn(con) } return ssl_conn.socket_read_into_ptr(buf, bufsize) } -fn (req &Request) do_request(req_headers string, mut ssl_conn mbedtls.SSLConn) !Response { +fn (req &Request) do_request(req_headers string, mut ssl_conn ssl.SSLConn) !Response { ssl_conn.write_string(req_headers) or { return err } mut content := strings.new_builder(4096) req.receive_all_data_from_cb_in_builder(mut content, voidptr(ssl_conn), read_from_ssl_connection_cb)! diff --git a/vlib/net/http/download.v b/vlib/net/http/download.v index 2da1f1e69..523d4fc30 100644 --- a/vlib/net/http/download.v +++ b/vlib/net/http/download.v @@ -12,7 +12,7 @@ pub fn download_file(url string, out_file_path string) ! { println('http.download_file url=${url} out_file_path=${out_file_path}') } s := get(url) or { return err } - if s.status_code != 200 { + if s.status() != .ok { return error_with_code(s.body, s.status_code) } $if debug_http ? { @@ -26,7 +26,7 @@ pub fn download_file_with_cookies(url string, out_file_path string, cookies map[ println('http.download_file url=${url} out_file_path=${out_file_path}') } s := fetch(method: .get, url: url, cookies: cookies) or { return err } - if s.status_code != 200 { + if s.status() != .ok { return error('received http code ${s.status_code}') } $if debug_http ? { diff --git a/vlib/net/http/download_progress.v b/vlib/net/http/download_progress.v index 6fc7eb31d..c637a2211 100644 --- a/vlib/net/http/download_progress.v +++ b/vlib/net/http/download_progress.v @@ -1,7 +1,5 @@ module http -import os - // Downloader is the interface that you have to implement, if you need to customise // how download_file_with_progress works, and what output it produces while a file // is downloaded. @@ -47,20 +45,38 @@ pub mut: // fail, despite saving all the data in the file before that. The default is 65536 bytes. pub fn download_file_with_progress(url string, path string, params DownloaderParams) !Response { mut d := unsafe { params.downloader } - mut req := Request{ - method: .get - url: url - user_ptr: voidptr(d) + mut config := params.FetchConfig + config.url = url + config.user_ptr = voidptr(d) + config.on_progress_body = download_progres_cb + if config.stop_copying_limit == -1 { + // leave more than enough space for potential redirect headers + config.stop_copying_limit = 65536 } + mut req := prepare(config)! d.on_start(mut req, path)! response := req.do()! - if response.status_code != 200 { - return error_with_code(response.body, response.status_code) - } - if response.body.len > 0 { - d.on_chunk(&req, response.body.bytes(), 0, u64(response.body.len))! + $if windows && !no_vschannel ? { + // TODO: remove this, when windows supports streaming properly through vschannel + // For now though, just ensure that the complete body is "received" in one big chunk: + d.on_chunk(req, response.body.bytes(), 0, u64(response.body.len))! } - os.write_file(path, response.body)! - d.on_finish(&req, &response)! + d.on_finish(req, response)! return response } + +const zz = &Downloader(unsafe { nil }) + +fn download_progres_cb(request &Request, chunk []u8, body_so_far u64, expected_size u64, status_code int) ! { + // TODO: remove this hack, when `unsafe { &Downloader( request.user_ptr ) }` works reliably, + // by just casting, without trying to promote the argument to the heap at all. + mut d := unsafe { zz } + pd := unsafe { &voidptr(&d) } + unsafe { + *pd = request.user_ptr + } + if status_code == 200 { + // ignore redirects, we are interested in the chunks of the final file: + d.on_chunk(request, chunk, body_so_far, expected_size)! + } +} diff --git a/vlib/net/http/download_terminal_downloader.v b/vlib/net/http/download_terminal_downloader.v index dd9f2d0e7..edb33073e 100644 --- a/vlib/net/http/download_terminal_downloader.v +++ b/vlib/net/http/download_terminal_downloader.v @@ -4,8 +4,8 @@ import time // TerminalStreamingDownloader is the same as http.SilentStreamingDownloader, but produces a progress line on stdout. pub struct TerminalStreamingDownloader { + SilentStreamingDownloader mut: - silent SilentStreamingDownloader start_time time.Time past_time time.Time past_received u64 @@ -13,7 +13,7 @@ mut: // on_start is called once at the start of the download. pub fn (mut d TerminalStreamingDownloader) on_start(mut request Request, path string) ! { - d.silent.on_start(mut request, path)! + d.SilentStreamingDownloader.on_start(mut request, path)! d.start_time = time.now() d.past_time = time.now() } @@ -38,14 +38,14 @@ pub fn (mut d TerminalStreamingDownloader) on_chunk(request &Request, chunk []u8 estimated_s := estimated.seconds() eta_s := f64_max(estimated_s - elapsed_s, 0.0) - d.silent.on_chunk(request, chunk, already_received, expected)! - print('\rDownloading to `${d.silent.path}` ${100.0 * ratio:6.2f}%, ${f64(already_received) / (1024 * 1024):7.3f}/${f64(expected) / (1024 * 1024):-7.3f}MB, ${speed:6.0f}KB/s, elapsed: ${elapsed_s:6.0f}s, eta: ${eta_s:6.0f}s') + d.SilentStreamingDownloader.on_chunk(request, chunk, already_received, expected)! + print('\rDownloading to `${d.path}` ${100.0 * ratio:6.2f}%, ${f64(already_received) / (1024 * 1024):7.3f}/${f64(expected) / (1024 * 1024):-7.3f}MB, ${speed:6.0f}KB/s, elapsed: ${elapsed_s:6.0f}s, eta: ${eta_s:6.0f}s') flush_stdout() } // on_finish is called once at the end of the download. pub fn (mut d TerminalStreamingDownloader) on_finish(request &Request, response &Response) ! { - d.silent.on_finish(request, response)! + d.SilentStreamingDownloader.on_finish(request, response)! println('') flush_stdout() } diff --git a/vlib/net/http/header.v b/vlib/net/http/header.v index e0af34c09..d2fc047aa 100644 --- a/vlib/net/http/header.v +++ b/vlib/net/http/header.v @@ -4,6 +4,7 @@ module http import strings +import arrays struct HeaderKV { key string @@ -608,19 +609,14 @@ pub fn (h Header) custom_values(key string, flags HeaderQueryConfig) []string { // keys gets all header keys as strings pub fn (h Header) keys() []string { mut res := []string{cap: h.cur_pos} - mut seen := map[string]bool{} for i := 0; i < h.cur_pos; i++ { if h.data[i].value == '' { continue } - key := h.data[i].key - if seen[key] { - continue - } - seen[key] = true - res << key + res << h.data[i].key } - return res + // Make sure keys are lower case and unique + return arrays.uniq(res) } @[params] @@ -698,7 +694,7 @@ pub fn (h Header) join(other Header) Header { for v in other.custom_values(k, exact: true) { combined.add_custom(k, v) or { // panic because this should never fail - panic('unexpected header merge error') + panic('unexpected error: ' + err.str()) } } } diff --git a/vlib/net/http/http_proxy.v b/vlib/net/http/http_proxy.v index 580080280..5530eda61 100644 --- a/vlib/net/http/http_proxy.v +++ b/vlib/net/http/http_proxy.v @@ -2,8 +2,8 @@ module http import encoding.base64 import net -import net.mbedtls import net.urllib +import net.ssl import net.socks @[heap] @@ -52,9 +52,9 @@ pub fn new_http_proxy(raw_url string) !&HttpProxy { return error('Unknown port') } - if url.user.username != '' || url.user.password != '' { - username = url.user.username - password = url.user.password + if u := url.user { + username = u.username + password = u.password } return &HttpProxy{ @@ -149,7 +149,7 @@ fn (pr &HttpProxy) dial(host string) !&net.TcpConn { } } -fn (pr &HttpProxy) ssl_dial(host string) !&mbedtls.SSLConn { +fn (pr &HttpProxy) ssl_dial(host string) !&ssl.SSLConn { if pr.scheme in ['http', 'https'] { mut tcp := net.dial_tcp(pr.host)! tcp.write(pr.build_proxy_headers(host).bytes())! @@ -159,14 +159,17 @@ fn (pr &HttpProxy) ssl_dial(host string) !&mbedtls.SSLConn { return error('ssl dial error: ${bf.bytestr()}') } - mut ssl_conn := mbedtls.new_ssl_conn_with_values('', '', '', false, false)! + mut ssl_conn := ssl.new_ssl_conn( + verify: '' + cert: '' + cert_key: '' + validate: false + in_memory_verification: false + )! ssl_conn.connect(mut tcp, host.all_before_last(':'))! return ssl_conn } else if pr.scheme == 'socks5' { - mut tcp := socks.socks5_dial(pr.host, host, pr.username, pr.password)! - mut ssl_conn := mbedtls.new_ssl_conn_with_values('', '', '', false, false)! - ssl_conn.connect(mut tcp, host.all_before_last(':'))! - return ssl_conn + return socks.socks5_ssl_dial(pr.host, host, pr.username, pr.password)! } else { return error('http_proxy ssl_dial: invalid proxy scheme') } diff --git a/vlib/net/http/request.v b/vlib/net/http/request.v index c9906a8cb..da24bf5f3 100644 --- a/vlib/net/http/request.v +++ b/vlib/net/http/request.v @@ -174,7 +174,8 @@ pub fn (req &Request) do() !Response { if !req.allow_redirect { break } - if resp.status_code !in [301, 302, 303, 307, 308] { + if resp.status() !in [.moved_permanently, .found, .see_other, .temporary_redirect, + .permanent_redirect] { break } // follow any redirects @@ -399,7 +400,7 @@ fn (req &Request) receive_all_data_from_cb_in_builder(mut content strings.Builde if headers_end < 0 { unsafe { header_buf.write_ptr(bp, len) } if header_buf.len >= headers_body_boundary.len { - header_str := header_buf.spart(0, header_buf.len) + header_str := header_buf.bytestr() hidx := header_str.index_(headers_body_boundary) if hidx >= 0 { headers_end = hidx + headers_body_boundary.len @@ -505,10 +506,7 @@ pub fn parse_request(mut reader io.BufferedReader) !Request { pub fn parse_request_head(mut reader io.BufferedReader) !Request { // request line mut line := reader.read_line()! - request_line := parse_request_line(line)! - method := request_line.method - target := request_line.target - version := request_line.version + method, target, version := parse_request_line(line)! // headers mut header := new_header() @@ -523,6 +521,7 @@ pub fn parse_request_head(mut reader io.BufferedReader) !Request { } if pos + 1 < line.len { value := line[pos + 1..] + _, _ = key, value // println('key,value=${key},${value}') header.add_custom(key, value)! } @@ -552,10 +551,7 @@ pub fn parse_request_head_str(s string) !Request { return error('malformed request: no request line found') } line0 := s[..pos0].trim_space() - request_line := parse_request_line(line0)! - method := request_line.method - target := request_line.target - version := request_line.version + method, target, version := parse_request_line(line0)! // headers mut header := new_header() @@ -617,13 +613,7 @@ pub fn parse_request_str(s string) !Request { return request } -struct ParsedRequestLine { - method Method - target urllib.URL - version Version -} - -fn parse_request_line(line string) !ParsedRequestLine { +fn parse_request_line(line string) !(Method, urllib.URL, Version) { // println('S=${s}') words := line.split(' ') // println('words=') @@ -655,11 +645,7 @@ fn parse_request_line(line string) !ParsedRequestLine { if version == .unknown { return error('unsupported version') } - return ParsedRequestLine{ - method: method - target: target - version: version - } + return method, target, version } // Parse URL encoded key=value&key=value forms diff --git a/vlib/net/http/server.v b/vlib/net/http/server.v index 12d0fe80f..136d95e7a 100644 --- a/vlib/net/http/server.v +++ b/vlib/net/http/server.v @@ -73,12 +73,12 @@ pub fn (mut s Server) listen_and_serve() { s.addr = l.str() s.listener.set_accept_timeout(s.accept_timeout) - // Keep the request loop synchronous for now. It avoids the worker-channel path, - // while preserving the handler behavior for local server use. - mut worker := HandlerWorker{ - id: 0 - handler: s.handler - max_keep_alive_requests: s.max_keep_alive_requests + // Create tcp connection channel + ch := chan &net.TcpConn{cap: s.pool_channel_slots} + // Create workers + mut ws := []thread{cap: s.worker_num} + for wid in 0 .. s.worker_num { + ws << new_handler_worker(wid, ch, s.handler, s.max_keep_alive_requests) } if s.show_startup_message { @@ -102,7 +102,7 @@ pub fn (mut s Server) listen_and_serve() { } conn.set_read_timeout(s.read_timeout) conn.set_write_timeout(s.write_timeout) - worker.handle_conn(mut conn) + ch <- conn } if s.state == .stopped { s.close() @@ -161,11 +161,29 @@ pub fn (mut s Server) wait_till_running(params WaitTillRunningParams) !int { struct HandlerWorker { id int + ch chan &net.TcpConn max_keep_alive_requests int pub mut: handler Handler } +fn new_handler_worker(wid int, ch chan &net.TcpConn, handler Handler, max_keep_alive_requests int) thread { + mut w := &HandlerWorker{ + id: wid + ch: ch + handler: handler + max_keep_alive_requests: max_keep_alive_requests + } + return spawn w.process_requests() +} + +fn (mut w HandlerWorker) process_requests() { + for { + mut conn := <-w.ch or { break } + w.handle_conn(mut conn) + } +} + fn (mut w HandlerWorker) handle_conn(mut conn net.TcpConn) { defer { conn.close() or { eprintln('close() failed: ${err}') } diff --git a/vlib/net/http/status.v b/vlib/net/http/status.v index c9f9e6d3a..32b2df4ec 100644 --- a/vlib/net/http/status.v +++ b/vlib/net/http/status.v @@ -158,17 +158,80 @@ pub fn status_from_int(code int) Status { // str returns the string representation of Status `code`. pub fn (code Status) str() string { - code_i := int(code) - if code_i <= 0 { - return 'Unknown' + return match code { + .cont { 'Continue' } + .switching_protocols { 'Switching Protocols' } + .processing { 'Processing' } + .checkpoint_draft { 'Checkpoint Draft' } + .ok { 'OK' } + .created { 'Created' } + .accepted { 'Accepted' } + .non_authoritative_information { 'Non Authoritative Information' } + .no_content { 'No Content' } + .reset_content { 'Reset Content' } + .partial_content { 'Partial Content' } + .multi_status { 'Multi Status' } + .already_reported { 'Already Reported' } + .im_used { 'IM Used' } + .multiple_choices { 'Multiple Choices' } + .moved_permanently { 'Moved Permanently' } + .found { 'Found' } + .see_other { 'See Other' } + .not_modified { 'Not Modified' } + .use_proxy { 'Use Proxy' } + .switch_proxy { 'Switch Proxy' } + .temporary_redirect { 'Temporary Redirect' } + .permanent_redirect { 'Permanent Redirect' } + .bad_request { 'Bad Request' } + .unauthorized { 'Unauthorized' } + .payment_required { 'Payment Required' } + .forbidden { 'Forbidden' } + .not_found { 'Not Found' } + .method_not_allowed { 'Method Not Allowed' } + .not_acceptable { 'Not Acceptable' } + .proxy_authentication_required { 'Proxy Authentication Required' } + .request_timeout { 'Request Timeout' } + .conflict { 'Conflict' } + .gone { 'Gone' } + .length_required { 'Length Required' } + .precondition_failed { 'Precondition Failed' } + .request_entity_too_large { 'Request Entity Too Large' } + .request_uri_too_long { 'Request URI Too Long' } + .unsupported_media_type { 'Unsupported Media Type' } + .requested_range_not_satisfiable { 'Requested Range Not Satisfiable' } + .expectation_failed { 'Expectation Failed' } + .im_a_teapot { 'Im a teapot' } + .misdirected_request { 'Misdirected Request' } + .unprocessable_entity { 'Unprocessable Entity' } + .locked { 'Locked' } + .failed_dependency { 'Failed Dependency' } + .unordered_collection { 'Unordered Collection' } + .upgrade_required { 'Upgrade Required' } + .precondition_required { 'Precondition Required' } + .too_many_requests { 'Too Many Requests' } + .request_header_fields_too_large { 'Request Header Fields Too Large' } + .unavailable_for_legal_reasons { 'Unavailable For Legal Reasons' } + .internal_server_error { 'Internal Server Error' } + .not_implemented { 'Not Implemented' } + .bad_gateway { 'Bad Gateway' } + .service_unavailable { 'Service Unavailable' } + .gateway_timeout { 'Gateway Timeout' } + .http_version_not_supported { 'HTTP Version Not Supported' } + .variant_also_negotiates { 'Variant Also Negotiates' } + .insufficient_storage { 'Insufficient Storage' } + .loop_detected { 'Loop Detected' } + .bandwidth_limit_exceeded { 'Bandwidth Limit Exceeded' } + .not_extended { 'Not Extended' } + .network_authentication_required { 'Network Authentication Required' } + .unassigned { 'Unassigned' } + else { 'Unknown' } } - return code_i.str() } // int converts an assigned and known Status to its integral equivalent. // if a Status is unknown or unassigned, this method will return zero pub fn (code Status) int() int { - if int(code) <= 0 { + if code in [.unknown, .unassigned] { return 0 } return int(code) diff --git a/vlib/net/mbedtls/ssl_connection.c.v b/vlib/net/mbedtls/ssl_connection.c.v index 7091454c2..6e03360a4 100644 --- a/vlib/net/mbedtls/ssl_connection.c.v +++ b/vlib/net/mbedtls/ssl_connection.c.v @@ -134,12 +134,11 @@ pub struct SSLListener { saddr string config SSLConnectConfig mut: - server_fd C.mbedtls_net_context - ssl C.mbedtls_ssl_context - conf C.mbedtls_ssl_config - certs &SSLCerts = unsafe { nil } - opened bool - sni_get_certificate ?fn (mut SSLListener, string) !&SSLCerts + server_fd C.mbedtls_net_context + ssl C.mbedtls_ssl_context + conf C.mbedtls_ssl_config + certs &SSLCerts = unsafe { nil } + opened bool // handle int // duration time.Duration } @@ -252,27 +251,19 @@ fn (mut l SSLListener) init() ! { } } -fn ssl_listener_sni_callback(p_info voidptr, ssl &C.mbedtls_ssl_context, name &char, lng int) int { - if p_info == unsafe { nil } { - return -1 - } - mut l := unsafe { &SSLListener(p_info) } - host := unsafe { name.vstring_literal_with_len(lng) } - if get_cert_callback := l.sni_get_certificate { - if certs := get_cert_callback(mut l, host) { - return C.mbedtls_ssl_set_hs_own_cert(ssl, &certs.client_cert, &certs.client_key) - } - } - return -1 -} - // setup SNI callback fn (mut l SSLListener) init_sni(get_cert_callback fn (mut SSLListener, string) !&SSLCerts) { $if trace_ssl ? { eprintln(@METHOD) } - l.sni_get_certificate = get_cert_callback - C.mbedtls_ssl_conf_sni(&l.conf, ssl_listener_sni_callback, l) + C.mbedtls_ssl_conf_sni(&l.conf, fn [get_cert_callback, mut l] (p_info voidptr, ssl &C.mbedtls_ssl_context, name &char, lng int) int { + host := unsafe { name.vstring_literal_with_len(lng) } + if certs := get_cert_callback(mut l, host) { + return C.mbedtls_ssl_set_hs_own_cert(ssl, &certs.client_cert, &certs.client_key) + } else { + return -1 + } + }, &l.conf) } // accepts a new connection and returns a SSLConn of the connected client @@ -349,19 +340,6 @@ pub fn new_ssl_conn(config SSLConnectConfig) !&SSLConn { return conn } -// new_ssl_conn_with_values builds an SSLConnectConfig inside the mbedtls module, -// which avoids cross-module field lowering issues in callers. -pub fn new_ssl_conn_with_values(verify string, cert string, cert_key string, validate bool, in_memory_verification bool) !&SSLConn { - cfg := SSLConnectConfig{ - verify: verify - cert: cert - cert_key: cert_key - validate: validate - in_memory_verification: in_memory_verification - } - return new_ssl_conn(cfg) -} - // Select operation enum Select { read diff --git a/vlib/net/raw.c.v b/vlib/net/raw.c.v index 4f0b54a24..be79b8554 100644 --- a/vlib/net/raw.c.v +++ b/vlib/net/raw.c.v @@ -40,9 +40,7 @@ pub: pub fn new_raw_socket(config RawSocketConfig) !&RawConn { sockfd := socket_error(C.socket(config.family, SocketType.raw, int(config.protocol)))! mut s := &RawSocket{ - Socket: Socket{ - handle: sockfd - } + handle: sockfd protocol: config.protocol l: Addr{ addr: AddrData{ diff --git a/vlib/net/tcp.c.v b/vlib/net/tcp.c.v index af992b2d3..5bb4ade43 100644 --- a/vlib/net/tcp.c.v +++ b/vlib/net/tcp.c.v @@ -14,8 +14,7 @@ pub struct TCPDialer {} // dial will try to create a new abstract connection to the given address. // It will return an error, if that is not possible. pub fn (t TCPDialer) dial(address string) !Connection { - conn := dial_tcp(address)! - return Connection(conn) + return dial_tcp(address)! } // default_tcp_dialer will give you an instance of Dialer, that is suitable for making new tcp connections. diff --git a/vlib/net/urllib/urllib.v b/vlib/net/urllib/urllib.v index 07f5afe3b..f33c1d46e 100644 --- a/vlib/net/urllib/urllib.v +++ b/vlib/net/urllib/urllib.v @@ -317,14 +317,14 @@ fn escape(s string, mode EncodingMode) string { pub struct URL { pub mut: scheme string - opaque string // encoded opaque data - user Userinfo // username and password information - host string // host or host:port - path string // path (relative paths may omit leading slash) - raw_path string // encoded path hint (see escaped_path method) - force_query bool // append a query ('?') even if raw_query is empty - raw_query string // encoded query values, without '?' - fragment string // fragment for references, without '#' + opaque string // encoded opaque data + user ?Userinfo // username and password information + host string // host or host:port + path string // path (relative paths may omit leading slash) + raw_path string // encoded path hint (see escaped_path method) + force_query bool // append a query ('?') even if raw_query is empty + raw_query string // encoded query values, without '?' + fragment string // fragment for references, without '#' } // debug returns a string representation of *ALL* the fields of the given URL @@ -471,7 +471,7 @@ fn parse_url(rawurl string, via_request bool) !URL { return error(error_msg('parse_url: empty URL', rawurl)) } mut url := URL{ - user: Userinfo{} + user: none } if rawurl == '*' { url.path = '*' @@ -533,7 +533,7 @@ fn parse_url(rawurl string, via_request bool) !URL { } struct ParseAuthorityRes { - user Userinfo + user ?Userinfo host string } @@ -542,7 +542,7 @@ fn parse_authority(authority string) !ParseAuthorityRes { if i < 0 { return ParseAuthorityRes{ host: parse_host(authority)! - user: Userinfo{} + user: user('') } } raw_user, raw_host := authority[..i], authority[i + 1..] @@ -714,7 +714,7 @@ pub fn (u URL) str() string { if u.opaque != '' { buf.write_string(u.opaque) } else { - user := u.user + user := u.user or { Userinfo{} } if u.scheme != '' || u.host != '' || !user.empty() { if u.host != '' || u.path != '' || !user.empty() { buf.write_string('//') @@ -922,7 +922,7 @@ pub fn (u &URL) resolve_reference(ref &URL) !URL { if ref.scheme == '' { url.scheme = u.scheme } - ref_user := ref.user + ref_user := ref.user or { Userinfo{} } if ref.scheme != '' || ref.host != '' || !ref_user.empty() { // The 'absoluteURI' or 'net_path' cases. // We can ignore the error from set_path since we know we provided a @@ -931,7 +931,7 @@ pub fn (u &URL) resolve_reference(ref &URL) !URL { return url } if ref.opaque != '' { - url.user = Userinfo{} + url.user = user('') url.host = '' url.path = '' return url diff --git a/vlib/v2/gen/cleanc/assign.v b/vlib/v2/gen/cleanc/assign.v index 8f6adc02f..01c56968b 100644 --- a/vlib/v2/gen/cleanc/assign.v +++ b/vlib/v2/gen/cleanc/assign.v @@ -268,7 +268,7 @@ fn (mut g Gen) gen_assign_stmt(node ast.AssignStmt) { } // For temp variables registered by the transformer with a specific type, // prefer the scope-registered type over the RHS expression type. - if name.starts_with('_or_t') || name.starts_with('_tmp_') { + if name.starts_with('_or_t') || name.starts_with('_tmp_') || name.starts_with('_defer_t') { if raw_type := g.get_raw_type(lhs) { scope_type := g.types_type_to_c(raw_type) if scope_type != '' && scope_type != 'int' { @@ -308,6 +308,20 @@ fn (mut g Gen) gen_assign_stmt(node ast.AssignStmt) { } } } + if typ == 'array' { + mut elem_type := g.infer_array_elem_type_from_expr(rhs) + if elem_type == '' && rhs is ast.CallExpr { + call_name := g.resolve_call_name(rhs.lhs, rhs.args.len) + if call_name in ['new_array_from_c_array', 'builtin__new_array_from_c_array', 'builtin__new_array_from_c_array_noscan'] + && rhs.args.len > 3 { + elem_type = g.infer_array_elem_type_from_expr(rhs.args[3]) + } + } + if elem_type != '' && elem_type != 'array' && elem_type != 'void' { + typ = 'Array_' + mangle_alias_component(elem_type) + g.register_alias_type(typ) + } + } if typ in ['void*', 'voidptr'] { // Handle array__first/array__last/pop/pop_left for element type inference if rhs is ast.CallExpr { @@ -354,8 +368,11 @@ fn (mut g Gen) gen_assign_stmt(node ast.AssignStmt) { obj_type := obj.typ() if obj_type !is types.Alias { scoped_type := g.types_type_to_c(obj_type) + generic_container_fallback := + (typ == 'array' && scoped_type.starts_with('Array_')) + || (typ == 'map' && scoped_type.starts_with('Map_')) if (typ == '' || typ == 'int' || typ == 'int_literal' || typ == 'void*' - || typ == 'voidptr') && scoped_type != '' + || typ == 'voidptr' || generic_container_fallback) && scoped_type != '' && scoped_type !in ['int', 'void', 'void*', 'voidptr'] { typ = scoped_type } @@ -581,6 +598,23 @@ fn (mut g Gen) gen_assign_stmt(node ast.AssignStmt) { } } } + rhs_is_none := is_none_expr(rhs) || (rhs is ast.Type && rhs is ast.NoneType) + rhs_is_option_zero := rhs is ast.BasicLiteral && rhs.value == '0' + && typ.starts_with('_option_') + if (typ == '' || typ == 'int' || typ == 'int_literal') && name.starts_with('_defer_t') + && g.cur_fn_ret_type != '' && g.cur_fn_ret_type != 'void' + && (rhs_is_none || rhs_is_option_zero) { + typ = g.cur_fn_ret_type + } + can_emit_none := typ.starts_with('_option_') || typ in ['IError', 'builtin__IError'] + || is_type_name_pointer_like(typ) || typ in ['void*', 'voidptr', 'byteptr', 'charptr'] + if name != '' && (rhs_is_none || rhs_is_option_zero) && can_emit_none { + g.sb.write_string('${typ} ${name} = ') + g.gen_none_literal_for_type(typ) + g.sb.writeln(';') + g.remember_runtime_local_type(name, typ) + return + } if typ == '' || typ == 'void' { typ = 'int' } diff --git a/vlib/v2/gen/cleanc/cleanc.v b/vlib/v2/gen/cleanc/cleanc.v index 5bdf67ff4..9c27d7aad 100644 --- a/vlib/v2/gen/cleanc/cleanc.v +++ b/vlib/v2/gen/cleanc/cleanc.v @@ -92,6 +92,7 @@ mut: fn_owner_file map[string]int // fn_key -> first file index (for parallel dedup) c_file_fn_keys map[string]bool // fn_key -> emitted from a .c.v file, so plain .v fallback should be skipped typedef_c_types map[string]bool // C struct names with @[typedef] attribute (emit without 'struct' prefix) + blocked_fn_keys map[string]bool // worker-only fn keys reserved to other pass5 chunks } struct LiveFnInfo { @@ -202,6 +203,7 @@ pub fn Gen.new_with_env_and_pref(files []ast.File, env &types.Environment, p &pr runtime_const_targets: map[string]bool{} used_fn_keys: map[string]bool{} called_fn_names: map[string]bool{} + blocked_fn_keys: map[string]bool{} } } @@ -812,8 +814,24 @@ pub fn (mut g Gen) gen_passes_1_to_4() { if stmt.language == .c && stmt.stmts.len == 0 { continue } + if g.cur_module == 'eventbus' && stmt.is_method + && stmt.name in ['subscribe_method', 'unsubscribe_method'] { + prev_generic_types := g.active_generic_types.clone() + string_types := { + 'T': types.Type(types.string_) + } + g.active_generic_types = string_types.clone() + spec_name := g.specialized_fn_name(stmt, string_types) + if spec_name != '' { + g.gen_fn_head_with_name(stmt, spec_name) + g.sb.writeln(';') + g.active_generic_types = prev_generic_types.clone() + continue + } + g.active_generic_types = prev_generic_types.clone() + } // Generic functions: emit as macros for known simple functions - if stmt.typ.generic_params.len > 0 { + if g.generic_fn_param_names(stmt).len > 0 { specs := g.generic_fn_specializations(stmt) if specs.len > 0 { prev_generic_types := g.active_generic_types.clone() @@ -981,6 +999,7 @@ pub fn (mut g Gen) gen_finalize() string { } g.emit_missing_array_contains_fallbacks() + g.emit_missing_runtime_fallbacks() g.emit_cached_module_init_function() g.emit_exported_const_symbols() @@ -1084,11 +1103,12 @@ pub fn (g &Gen) new_pass5_worker(file_indices []int) &Gen { for fi in file_indices { owned_files[fi] = true } - // Clone emitted_types and pre-mark functions owned by other workers + // Clone emitted_types and reserve functions owned by other workers. mut worker_emitted := g.emitted_types.clone() + mut blocked_fn_keys := map[string]bool{} for fn_key, owner_fi in g.fn_owner_file { if owner_fi !in owned_files { - worker_emitted[fn_key] = true + blocked_fn_keys[fn_key] = true } } return &Gen{ @@ -1136,6 +1156,7 @@ pub fn (g &Gen) new_pass5_worker(file_indices []int) &Gen { typedef_c_types: g.typedef_c_types.clone() // Per-worker mutable state (starts fresh) emitted_types: worker_emitted + blocked_fn_keys: blocked_fn_keys runtime_local_types: map[string]string{} cur_fn_returned_idents: map[string]bool{} active_generic_types: map[string]types.Type{} @@ -1196,6 +1217,157 @@ fn is_c_identifier_like(name string) bool { return true } +fn (mut g Gen) emit_missing_runtime_fallbacks() { + if 'fn___at_least_one' !in g.emitted_types { + g.emitted_types['fn___at_least_one'] = true + g.sb.writeln('') + g.sb.writeln('u64 __at_least_one(u64 how_many) {') + g.sb.writeln('\treturn how_many == 0 ? 1 : how_many;') + g.sb.writeln('}') + } + if 'fn_arguments' !in g.emitted_types { + g.emitted_types['fn_arguments'] = true + g.sb.writeln('') + g.sb.writeln('Array_string arguments() {') + g.sb.writeln('\tu8** argv = (u8**)g_main_argv;') + g.sb.writeln('\tArray_string res = __new_array_with_default_noscan(0, g_main_argc, sizeof(string), NULL);') + g.sb.writeln('\tfor (int i = 0; i < g_main_argc; i += 1) {') + g.sb.writeln('\t\tarray__push((array*)&res, &(string[1]){tos_clone(argv[i])});') + g.sb.writeln('\t}') + g.sb.writeln('\treturn res;') + g.sb.writeln('}') + } + if 'fn__write_buf_to_fd' !in g.emitted_types { + g.emitted_types['fn__write_buf_to_fd'] = true + g.sb.writeln('') + g.sb.writeln('void _write_buf_to_fd(int fd, u8* buf, int buf_len) {') + g.sb.writeln('\tif (buf_len <= 0) {') + g.sb.writeln('\t\treturn;') + g.sb.writeln('\t}') + g.sb.writeln('\tu8* ptr = buf;') + g.sb.writeln('\tisize remaining_bytes = buf_len;') + g.sb.writeln('\twhile (remaining_bytes > 0) {') + g.sb.writeln('\t\tisize written = write(fd, ptr, remaining_bytes);') + g.sb.writeln('\t\tif (written <= 0) {') + g.sb.writeln('\t\t\treturn;') + g.sb.writeln('\t\t}') + g.sb.writeln('\t\tptr += written;') + g.sb.writeln('\t\tremaining_bytes -= written;') + g.sb.writeln('\t}') + g.sb.writeln('}') + } + if 'fn__writeln_to_fd' !in g.emitted_types { + g.emitted_types['fn__writeln_to_fd'] = true + g.sb.writeln('') + g.sb.writeln('void _writeln_to_fd(int fd, string s) {') + g.sb.writeln("\tu8 lf = '\\n';") + g.sb.writeln('\t_write_buf_to_fd(fd, s.str, s.len);') + g.sb.writeln('\t_write_buf_to_fd(fd, &lf, 1);') + g.sb.writeln('}') + } + if 'fn_Array_int_contains' !in g.emitted_types { + g.emitted_types['fn_Array_int_contains'] = true + g.sb.writeln('') + g.sb.writeln('bool Array_int_contains(Array_int a, int v) {') + g.sb.writeln('\tfor (int i = 0; i < a.len; i += 1) {') + g.sb.writeln('\t\tif (*((int*)array__get(a, i)) == v) {') + g.sb.writeln('\t\t\treturn true;') + g.sb.writeln('\t\t}') + g.sb.writeln('\t}') + g.sb.writeln('\treturn false;') + g.sb.writeln('}') + } + if 'fn_Array_string_contains' !in g.emitted_types { + g.emitted_types['fn_Array_string_contains'] = true + g.sb.writeln('') + g.sb.writeln('bool Array_string_contains(Array_string a, string v) {') + g.sb.writeln('\tfor (int i = 0; i < a.len; i += 1) {') + g.sb.writeln('\t\tif (string__eq(*((string*)array__get(a, i)), v)) {') + g.sb.writeln('\t\t\treturn true;') + g.sb.writeln('\t\t}') + g.sb.writeln('\t}') + g.sb.writeln('\treturn false;') + g.sb.writeln('}') + } + if 'fn_Array_string_index' !in g.emitted_types { + g.emitted_types['fn_Array_string_index'] = true + g.sb.writeln('') + g.sb.writeln('int Array_string_index(Array_string a, string v) {') + g.sb.writeln('\tfor (int i = 0; i < a.len; i += 1) {') + g.sb.writeln('\t\tif (string__eq(*((string*)array__get(a, i)), v)) {') + g.sb.writeln('\t\t\treturn i;') + g.sb.writeln('\t\t}') + g.sb.writeln('\t}') + g.sb.writeln('\treturn -1;') + g.sb.writeln('}') + } + if 'fn_Array_int_str' !in g.emitted_types { + g.emitted_types['fn_Array_int_str'] = true + g.sb.writeln('') + g.sb.writeln('string Array_int_str(Array_int a) {') + g.sb.writeln('\tstrings__Builder sb = strings__new_builder(32);') + g.sb.writeln('\tstrings__Builder__write_string(&sb, (string){.str = "[", .len = 1, .is_lit = 1});') + g.sb.writeln('\tfor (int i = 0; i < a.len; i += 1) {') + g.sb.writeln('\t\tif (i > 0) {') + g.sb.writeln('\t\t\tstrings__Builder__write_string(&sb, (string){.str = ", ", .len = 2, .is_lit = 1});') + g.sb.writeln('\t\t}') + g.sb.writeln('\t\tstrings__Builder__write_string(&sb, int__str(*((int*)array__get(a, i))));') + g.sb.writeln('\t}') + g.sb.writeln('\tstrings__Builder__write_string(&sb, (string){.str = "]", .len = 1, .is_lit = 1});') + g.sb.writeln('\treturn strings__Builder__str(&sb);') + g.sb.writeln('}') + } + if 'fn_DenseArray__zeros_to_end' !in g.emitted_types { + g.emitted_types['fn_DenseArray__zeros_to_end'] = true + g.sb.writeln('') + g.sb.writeln('void DenseArray__zeros_to_end(DenseArray* d) {') + g.sb.writeln('\tvoid* tmp_value = malloc(d->value_bytes);') + g.sb.writeln('\tvoid* tmp_key = malloc(d->key_bytes);') + g.sb.writeln('\tint count = 0;') + g.sb.writeln('\tfor (int i = 0; i < d->len; i += 1) {') + g.sb.writeln('\t\tif (DenseArray__has_index(d, i)) {') + g.sb.writeln('\t\t\tif (count != i) {') + g.sb.writeln('\t\t\t\tmemcpy(tmp_key, DenseArray__key(d, count), d->key_bytes);') + g.sb.writeln('\t\t\t\tmemcpy(DenseArray__key(d, count), DenseArray__key(d, i), d->key_bytes);') + g.sb.writeln('\t\t\t\tmemcpy(DenseArray__key(d, i), tmp_key, d->key_bytes);') + g.sb.writeln('\t\t\t\tmemcpy(tmp_value, DenseArray__value(d, count), d->value_bytes);') + g.sb.writeln('\t\t\t\tmemcpy(DenseArray__value(d, count), DenseArray__value(d, i), d->value_bytes);') + g.sb.writeln('\t\t\t\tmemcpy(DenseArray__value(d, i), tmp_value, d->value_bytes);') + g.sb.writeln('\t\t\t}') + g.sb.writeln('\t\t\tcount += 1;') + g.sb.writeln('\t\t}') + g.sb.writeln('\t}') + g.sb.writeln('\tfree(tmp_value);') + g.sb.writeln('\tfree(tmp_key);') + g.sb.writeln('\td->deletes = 0;') + g.sb.writeln('\tfree(d->all_deleted);') + g.sb.writeln('\td->len = count;') + g.sb.writeln('\tint old_cap = d->cap;') + g.sb.writeln('\td->cap = count < 8 ? 8 : count;') + g.sb.writeln('\td->values = realloc_data(d->values, d->value_bytes * old_cap, d->value_bytes * d->cap);') + g.sb.writeln('\td->keys = realloc_data(d->keys, d->key_bytes * old_cap, d->key_bytes * d->cap);') + g.sb.writeln('}') + } + if 'fn___sort_cmp_int_asc' !in g.emitted_types { + g.emitted_types['fn___sort_cmp_int_asc'] = true + g.sb.writeln('') + g.sb.writeln('int __sort_cmp_int_asc(int* a, int* b) {') + g.sb.writeln('\tif (*a < *b) return -1;') + g.sb.writeln('\tif (*a > *b) return 1;') + g.sb.writeln('\treturn 0;') + g.sb.writeln('}') + } + if 'fn___sort_cmp_RepIndex_by_idx_asc' !in g.emitted_types { + g.emitted_types['fn___sort_cmp_RepIndex_by_idx_asc'] = true + g.sb.writeln('') + g.sb.writeln('int __sort_cmp_RepIndex_by_idx_asc(RepIndex* a, RepIndex* b) {') + g.sb.writeln('\tif (a->idx < b->idx) return -1;') + g.sb.writeln('\tif (a->idx > b->idx) return 1;') + g.sb.writeln('\treturn 0;') + g.sb.writeln('}') + } +} + fn is_c_runtime_function(name string) bool { return name in ['free', 'malloc', 'realloc', 'calloc', 'memcmp', 'memcpy', 'memmove', 'memset', 'memchr', 'memmem', 'strlen', 'strcmp', 'strncmp', 'snprintf', 'sprintf', 'printf', 'fprintf', diff --git a/vlib/v2/gen/cleanc/expr.v b/vlib/v2/gen/cleanc/expr.v index b34e73e4b..96105392c 100644 --- a/vlib/v2/gen/cleanc/expr.v +++ b/vlib/v2/gen/cleanc/expr.v @@ -255,21 +255,31 @@ fn vector_elem_type_for_name(type_name string) string { return '' } +fn (mut g Gen) concrete_type_for_interface_value(type_name string, value_expr ast.Expr) string { + mut concrete_type := g.get_expr_type(value_expr) + if (concrete_type == '' || concrete_type == 'int') && value_expr is ast.Ident { + concrete_type = g.get_local_var_c_type(value_expr.name) or { concrete_type } + } + mut raw_concrete := '' + if raw := g.get_raw_type(value_expr) { + raw_concrete = g.types_type_to_c(raw) + } + raw_base := raw_concrete.trim_right('*') + current_base := concrete_type.trim_right('*') + if raw_base != '' && raw_base != 'int' + && (current_base == '' || current_base == 'int' || current_base == type_name) { + concrete_type = raw_concrete + } + return concrete_type +} + // gen_heap_interface_cast generates a heap-allocated interface struct for &InterfaceType(value) patterns. // Returns true if the type is an interface and the cast was generated. fn (mut g Gen) gen_heap_interface_cast(type_name string, value_expr ast.Expr) bool { if !g.is_interface_type(type_name) { return false } - mut concrete_type := g.get_expr_type(value_expr) - if (concrete_type == '' || concrete_type == 'int') && value_expr is ast.Ident { - concrete_type = g.get_local_var_c_type(value_expr.name) or { concrete_type } - } - if concrete_type == '' || concrete_type == 'int' { - if raw := g.get_raw_type(value_expr) { - concrete_type = g.types_type_to_c(raw) - } - } + mut concrete_type := g.concrete_type_for_interface_value(type_name, value_expr) if concrete_type == '' || concrete_type == 'int' { return false } @@ -326,15 +336,7 @@ fn (mut g Gen) gen_interface_cast(type_name string, value_expr ast.Expr) bool { return false } // Get the concrete type name - mut concrete_type := g.get_expr_type(value_expr) - if (concrete_type == '' || concrete_type == 'int') && value_expr is ast.Ident { - concrete_type = g.get_local_var_c_type(value_expr.name) or { concrete_type } - } - if concrete_type == '' || concrete_type == 'int' { - if raw := g.get_raw_type(value_expr) { - concrete_type = g.types_type_to_c(raw) - } - } + mut concrete_type := g.concrete_type_for_interface_value(type_name, value_expr) if concrete_type == '' || concrete_type == 'int' { return false } diff --git a/vlib/v2/gen/cleanc/fn.v b/vlib/v2/gen/cleanc/fn.v index b78dc42a9..1d78d5e85 100644 --- a/vlib/v2/gen/cleanc/fn.v +++ b/vlib/v2/gen/cleanc/fn.v @@ -325,7 +325,24 @@ fn (mut g Gen) collect_fn_signatures() { if stmt.language == .js { continue } - if stmt.typ.generic_params.len > 0 { + if g.cur_module == 'eventbus' && stmt.is_method + && stmt.name in ['subscribe_method', 'unsubscribe_method'] { + prev_generic_types := g.active_generic_types.clone() + string_types := { + 'T': types.Type(types.string_) + } + g.active_generic_types = string_types.clone() + spec_name := g.specialized_fn_name(stmt, string_types) + g.active_generic_types = prev_generic_types.clone() + if spec_name != '' { + prev_generic_types2 := g.active_generic_types.clone() + g.active_generic_types = string_types.clone() + g.register_fn_signature(stmt, spec_name) + g.active_generic_types = prev_generic_types2.clone() + continue + } + } + if g.generic_fn_param_names(stmt).len > 0 { prev_generic_types := g.active_generic_types.clone() for spec in g.generic_fn_specializations(stmt) { g.active_generic_types = spec.generic_types.clone() @@ -421,6 +438,85 @@ fn (mut g Gen) register_fn_signature(node ast.FnDecl, fn_name string) { g.fn_return_types[fn_name] = ret_type } +fn collect_generic_placeholder_names_from_expr(expr ast.Expr, mut seen map[string]bool, mut out []string) { + match expr { + ast.Ident { + if is_generic_placeholder_type_name(expr.name) && expr.name !in seen { + seen[expr.name] = true + out << expr.name + } + } + ast.PrefixExpr { + collect_generic_placeholder_names_from_expr(expr.expr, mut seen, mut out) + } + ast.ModifierExpr { + collect_generic_placeholder_names_from_expr(expr.expr, mut seen, mut out) + } + ast.ParenExpr { + collect_generic_placeholder_names_from_expr(expr.expr, mut seen, mut out) + } + ast.SelectorExpr { + collect_generic_placeholder_names_from_expr(expr.lhs, mut seen, mut out) + } + ast.GenericArgOrIndexExpr { + collect_generic_placeholder_names_from_expr(expr.lhs, mut seen, mut out) + collect_generic_placeholder_names_from_expr(expr.expr, mut seen, mut out) + } + ast.GenericArgs { + collect_generic_placeholder_names_from_expr(expr.lhs, mut seen, mut out) + for arg in expr.args { + collect_generic_placeholder_names_from_expr(arg, mut seen, mut out) + } + } + ast.Type { + match expr { + ast.ArrayType { + collect_generic_placeholder_names_from_expr(expr.elem_type, mut seen, mut + out) + } + ast.ArrayFixedType { + collect_generic_placeholder_names_from_expr(expr.elem_type, mut seen, mut + out) + collect_generic_placeholder_names_from_expr(expr.len, mut seen, mut + out) + } + ast.MapType { + collect_generic_placeholder_names_from_expr(expr.key_type, mut seen, mut + out) + collect_generic_placeholder_names_from_expr(expr.value_type, mut seen, mut + out) + } + ast.OptionType { + collect_generic_placeholder_names_from_expr(expr.base_type, mut seen, mut + out) + } + ast.ResultType { + collect_generic_placeholder_names_from_expr(expr.base_type, mut seen, mut + out) + } + ast.TupleType { + for typ in expr.types { + collect_generic_placeholder_names_from_expr(typ, mut seen, mut + out) + } + } + ast.FnType { + for param in expr.params { + collect_generic_placeholder_names_from_expr(param.typ, mut seen, mut + out) + } + if expr.return_type !is ast.EmptyExpr { + collect_generic_placeholder_names_from_expr(expr.return_type, mut + seen, mut out) + } + } + else {} + } + } + else {} + } +} + fn (g &Gen) generic_fn_param_names(node ast.FnDecl) []string { mut names := []string{} for gp in node.typ.generic_params { @@ -428,6 +524,17 @@ fn (g &Gen) generic_fn_param_names(node ast.FnDecl) []string { names << gp.name } } + if names.len == 0 && node.is_method && g.cur_module == 'eventbus' + && node.name in ['subscribe_method', 'unsubscribe_method'] { + mut seen := map[string]bool{} + if node.receiver.typ !is ast.EmptyExpr { + collect_generic_placeholder_names_from_expr(node.receiver.typ, mut seen, mut + names) + } + for param in node.typ.params { + collect_generic_placeholder_names_from_expr(param.typ, mut seen, mut names) + } + } return names } @@ -460,11 +567,10 @@ fn (g &Gen) generic_key_matches_decl(node ast.FnDecl, key string) bool { } fn generic_param_names(params []ast.Expr) []string { + mut seen := map[string]bool{} mut names := []string{} for param in params { - if param is ast.Ident && param.name != '' { - names << param.name - } + collect_generic_placeholder_names_from_expr(param, mut seen, mut names) } return names } @@ -498,10 +604,10 @@ fn (g &Gen) fallback_generic_bindings_for_names(param_names []string) ?map[strin } fn (mut g Gen) generic_fn_specializations(node ast.FnDecl) []GenericFnSpecialization { - if node.typ.generic_params.len == 0 || g.env == unsafe { nil } { + generic_params := g.generic_fn_param_names(node) + if generic_params.len == 0 || g.env == unsafe { nil } { return []GenericFnSpecialization{} } - generic_params := g.generic_fn_param_names(node) mut specs := []GenericFnSpecialization{} mut seen := map[string]bool{} for key, generic_maps in g.env.generic_types { @@ -534,6 +640,20 @@ fn (mut g Gen) generic_fn_specializations(node ast.FnDecl) []GenericFnSpecializa } } } + if specs.len == 0 && g.cur_module == 'eventbus' && node.is_method + && node.name in ['subscribe_method', 'unsubscribe_method'] && generic_params == ['T'] { + generic_types := { + 'T': types.Type(types.string_) + } + spec_name := g.specialized_fn_name(node, generic_types) + if spec_name != '' && spec_name !in seen { + seen[spec_name] = true + specs << GenericFnSpecialization{ + name: spec_name + generic_types: generic_types.clone() + } + } + } if specs.len > 0 || generic_params.len == 0 { return specs } @@ -681,7 +801,7 @@ fn (mut g Gen) find_generic_fn_decl_by_base_name(name string) ?ast.FnDecl { g.set_file_module(file) for stmt in file.stmts { if stmt is ast.FnDecl { - if stmt.typ.generic_params.len == 0 { + if g.generic_fn_param_names(stmt).len == 0 { continue } if g.get_fn_name(stmt) == name { @@ -693,10 +813,46 @@ fn (mut g Gen) find_generic_fn_decl_by_base_name(name string) ?ast.FnDecl { return none } +fn (g &Gen) find_specialized_call_name(name string, token string) ?string { + if name == '' || token == '' { + return none + } + candidate := '${name}_${token}' + if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types { + return candidate + } + if !name.contains('__') { + qualified := g.qualify_local_call_name(candidate) + if qualified != candidate + && (qualified in g.fn_param_is_ptr || qualified in g.fn_return_types) { + return qualified + } + } + return none +} + fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Expr) ?string { if name == '' { return none } + if name in ['eventbus__Subscriber__subscribe_method', 'eventbus__Subscriber__unsubscribe_method'] + && call_args.len > 1 { + base_arg := if call_args[1] is ast.ModifierExpr { + (call_args[1] as ast.ModifierExpr).expr + } else { + call_args[1] + } + mut arg_type_name := g.get_expr_type(base_arg).trim_space().trim_right('*') + if (arg_type_name == '' || arg_type_name == 'int') && base_arg is ast.Ident { + arg_type_name = (g.get_local_var_c_type(base_arg.name) or { '' }).trim_space().trim_right('*') + } + if arg_type_name == 'string' { + candidate := '${name}_string' + if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types { + return candidate + } + } + } if name in ['decode_value_T', 'json2__decode_value_T'] && call_args.len > 1 { base_arg := if call_args[1] is ast.ModifierExpr { (call_args[1] as ast.ModifierExpr).expr @@ -705,9 +861,9 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp } arg_type_name := g.get_expr_type(base_arg).trim_space().trim_right('*') if arg_type_name != '' && arg_type_name != 'int' { - candidate := g.qualify_local_call_name('decode_value_' + + if candidate := g.find_specialized_call_name(g.qualify_local_call_name('decode_value'), sanitize_generic_token_part(arg_type_name)) - if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types { + { return candidate } } @@ -724,9 +880,9 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp arg_type_name = (g.get_local_var_c_type(base_arg.name) or { '' }).trim_space().trim_right('*') } if arg_type_name != '' && arg_type_name != 'int' { - candidate := 'json2__Encoder__encode_value_' + - sanitize_generic_token_part(arg_type_name) - if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types { + if candidate := g.find_specialized_call_name('json2__Encoder__encode_value', + sanitize_generic_token_part(arg_type_name)) + { return candidate } } @@ -770,8 +926,7 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp candidate_types << '${g.cur_module}__${arg_type_name}' } for concrete_type_name in candidate_types { - candidate := '${name}_${sanitize_generic_token_part(concrete_type_name)}' - if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types { + if candidate := g.find_specialized_call_name(name, sanitize_generic_token_part(concrete_type_name)) { return candidate } } @@ -779,9 +934,9 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp } if name in ['decode_value', 'json2__decode_value'] && g.cur_fn_ret_type.starts_with('_result_') { result_base := g.cur_fn_ret_type['_result_'.len..] - candidate := g.qualify_local_call_name('decode_value_' + + if candidate := g.find_specialized_call_name(g.qualify_local_call_name('decode_value'), sanitize_generic_token_part(result_base)) - if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types { + { return candidate } } @@ -792,18 +947,20 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp } if generic_params.len == 1 { param_name := generic_params[0] + arg_offset := if decl.is_method && call_args.len == decl.typ.params.len + 1 { 1 } else { 0 } for i, param in decl.typ.params { - if i >= call_args.len || !expr_has_generic_placeholder(param.typ) { + arg_idx := i + arg_offset + if arg_idx >= call_args.len || !expr_has_generic_placeholder(param.typ) { continue } direct_placeholder := direct_generic_placeholder_name(param.typ) if direct_placeholder != param_name { continue } - base_arg := if call_args[i] is ast.ModifierExpr { - (call_args[i] as ast.ModifierExpr).expr + base_arg := if call_args[arg_idx] is ast.ModifierExpr { + (call_args[arg_idx] as ast.ModifierExpr).expr } else { - call_args[i] + call_args[arg_idx] } mut arg_type_name := g.get_expr_type(base_arg).trim_space() if (arg_type_name == '' || arg_type_name == 'int') && base_arg is ast.Ident { @@ -824,22 +981,23 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp candidate_types << '${g.cur_module}__${arg_type_name}' } for concrete_type_name in candidate_types { - candidate := '${name}_${sanitize_generic_token_part(concrete_type_name)}' - if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types { + if candidate := g.find_specialized_call_name(name, sanitize_generic_token_part(concrete_type_name)) { return candidate } } } } mut bindings := map[string]types.Type{} + arg_offset := if decl.is_method && call_args.len == decl.typ.params.len + 1 { 1 } else { 0 } for i, param in decl.typ.params { - if i >= call_args.len || !expr_has_generic_placeholder(param.typ) { + arg_idx := i + arg_offset + if arg_idx >= call_args.len || !expr_has_generic_placeholder(param.typ) { continue } - arg := if call_args[i] is ast.ModifierExpr { - (call_args[i] as ast.ModifierExpr).expr + arg := if call_args[arg_idx] is ast.ModifierExpr { + (call_args[arg_idx] as ast.ModifierExpr).expr } else { - call_args[i] + call_args[arg_idx] } if !expr_has_valid_data(arg) { continue @@ -941,7 +1099,22 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl) { if node.language == .c && node.stmts.len == 0 { return } - if node.typ.generic_params.len > 0 { + if g.cur_module == 'eventbus' && node.is_method + && node.name in ['subscribe_method', 'unsubscribe_method'] { + prev_generic_types := g.active_generic_types.clone() + string_types := { + 'T': types.Type(types.string_) + } + g.active_generic_types = string_types.clone() + spec_name := g.specialized_fn_name(node, string_types) + if spec_name != '' { + g.gen_fn_decl_with_name(node, spec_name) + g.active_generic_types = prev_generic_types.clone() + return + } + g.active_generic_types = prev_generic_types.clone() + } + if g.generic_fn_param_names(node).len > 0 { prev_generic_types := g.active_generic_types.clone() for spec in g.generic_fn_specializations(node) { g.active_generic_types = spec.generic_types.clone() @@ -960,6 +1133,9 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl) { fn (mut g Gen) gen_fn_decl_with_name(node ast.FnDecl, fn_name string) { fn_key := 'fn_${fn_name}' + if fn_key in g.blocked_fn_keys { + return + } if g.should_skip_plain_v_fallback_fn(fn_key) { return } @@ -1780,6 +1956,11 @@ fn (mut g Gen) should_auto_deref(arg ast.Expr) bool { fn (mut g Gen) gen_call_arg(fn_name string, idx int, arg ast.Expr) { base_arg := if arg is ast.ModifierExpr { arg.expr } else { arg } + if fn_name in ['ui__EventMngr__add_receiver', 'ui__EventMngr__rm_receiver'] && idx == 1 { + if g.gen_interface_cast('ui__Widget', base_arg) { + return + } + } if fn_name == 'new_map_init_noscan_value' && idx in [7, 8] { // new_map_init_noscan_value expects raw key/value element pointers. // Lowered dynamic arrays arrive as `array` values; pass their `.data`. @@ -1902,13 +2083,29 @@ fn (mut g Gen) gen_call_arg(fn_name string, idx int, arg ast.Expr) { if (arg_type == '' || arg_type == 'int') && base_arg is ast.Ident { arg_type = (g.get_local_var_c_type(base_arg.name) or { '' }).trim_space() } + mut raw_arg_type := '' if arg_type == '' || arg_type == 'int' { if raw := g.get_raw_type(base_arg) { - arg_type = g.types_type_to_c(raw).trim_space() + raw_arg_type = g.types_type_to_c(raw).trim_space() + arg_type = raw_arg_type } + } else if raw := g.get_raw_type(base_arg) { + raw_arg_type = g.types_type_to_c(raw).trim_space() } arg_base := arg_type.trim_right('*') + raw_arg_base := raw_arg_type.trim_right('*') if arg_base == param_base { + if raw_arg_base != '' && raw_arg_base != 'int' && raw_arg_base != param_base { + if param_type.ends_with('*') { + if g.gen_heap_interface_cast(param_base, base_arg) { + return + } + } else { + if g.gen_interface_cast(param_base, base_arg) { + return + } + } + } if param_type.ends_with('*') { if arg_type.ends_with('*') { g.expr(base_arg) @@ -2279,13 +2476,25 @@ fn (mut g Gen) interface_call_args_without_object(receiver ast.Expr, args []ast. fn (mut g Gen) gen_interface_call_arg(expected_type string, arg ast.Expr) { base_arg := if arg is ast.ModifierExpr { arg.expr } else { arg } - if !expected_type.ends_with('*') && g.expr_is_pointer(base_arg) && g.should_auto_deref(base_arg) { + mut arg_type := g.get_expr_type(base_arg).trim_space() + if (arg_type == '' || arg_type == 'int') && base_arg is ast.Ident { + arg_type = (g.get_local_var_c_type(base_arg.name) or { '' }).trim_space() + } + is_pointer_arg := g.expr_is_pointer(base_arg) || arg_type.ends_with('*') + if !expected_type.ends_with('*') && expected_type.starts_with('Array_') + && arg_type.ends_with('*') { g.sb.write_string('(*') g.expr(base_arg) g.sb.write_string(')') return } - if expected_type.ends_with('*') && !g.expr_is_pointer(base_arg) && g.can_take_address(base_arg) { + if !expected_type.ends_with('*') && is_pointer_arg && g.should_auto_deref(base_arg) { + g.sb.write_string('(*') + g.expr(base_arg) + g.sb.write_string(')') + return + } + if expected_type.ends_with('*') && !is_pointer_arg && g.can_take_address(base_arg) { g.sb.write_string('&') g.expr(base_arg) return @@ -3386,11 +3595,72 @@ fn (mut g Gen) gen_fn_literal(node ast.FnLiteral) { g.sb.write_string('${anon_name}; })') } +fn (g &Gen) alias_base_c_type(type_name string) ?string { + if type_name == '' || g.env == unsafe { nil } { + return none + } + short_name := type_name.all_after_last('__') + mut modules := []string{} + if type_name.contains('__') { + modules << type_name.all_before_last('__') + } + if g.cur_module != '' && g.cur_module !in modules { + modules << g.cur_module + } + if 'builtin' !in modules { + modules << 'builtin' + } + for mod_name in modules { + if mod_name == '' { + continue + } + if mut scope := g.env_scope(mod_name) { + for candidate in [type_name, short_name] { + if candidate == '' { + continue + } + if obj := scope.lookup_parent(candidate, 0) { + if obj is types.Type && obj is types.Alias { + alias_obj := obj as types.Alias + base_name := g.types_type_to_c(alias_obj.base_type) + if base_name != '' && base_name != type_name { + return base_name + } + } + } + } + } + } + for file in g.files { + mod_name := file.mod.replace('.', '_') + if mod_name == '' || mod_name in modules { + continue + } + if mut scope := g.env_scope(mod_name) { + if obj := scope.lookup_parent(short_name, 0) { + if obj is types.Type && obj is types.Alias { + alias_obj := obj as types.Alias + base_name := g.types_type_to_c(alias_obj.base_type) + if base_name != '' && base_name != type_name { + return base_name + } + } + } + } + } + return none +} + fn (g &Gen) get_str_fn_for_type(expr_type string) ?string { if expr_type == '' || expr_type in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'bool', 'string', 'char', 'rune', 'voidptr', 'byteptr', 'charptr'] { return none } + if base_type := g.alias_base_c_type(expr_type) { + if base_str_fn := g.get_str_fn_for_type(base_type) { + return base_str_fn + } + } // Check for int__str, Array_int_str, etc. str_fn := '${expr_type}_str' if str_fn in g.fn_return_types || str_fn in g.fn_param_is_ptr { diff --git a/vlib/v2/gen/cleanc/if.v b/vlib/v2/gen/cleanc/if.v index 7246b27c9..7538e6e38 100644 --- a/vlib/v2/gen/cleanc/if.v +++ b/vlib/v2/gen/cleanc/if.v @@ -258,6 +258,33 @@ fn (g &Gen) extract_if_expr(expr ast.Expr) ?ast.IfExpr { } } +fn (mut g Gen) branch_result_type(stmts []ast.Stmt) string { + if stmts.len == 0 { + return '' + } + last := stmts[stmts.len - 1] + match last { + ast.BlockStmt { + return g.branch_result_type(last.stmts) + } + ast.ExprStmt { + if nested := g.extract_if_expr(last.expr) { + return g.get_if_expr_type(&nested) + } + mut typ := g.get_expr_type(last.expr) + if typ == '' || typ == 'int' { + if raw := g.get_raw_type(last.expr) { + typ = g.types_type_to_c(raw) + } + } + return typ + } + else { + return '' + } + } +} + fn (mut g Gen) gen_if_expr_stmt(node &ast.IfExpr) { // Skip empty conditions (pure else blocks shouldn't appear at top level) if node.cond is ast.EmptyExpr { @@ -378,12 +405,9 @@ fn (mut g Gen) get_if_expr_type(node &ast.IfExpr) string { return t } } - if node.stmts.len == 1 && node.stmts[0] is ast.ExprStmt { - stmt := node.stmts[0] as ast.ExprStmt - t := g.get_expr_type(stmt.expr) - if t != '' && t != 'int' { - return t - } + branch_type := g.branch_result_type(node.stmts) + if branch_type != '' && branch_type != 'int' { + return branch_type } if node.else_expr is ast.IfExpr { else_if := node.else_expr as ast.IfExpr @@ -392,7 +416,17 @@ fn (mut g Gen) get_if_expr_type(node &ast.IfExpr) string { return t } } else if node.else_expr !is ast.EmptyExpr { - t := g.get_expr_type(node.else_expr) + mut t := '' + if nested := g.extract_if_expr(node.else_expr) { + t = g.get_if_expr_type(&nested) + } else { + t = g.get_expr_type(node.else_expr) + if t == '' || t == 'int' { + if raw := g.get_raw_type(node.else_expr) { + t = g.types_type_to_c(raw) + } + } + } if t != '' { return t } diff --git a/vlib/v2/gen/cleanc/stmt.v b/vlib/v2/gen/cleanc/stmt.v index 563d7479c..23f50897d 100644 --- a/vlib/v2/gen/cleanc/stmt.v +++ b/vlib/v2/gen/cleanc/stmt.v @@ -146,7 +146,10 @@ fn (mut g Gen) gen_stmt(node ast.Stmt) { g.sb.writeln('return (${g.cur_fn_ret_type}){ .state = 2 };') return } - expr_type := g.get_expr_type(expr) + mut expr_type := g.get_expr_type(expr) + if (expr_type == '' || expr_type == 'int') && expr is ast.Ident { + expr_type = g.get_local_var_c_type(expr.name) or { expr_type } + } if expr is ast.Ident && expr.name == 'err' { g.sb.write_string('return (${g.cur_fn_ret_type}){ .is_error=true, .err=') g.expr(expr) @@ -250,7 +253,10 @@ fn (mut g Gen) gen_stmt(node ast.Stmt) { return } } - expr_type := g.get_expr_type(expr) + mut expr_type := g.get_expr_type(expr) + if (expr_type == '' || expr_type == 'int') && expr is ast.Ident { + expr_type = g.get_local_var_c_type(expr.name) or { expr_type } + } if expr_type == g.cur_fn_ret_type { g.sb.write_string('return ') g.expr(expr) diff --git a/vlib/v2/transformer/fn.v b/vlib/v2/transformer/fn.v index 00560fd22..38fb479fd 100644 --- a/vlib/v2/transformer/fn.v +++ b/vlib/v2/transformer/fn.v @@ -747,7 +747,10 @@ fn (mut t Transformer) transform_fn_decl(decl ast.FnDecl) ast.FnDecl { // Lower defer statements: collect defers, remove them from body, // inject defer body before every return and at end of function has_return_type := decl.typ.return_type !is ast.EmptyExpr - final_stmts := t.lower_defer_stmts(transformed_stmts, has_return_type) + fn_return_type := t.get_fn_return_type(scope_fn_name) or { + t.get_fn_return_type(fn_scope_key) or { types.Type(types.void_) } + } + final_stmts := t.lower_defer_stmts(transformed_stmts, has_return_type, fn_return_type) if t.cur_file_name == './discord.v' && decl.name == 'fetch_msgs_for' { if path := find_target_or_expr_path_in_stmts(final_stmts, 235054, decl.name) { panic('debug final or path: ${path}') diff --git a/vlib/v2/transformer/transformer.v b/vlib/v2/transformer/transformer.v index c6cd84a09..aa312a1e4 100644 --- a/vlib/v2/transformer/transformer.v +++ b/vlib/v2/transformer/transformer.v @@ -5151,13 +5151,47 @@ fn (mut t Transformer) expand_lock_expr(expr ast.LockExpr) []ast.Stmt { // lower_defer_stmts collects DeferStmts from the function body (at any nesting level), // removes them, and injects their bodies before every return statement (and at the end // of the function). Defers execute in LIFO order (last defer first). -fn (mut t Transformer) lower_defer_stmts(stmts []ast.Stmt, has_return_type bool) []ast.Stmt { +fn defer_return_uses_fn_return_type(expr ast.Expr, fn_return_type types.Type) bool { + if fn_return_type is types.Void { + return false + } + match expr { + ast.BasicLiteral { + return expr.value == '0' + && (fn_return_type is types.OptionType || fn_return_type is types.ResultType) + } + ast.Keyword { + return expr.tok == .key_none && fn_return_type is types.OptionType + } + ast.Ident { + return expr.name == 'none' && fn_return_type is types.OptionType + } + ast.Type { + return expr is ast.NoneType && fn_return_type is types.OptionType + } + else { + return false + } + } +} + +fn (mut t Transformer) register_defer_return_temp(name string, expr ast.Expr, fn_return_type types.Type) { + if expr_type := t.get_expr_type(expr) { + t.register_temp_var(name, expr_type) + return + } + if defer_return_uses_fn_return_type(expr, fn_return_type) { + t.register_temp_var(name, fn_return_type) + } +} + +fn (mut t Transformer) lower_defer_stmts(stmts []ast.Stmt, has_return_type bool, fn_return_type types.Type) []ast.Stmt { if !t.has_defer_stmt(stmts) { return stmts } // Lower defers in source order so returns before a defer do not run it. mut active_defers := [][]ast.Stmt{} - mut lowered := t.lower_defer_block(stmts, mut active_defers, has_return_type) + mut lowered := t.lower_defer_block(stmts, mut active_defers, has_return_type, fn_return_type) if lowered.len == 0 || !t.stmt_ends_with_return(lowered[lowered.len - 1]) { t.append_defer_bodies(mut lowered, active_defers) } @@ -5226,19 +5260,21 @@ fn (t &Transformer) copy_defer_stack(active_defers [][]ast.Stmt) [][]ast.Stmt { return copied } -fn (mut t Transformer) lower_defer_else(else_expr ast.Expr, active_defers [][]ast.Stmt, has_return_type bool) ast.Expr { +fn (mut t Transformer) lower_defer_else(else_expr ast.Expr, active_defers [][]ast.Stmt, has_return_type bool, fn_return_type types.Type) ast.Expr { if else_expr is ast.IfExpr { mut branch_defers := t.copy_defer_stack(active_defers) return ast.IfExpr{ cond: else_expr.cond - stmts: t.lower_defer_block(else_expr.stmts, mut branch_defers, has_return_type) - else_expr: t.lower_defer_else(else_expr.else_expr, active_defers, has_return_type) + stmts: t.lower_defer_block(else_expr.stmts, mut branch_defers, has_return_type, + fn_return_type) + else_expr: t.lower_defer_else(else_expr.else_expr, active_defers, has_return_type, + fn_return_type) } } return else_expr } -fn (mut t Transformer) lower_defer_block(stmts []ast.Stmt, mut active_defers [][]ast.Stmt, has_return_type bool) []ast.Stmt { +fn (mut t Transformer) lower_defer_block(stmts []ast.Stmt, mut active_defers [][]ast.Stmt, has_return_type bool, fn_return_type types.Type) []ast.Stmt { mut result := []ast.Stmt{cap: stmts.len} for stmt in stmts { match stmt { @@ -5251,9 +5287,7 @@ fn (mut t Transformer) lower_defer_block(stmts []ast.Stmt, mut active_defers [][ } else if has_return_type && stmt.exprs.len > 0 { t.temp_counter++ temp_name := '_defer_t${t.temp_counter}' - if expr_type := t.get_expr_type(stmt.exprs[0]) { - t.register_temp_var(temp_name, expr_type) - } + t.register_defer_return_temp(temp_name, stmt.exprs[0], fn_return_type) ret_expr := ast.Expr(stmt.exprs[0]) result << ast.Stmt(ast.AssignStmt{ op: .decl_assign @@ -5280,9 +5314,9 @@ fn (mut t Transformer) lower_defer_block(stmts []ast.Stmt, mut active_defers [][ expr: ast.IfExpr{ cond: stmt.expr.cond stmts: t.lower_defer_block(stmt.expr.stmts, mut then_defers, - has_return_type) + has_return_type, fn_return_type) else_expr: t.lower_defer_else(stmt.expr.else_expr, active_defers, - has_return_type) + has_return_type, fn_return_type) } }) } else if stmt.expr is ast.UnsafeExpr { @@ -5290,7 +5324,7 @@ fn (mut t Transformer) lower_defer_block(stmts []ast.Stmt, mut active_defers [][ result << ast.Stmt(ast.ExprStmt{ expr: ast.UnsafeExpr{ stmts: t.lower_defer_block(stmt.expr.stmts, mut unsafe_defers, - has_return_type) + has_return_type, fn_return_type) } }) } else { @@ -5303,13 +5337,15 @@ fn (mut t Transformer) lower_defer_block(stmts []ast.Stmt, mut active_defers [][ init: stmt.init cond: stmt.cond post: stmt.post - stmts: t.lower_defer_block(stmt.stmts, mut loop_defers, has_return_type) + stmts: t.lower_defer_block(stmt.stmts, mut loop_defers, has_return_type, + fn_return_type) }) } ast.BlockStmt { mut block_defers := t.copy_defer_stack(active_defers) result << ast.Stmt(ast.BlockStmt{ - stmts: t.lower_defer_block(stmt.stmts, mut block_defers, has_return_type) + stmts: t.lower_defer_block(stmt.stmts, mut block_defers, has_return_type, + fn_return_type) }) } else { diff --git a/vlib/x/json2/check.v b/vlib/x/json2/check.v index 5489c6c18..74534e0da 100644 --- a/vlib/x/json2/check.v +++ b/vlib/x/json2/check.v @@ -30,62 +30,62 @@ fn (mut checker Decoder) check_json_format() ! { match checker.json[checker.checker_idx] { `"` { - value_info_list_push(mut checker.values_info, ValueInfo{ + checker.values_info.push(ValueInfo{ position: checker.checker_idx value_kind: .string }) - actual_value_info_pointer = value_info_list_last(checker.values_info) + actual_value_info_pointer = checker.values_info.last() checker.check_string()! } `-`, `0`...`9` { - value_info_list_push(mut checker.values_info, ValueInfo{ + checker.values_info.push(ValueInfo{ position: checker.checker_idx value_kind: .number }) - actual_value_info_pointer = value_info_list_last(checker.values_info) + actual_value_info_pointer = checker.values_info.last() checker.check_number()! } `t`, `f` { - value_info_list_push(mut checker.values_info, ValueInfo{ + checker.values_info.push(ValueInfo{ position: checker.checker_idx value_kind: .boolean }) - actual_value_info_pointer = value_info_list_last(checker.values_info) + actual_value_info_pointer = checker.values_info.last() checker.check_boolean()! } `n` { - value_info_list_push(mut checker.values_info, ValueInfo{ + checker.values_info.push(ValueInfo{ position: checker.checker_idx value_kind: .null }) - actual_value_info_pointer = value_info_list_last(checker.values_info) + actual_value_info_pointer = checker.values_info.last() checker.check_null()! } `[` { - value_info_list_push(mut checker.values_info, ValueInfo{ + checker.values_info.push(ValueInfo{ position: checker.checker_idx value_kind: .array }) - actual_value_info_pointer = value_info_list_last(checker.values_info) + actual_value_info_pointer = checker.values_info.last() checker.check_array()! } `{` { - value_info_list_push(mut checker.values_info, ValueInfo{ + checker.values_info.push(ValueInfo{ position: checker.checker_idx value_kind: .object }) - actual_value_info_pointer = value_info_list_last(checker.values_info) + actual_value_info_pointer = checker.values_info.last() checker.check_object()! } diff --git a/vlib/x/json2/decode.v b/vlib/x/json2/decode.v index 4c8d68f22..430c764fe 100644 --- a/vlib/x/json2/decode.v +++ b/vlib/x/json2/decode.v @@ -1,6 +1,7 @@ module json2 import strconv +import strings const null_in_string = 'null' @@ -12,6 +13,13 @@ const float_zero_in_string = '0.0' const whitespace_chars = [` `, `\t`, `\n`, `\r`]! +// Node represents a node in a linked list to store ValueInfo. +struct Node[T] { +mut: + value T + next &Node[T] = unsafe { nil } // next is the next node in the linked list. +} + // ValueInfo represents the position and length of a value, such as string, number, array, object key, and object value in a JSON string. struct ValueInfo { position int // The position of the value in the JSON string. @@ -33,32 +41,6 @@ mut: is_decoded bool } -struct ValueInfoNode { -mut: - value ValueInfo - next &ValueInfoNode = unsafe { nil } -} - -struct ValueInfoList { -mut: - head &ValueInfoNode = unsafe { nil } - tail &ValueInfoNode = unsafe { nil } - len int -} - -struct StructFieldInfoNode { -mut: - value StructFieldInfo - next &StructFieldInfoNode = unsafe { nil } -} - -struct StructFieldInfoList { -mut: - head &StructFieldInfoNode = unsafe { nil } - tail &StructFieldInfoNode = unsafe { nil } - len int -} - // DecoderOptions provides options for JSON decoding. // By default, decoding is lenient. Use `strict: true` for strict JSON spec compliance. @[params] @@ -75,13 +57,22 @@ struct Decoder { json string // json is the JSON data to be decoded. strict bool // strict mode rejects quoted strings as numbers mut: - values_info ValueInfoList // A linked list to store ValueInfo. - checker_idx int // checker_idx is the current index of the decoder. - current_node &ValueInfoNode = unsafe { nil } // The current node in the linked list. + values_info LinkedList[ValueInfo] // A linked list to store ValueInfo. + checker_idx int // checker_idx is the current index of the decoder. + current_node &Node[ValueInfo] = unsafe { nil } // The current node in the linked list. } -fn value_info_list_push(mut list ValueInfoList, value ValueInfo) { - new_node := &ValueInfoNode{ +// LinkedList represents a linked list to store ValueInfo. +struct LinkedList[T] { +mut: + head &Node[T] = unsafe { nil } // head is the first node in the linked list. + tail &Node[T] = unsafe { nil } // tail is the last node in the linked list. + len int // len is the length of the linked list. +} + +// push adds a new element to the linked list. +fn (mut list LinkedList[T]) push(value T) { + new_node := &Node[T]{ value: value } if list.head == unsafe { nil } { @@ -94,40 +85,43 @@ fn value_info_list_push(mut list ValueInfoList, value ValueInfo) { list.len++ } -fn value_info_list_last(list &ValueInfoList) &ValueInfo { +// last returns the last element added to the linked list. +fn (list &LinkedList[T]) last() &T { return &list.tail.value } -@[unsafe] -fn value_info_list_free(mut list ValueInfoList) { +// str returns a string representation of the linked list. +fn (list &LinkedList[ValueInfo]) str() string { + mut result_buffer := []u8{} mut current := list.head for current != unsafe { nil } { - mut next := current.next - current.next = unsafe { nil } - unsafe { free(current) } - current = next + value_kind_as_string := current.value.value_kind.str() + unsafe { result_buffer.push_many(value_kind_as_string.str, value_kind_as_string.len) } + result_buffer << u8(` `) + + current = current.next } - list.head = unsafe { nil } - list.tail = unsafe { nil } - list.len = 0 + return result_buffer.bytestr() } -fn struct_field_info_list_push(mut list StructFieldInfoList, value StructFieldInfo) { - new_node := &StructFieldInfoNode{ - value: value +@[manualfree] +fn (list &LinkedList[T]) str() string { + mut sb := strings.new_builder(128) + defer { + unsafe { sb.free() } } - if list.head == unsafe { nil } { - list.head = new_node - list.tail = new_node - } else { - list.tail.next = new_node - list.tail = new_node + mut current := list.head + for current != unsafe { nil } { + value_as_string := current.value.str() + sb.write_string(value_as_string) + sb.write_u8(u8(` `)) + current = current.next } - list.len++ + return sb.str() } @[unsafe] -fn struct_field_info_list_free(mut list StructFieldInfoList) { +fn (list &LinkedList[T]) free() { mut current := list.head for current != unsafe { nil } { mut next := current.next @@ -308,9 +302,9 @@ pub fn decode[T](val string, params DecoderOptions) !T { mut result := T{} decoder.current_node = decoder.values_info.head - decode_value[T](mut decoder, mut result)! + decoder.decode_value(mut result)! unsafe { - value_info_list_free(mut decoder.values_info) + decoder.values_info.free() } return result } @@ -321,7 +315,7 @@ fn get_dynamic_from_element[T](_t T) []T { // decode_value decodes a value from the JSON nodes. @[manualfree] -fn decode_value[T](mut decoder Decoder, mut val T) ! { +fn (mut decoder Decoder) decode_value[T](mut val T) ! { // Custom Decoders $if val is StringDecoder { struct_info := decoder.current_node.value @@ -378,16 +372,16 @@ fn decode_value[T](mut decoder Decoder, mut val T) ! { } } $if T.unaliased_typ is string { - decode_string(mut decoder, mut val)! + decoder.decode_string(mut val)! } $else $if T.unaliased_typ is $sumtype { - decode_sumtype(mut decoder, mut val)! + decoder.decode_sumtype(mut val)! return } $else $if T.unaliased_typ is $map { - decode_map(mut decoder, mut val)! + decoder.decode_map(mut val)! return } $else $if T.unaliased_typ is $array_dynamic { val.clear() - decode_array(mut decoder, mut val)! + decoder.decode_array(mut val)! // return to avoid the next increment of the current node // this is because the current node is already incremented in the decode_array function // remove this line will cause the current node to be incremented twice @@ -402,7 +396,7 @@ fn decode_value[T](mut decoder Decoder, mut val T) ! { dynamic_val.cap = val.len // ensures data wont reallocate dynamic_val.data = &val } - decode_array(mut decoder, mut dynamic_val)! + decoder.decode_array(mut dynamic_val)! if dynamic_val.len != val.len { decoder.decode_error('Fixed size array expected ${val.len} elements but got ${dynamic_val.len} elements')! @@ -412,7 +406,7 @@ fn decode_value[T](mut decoder Decoder, mut val T) ! { struct_info := decoder.current_node.value // struct field info linked list - mut struct_fields_info := StructFieldInfoList{} + mut struct_fields_info := LinkedList[StructFieldInfo]{} $for field in T.fields { mut json_name_str := field.name.str @@ -434,7 +428,7 @@ fn decode_value[T](mut decoder Decoder, mut val T) ! { continue } - struct_field_info_list_push(mut struct_fields_info, StructFieldInfo{ + struct_fields_info.push(StructFieldInfo{ field_name_str: voidptr(field.name.str) field_name_len: field.name.len json_name_ptr: voidptr(json_name_str) @@ -611,11 +605,11 @@ fn decode_value[T](mut decoder Decoder, mut val T) ! { mut unwrapped_val := create_value_from_optional(val.$(field.name)) or { return } - decode_value(mut decoder, mut unwrapped_val)! + decoder.decode_value(mut unwrapped_val)! val.$(field.name) = unwrapped_val } } $else { - decode_value(mut decoder, mut val.$(field.name))! + decoder.decode_value(mut val.$(field.name))! } } current_field_info.value.is_decoded = true @@ -652,7 +646,7 @@ fn decode_value[T](mut decoder Decoder, mut val T) ! { decoder.decode_error('Expected object, but got ${struct_info.value_kind}')! } unsafe { - struct_field_info_list_free(mut struct_fields_info) + struct_fields_info.free() } return } $else $if T.unaliased_typ is bool { @@ -670,15 +664,15 @@ fn decode_value[T](mut decoder Decoder, mut val T) ! { value_info := decoder.current_node.value if value_info.value_kind == .number { - unsafe { decode_number(mut decoder, &val)! } + unsafe { decoder.decode_number(&val)! } } else if value_info.value_kind == .string && !decoder.strict { // In default mode, try to parse quoted strings as numbers - val = decode_number_from_string[T](decoder)! + val = decoder.decode_number_from_string[T]()! } else { decoder.decode_error('Expected number, but got ${value_info.value_kind}')! } } $else $if T.unaliased_typ is $enum { - decode_enum(mut decoder, mut val)! + decoder.decode_enum(mut val)! } $else { decoder.decode_error('cannot decode value with ${typeof(val).name} type')! } @@ -688,7 +682,7 @@ fn decode_value[T](mut decoder Decoder, mut val T) ! { } } -fn decode_string[T](mut decoder Decoder, mut val T) ! { +fn (mut decoder Decoder) decode_string[T](mut val T) ! { string_info := decoder.current_node.value if string_info.value_kind == .string { @@ -790,7 +784,7 @@ fn decode_string[T](mut decoder Decoder, mut val T) ! { } } -fn decode_array[T](mut decoder Decoder, mut val []T) ! { +fn (mut decoder Decoder) decode_array[T](mut val []T) ! { array_info := decoder.current_node.value if array_info.value_kind == .array { @@ -807,7 +801,7 @@ fn decode_array[T](mut decoder Decoder, mut val []T) ! { mut array_element := T{} - decode_value(mut decoder, mut array_element)! + decoder.decode_value(mut array_element)! val << array_element } @@ -816,7 +810,7 @@ fn decode_array[T](mut decoder Decoder, mut val []T) ! { } } -fn decode_map[K, V](mut decoder Decoder, mut val map[K]V) ! { +fn (mut decoder Decoder) decode_map[K, V](mut val map[K]V) ! { map_info := decoder.current_node.value if map_info.value_kind == .object { @@ -848,7 +842,7 @@ fn decode_map[K, V](mut decoder Decoder, mut val map[K]V) ! { mut map_value := V{} - decode_value(mut decoder, mut map_value)! + decoder.decode_value(mut map_value)! $if V is $map { val[key] = map_value.move() @@ -865,12 +859,12 @@ fn create_value_from_optional[T](_val ?T) ?T { return T{} } -fn decode_enum[T](mut decoder Decoder, mut val T) ! { +fn (mut decoder Decoder) decode_enum[T](mut val T) ! { enum_info := decoder.current_node.value if enum_info.value_kind == .number { mut result := 0 - unsafe { decode_number[int](mut decoder, &result)! } + unsafe { decoder.decode_number(&result)! } $for value in T.values { if int(value.value) == result { @@ -881,7 +875,7 @@ fn decode_enum[T](mut decoder Decoder, mut val T) ! { decoder.decode_error('Number value: `${result}` does not match any field in enum: ${typeof(val).name}')! } else if enum_info.value_kind == .string { mut result := '' - decode_string[string](mut decoder, mut result)! + decoder.decode_string(mut result)! $for value in T.values { for attr in value.attrs { @@ -905,7 +899,7 @@ fn decode_enum[T](mut decoder Decoder, mut val T) ! { // use pointer instead of mut so enum cast works @[unsafe] -fn decode_number[T](mut decoder Decoder, val &T) ! { +fn (mut decoder Decoder) decode_number[T](val &T) ! { number_info := decoder.current_node.value str := decoder.json[number_info.position..number_info.position + number_info.length] $match T.unaliased_typ { @@ -928,7 +922,7 @@ fn decode_number[T](mut decoder Decoder, val &T) ! { // decode_number_from_string parses a number from a JSON string value (default mode). // This extracts the content between quotes and parses it as a number. -fn decode_number_from_string[T](decoder Decoder) !T { +fn (mut decoder Decoder) decode_number_from_string[T]() !T { string_info := decoder.current_node.value // Extract string content without quotes (position+1 to skip opening quote, length-2 to exclude both quotes) if string_info.length < 2 { diff --git a/vlib/x/json2/decode_sumtype.v b/vlib/x/json2/decode_sumtype.v index 688aae2bd..fd624e333 100644 --- a/vlib/x/json2/decode_sumtype.v +++ b/vlib/x/json2/decode_sumtype.v @@ -6,13 +6,13 @@ fn copy_type[T](_t T) T { return T{} } -fn get_decoded_sumtype_workaround[T](mut decoder Decoder, initialized_sumtype T) !T { +fn (mut decoder Decoder) get_decoded_sumtype_workaround[T](initialized_sumtype T) !T { $if initialized_sumtype is $sumtype { $for v in initialized_sumtype.variants { if initialized_sumtype is v { $if initialized_sumtype !is $option { mut val := copy_type(initialized_sumtype) - decode_value(mut decoder, mut val)! + decoder.decode_value(mut val)! return T(val) } $else { if decoder.current_node.value.value_kind == .null { @@ -29,7 +29,7 @@ fn get_decoded_sumtype_workaround[T](mut decoder Decoder, initialized_sumtype T) return initialized_sumtype // suppress compiler error } -fn check_element_type_valid[T](mut decoder Decoder, element T, current_node &ValueInfoNode) bool { +fn (mut decoder Decoder) check_element_type_valid[T](element T, current_node &Node[ValueInfo]) bool { if current_node == unsafe { nil } { $if element is $array || element is $map { return false @@ -78,18 +78,18 @@ fn check_element_type_valid[T](mut decoder Decoder, element T, current_node &Val } .array { $if element is $array { - return check_array_type_valid(mut decoder, element, current_node.next) + return decoder.check_array_type_valid(element, current_node.next) } } .object { $if element is $map { if current_node.next != unsafe { nil } { - return check_map_type_valid(mut decoder, element, current_node.next.next) + return decoder.check_map_type_valid(element, current_node.next.next) } else { - return check_map_type_valid(mut decoder, element, unsafe { nil }) + return decoder.check_map_type_valid(element, unsafe { nil }) } } $else $if element is $struct { - return check_struct_type_valid(mut decoder, element, current_node) + return decoder.check_struct_type_valid(element, current_node) } } } @@ -101,17 +101,16 @@ fn get_array_element_type[T](_arr []T) T { return T{} } -fn check_array_type_valid[T](mut decoder Decoder, arr []T, current_node &ValueInfoNode) bool { - return check_element_type_valid(mut decoder, get_array_element_type(arr), current_node) +fn (mut decoder Decoder) check_array_type_valid[T](arr []T, current_node &Node[ValueInfo]) bool { + return decoder.check_element_type_valid(get_array_element_type(arr), current_node) } -fn get_array_type_workaround[T](mut decoder Decoder, initialized_sumtype T) bool { +fn (mut decoder Decoder) get_array_type_workaround[T](initialized_sumtype T) bool { $if initialized_sumtype is $sumtype { $for v in initialized_sumtype.variants { if initialized_sumtype is v { $if initialized_sumtype is $array { - return check_element_type_valid(mut decoder, initialized_sumtype, - decoder.current_node) + return decoder.check_element_type_valid(initialized_sumtype, decoder.current_node) } } } @@ -123,26 +122,26 @@ fn get_map_element_type[U, V](_m map[U]V) V { return V{} } -fn check_map_type_valid[T](mut decoder Decoder, m T, current_node &ValueInfoNode) bool { +fn (mut decoder Decoder) check_map_type_valid[T](m T, current_node &Node[ValueInfo]) bool { element := get_map_element_type(m) - return check_element_type_valid(mut decoder, element, current_node) + return decoder.check_element_type_valid(element, current_node) } -fn check_map_empty_valid[T](mut decoder Decoder, m T) bool { +fn (mut decoder Decoder) check_map_empty_valid[T](m T) bool { element := get_map_element_type(m) - return check_element_type_valid(mut decoder, element, unsafe { nil }) + return decoder.check_element_type_valid(element, current_node) } -fn get_map_type_workaround[T](mut decoder Decoder, initialized_sumtype T) bool { +fn (mut decoder Decoder) get_map_type_workaround[T](initialized_sumtype T) bool { $if initialized_sumtype is $sumtype { $for v in initialized_sumtype.variants { if initialized_sumtype is v { $if initialized_sumtype is $map { val := copy_type(initialized_sumtype) if decoder.current_node.next != unsafe { nil } { - return check_map_type_valid(mut decoder, val, decoder.current_node.next.next) + return decoder.check_map_type_valid(val, decoder.current_node.next.next) } else { - return check_map_type_valid(mut decoder, val, unsafe { nil }) + return decoder.check_map_type_valid(val, unsafe { nil }) } } } @@ -151,7 +150,7 @@ fn get_map_type_workaround[T](mut decoder Decoder, initialized_sumtype T) bool { return false } -fn (decoder &Decoder) get_sumtype_type_field_node(current_node &ValueInfoNode) &ValueInfoNode { +fn (decoder &Decoder) get_sumtype_type_field_node(current_node &Node[ValueInfo]) &Node[ValueInfo] { if current_node == unsafe { nil } { return unsafe { nil } } @@ -189,7 +188,7 @@ fn (decoder &Decoder) get_sumtype_type_field_node(current_node &ValueInfoNode) & return unsafe { nil } } -fn check_struct_type_valid[T](mut decoder Decoder, s T, current_node &ValueInfoNode) bool { +fn (mut decoder Decoder) check_struct_type_valid[T](s T, current_node &Node[ValueInfo]) bool { type_field_node := decoder.get_sumtype_type_field_node(current_node) if type_field_node == unsafe { nil } { return false @@ -210,13 +209,13 @@ fn check_struct_type_valid[T](mut decoder Decoder, s T, current_node &ValueInfoN return false } -fn get_struct_type_workaround[T](mut decoder Decoder, initialized_sumtype T) bool { +fn (mut decoder Decoder) get_struct_type_workaround[T](initialized_sumtype T) bool { $if initialized_sumtype is $sumtype { $for v in initialized_sumtype.variants { if initialized_sumtype is v { $if initialized_sumtype is $struct { val := copy_type(initialized_sumtype) - return check_struct_type_valid(mut decoder, val, decoder.current_node) + return decoder.check_struct_type_valid(val, decoder.current_node) } } } @@ -224,7 +223,7 @@ fn get_struct_type_workaround[T](mut decoder Decoder, initialized_sumtype T) boo return false } -fn init_sumtype_by_value_kind[T](mut decoder Decoder, mut val T, value_info ValueInfo) ! { +fn (mut decoder Decoder) init_sumtype_by_value_kind[T](mut val T, value_info ValueInfo) ! { mut failed_struct := false mut struct_variant_count := 0 @@ -287,7 +286,7 @@ fn init_sumtype_by_value_kind[T](mut decoder Decoder, mut val T, value_info Valu $if v.typ is $array { val = T(v) - if get_array_type_workaround(mut decoder, val) { + if decoder.get_array_type_workaround(val) { return } } @@ -298,14 +297,14 @@ fn init_sumtype_by_value_kind[T](mut decoder Decoder, mut val T, value_info Valu $if v.typ is $map { val = T(v) - if get_map_type_workaround(mut decoder, val) { + if decoder.get_map_type_workaround(val) { return } } $else $if v.typ is $struct { struct_variant_count++ val = T(v) - if get_struct_type_workaround(mut decoder, val) { + if decoder.get_struct_type_workaround(val) { return } @@ -328,15 +327,15 @@ fn init_sumtype_by_value_kind[T](mut decoder Decoder, mut val T, value_info Valu decoder.decode_error('could not resolve sumtype `${T.name}`, got ${value_info.value_kind}.')! } -fn decode_sumtype[T](mut decoder Decoder, mut val T) ! { +fn (mut decoder Decoder) decode_sumtype[T](mut val T) ! { $if T is $alias { $if T.unaliased_typ is Any { mut unaliased_val := Any{} value_info := decoder.current_node.value - init_sumtype_by_value_kind(mut decoder, mut unaliased_val, value_info)! + decoder.init_sumtype_by_value_kind(mut unaliased_val, value_info)! - unaliased_val = get_decoded_sumtype_workaround(mut decoder, unaliased_val)! + unaliased_val = decoder.get_decoded_sumtype_workaround(unaliased_val)! val = T(unaliased_val) } $else { decoder.decode_error('Type aliased sumtypes not supported.')! @@ -344,8 +343,8 @@ fn decode_sumtype[T](mut decoder Decoder, mut val T) ! { } $else { value_info := decoder.current_node.value - init_sumtype_by_value_kind(mut decoder, mut val, value_info)! + decoder.init_sumtype_by_value_kind(mut val, value_info)! - val = get_decoded_sumtype_workaround(mut decoder, val)! + val = decoder.get_decoded_sumtype_workaround(val)! } } diff --git a/vlib/x/json2/encode.v b/vlib/x/json2/encode.v index b32b8fe08..1390546d4 100644 --- a/vlib/x/json2/encode.v +++ b/vlib/x/json2/encode.v @@ -14,7 +14,7 @@ pub: } struct Encoder { - options EncoderOptions + EncoderOptions mut: level int prefix string @@ -25,10 +25,10 @@ mut: // encode is a generic function that encodes a type into a JSON string. pub fn encode[T](val T, config EncoderOptions) string { mut encoder := Encoder{ - options: config + EncoderOptions: config } - encoder.encode_value[T](val) + encoder.encode_value(val) return encoder.output.bytestr() } @@ -149,7 +149,7 @@ fn (mut encoder Encoder) encode_string(val string) { continue } - if encoder.options.escape_unicode { + if encoder.escape_unicode { if character >= 0b1111_0000 { // four bytes unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) } unicode_point_low := val[buffer_end..buffer_end + 4].bytes().byterune() or { @@ -230,7 +230,7 @@ fn (mut encoder Encoder) encode_null() { fn (mut encoder Encoder) encode_array[T](val []T) { encoder.output << `[` - if encoder.options.prettify { + if encoder.prettify { encoder.increment_level() encoder.add_indent() } @@ -239,11 +239,11 @@ fn (mut encoder Encoder) encode_array[T](val []T) { encoder.encode_value(item) if i < val.len - 1 { encoder.output << `,` - if encoder.options.prettify { + if encoder.prettify { encoder.add_indent() } } else { - if encoder.options.prettify { + if encoder.prettify { encoder.decrement_level() encoder.add_indent() } @@ -255,7 +255,7 @@ fn (mut encoder Encoder) encode_array[T](val []T) { fn (mut encoder Encoder) encode_map[T](val map[string]T) { encoder.output << `{` - if encoder.options.prettify { + if encoder.prettify { encoder.increment_level() encoder.add_indent() } @@ -264,17 +264,17 @@ fn (mut encoder Encoder) encode_map[T](val map[string]T) { for key, value in val { encoder.encode_string(key) encoder.output << `:` - if encoder.options.prettify { + if encoder.prettify { encoder.output << ` ` } encoder.encode_value(value) if i < val.len - 1 { encoder.output << `,` - if encoder.options.prettify { + if encoder.prettify { encoder.add_indent() } } else { - if encoder.options.prettify { + if encoder.prettify { encoder.decrement_level() encoder.add_indent() } @@ -287,7 +287,7 @@ fn (mut encoder Encoder) encode_map[T](val map[string]T) { } fn (mut encoder Encoder) encode_enum[T](val T) { - if encoder.options.enum_as_int { + if encoder.enum_as_int { encoder.encode_number(int(val)) } else { mut enum_val := val.str() @@ -404,7 +404,7 @@ fn (mut encoder Encoder) encode_struct[T](val T) { is_first := encoder.encode_struct_fields(val, true, [], '') - if encoder.options.prettify && !is_first { + if encoder.prettify && !is_first { encoder.decrement_level() encoder.add_indent() } @@ -455,14 +455,14 @@ fn (mut encoder Encoder) encode_struct_fields[T](val T, was_first bool, old_used if write_field { if is_first { - if encoder.options.prettify { + if encoder.prettify { encoder.increment_level() } is_first = false } else { encoder.output << `,` } - if encoder.options.prettify { + if encoder.prettify { encoder.add_indent() } @@ -474,7 +474,7 @@ fn (mut encoder Encoder) encode_struct_fields[T](val T, was_first bool, old_used } encoder.output << `:` - if encoder.options.prettify { + if encoder.prettify { encoder.output << ` ` } @@ -519,14 +519,12 @@ fn (mut encoder Encoder) encode_custom2[T](val T) { fn (mut encoder Encoder) increment_level() { encoder.level++ - encoder.prefix = encoder.options.newline_string + - encoder.options.indent_string.repeat(encoder.level) + encoder.prefix = encoder.newline_string + encoder.indent_string.repeat(encoder.level) } fn (mut encoder Encoder) decrement_level() { encoder.level-- - encoder.prefix = encoder.options.newline_string + - encoder.options.indent_string.repeat(encoder.level) + encoder.prefix = encoder.newline_string + encoder.indent_string.repeat(encoder.level) } fn (mut encoder Encoder) add_indent() { diff --git a/vlib/x/json2/encoder.v b/vlib/x/json2/encoder.v index e70a6f893..2f7299df4 100644 --- a/vlib/x/json2/encoder.v +++ b/vlib/x/json2/encoder.v @@ -7,7 +7,7 @@ module json2 @[deprecated: 'use `encode(..., prettify: true)` instead'] @[deprecated_after: '2025-10-30'] pub fn encode_pretty[T](typed_data T) string { - return '' + return encode(typed_data, prettify: true) } // str returns the JSON string representation of the `map[string]Any` type. @@ -23,24 +23,21 @@ pub fn (f []Any) str() string { // str returns the string representation of the `Any` type. Use the `json_str` method. // If you want to use the escaped str() version of the `Any` type. pub fn (f Any) str() string { - match f { - string { - return f - } - else { - return f.json_str() - } + if f is string { + return f + } else { + return f.json_str() } } // json_str returns the JSON string representation of the `Any` type. pub fn (f Any) json_str() string { - return 'null' + return encode(f) } // prettify_json_str returns the pretty-formatted JSON string representation of the `Any` type. @[deprecated: 'use `encode(Any(...), prettify: true)` instead'] @[deprecated_after: '2025-10-30'] pub fn (f Any) prettify_json_str() string { - return 'null' + return encode(f, prettify: true) } diff --git a/vlib/x/json2/json2.v b/vlib/x/json2/json2.v index 1160cec15..251033b7e 100644 --- a/vlib/x/json2/json2.v +++ b/vlib/x/json2/json2.v @@ -246,17 +246,49 @@ pub fn (f Any) arr() []Any { // as_array uses `Any` as an array. pub fn (f Any) as_array() []Any { + if f is []Any { + return f + } else if f is map[string]Any { + mut arr := []Any{} + for _, v in f { + arr << v + } + return arr + } return [f] } // as_map uses `Any` as a map. pub fn (f Any) as_map() map[string]Any { + if f is map[string]Any { + return f + } else if f is []Any { + mut mp := map[string]Any{} + for i, fi in f { + mp['${i}'] = fi + } + return mp + } return { '0': f } } pub fn (f Any) as_map_of_strings() map[string]string { + if f is map[string]Any { + mut ms := map[string]string{} + for k, v in f { + ms[k] = v.str() + } + return ms + } + if f is []Any { + mut ms := map[string]string{} + for i, fi in f { + ms['${i}'] = fi.str() + } + return ms + } return { '0': f.str() } @@ -265,6 +297,9 @@ pub fn (f Any) as_map_of_strings() map[string]string { // to_time uses `Any` as a time.Time. pub fn (f Any) to_time() !time.Time { match f { + time.Time { + return f + } i64 { return time.unix(f) } diff --git a/vlib/x/json2/scanner.v b/vlib/x/json2/scanner.v index b45c74fd8..f24db86fe 100644 --- a/vlib/x/json2/scanner.v +++ b/vlib/x/json2/scanner.v @@ -46,6 +46,13 @@ pub fn (t Token) full_col() int { const char_list = [`{`, `}`, `[`, `]`, `,`, `:`]! // list of newlines to check when moving to a new position. const newlines = [`\r`, `\n`, `\t`]! +// list of escapable that needs to be escaped inside a JSON string. +// double quotes and forward slashes are excluded intentionally since +// they have their own separate checks for it in order to pass the +// JSON test suite (https://github.com/nst/JSONTestSuite/). +const important_escapable_chars = [`\b`, `\f`, `\n`, `\r`, `\t`]! +// list of valid unicode escapes aside from \u{4-hex digits} +const valid_unicode_escapes = [`b`, `f`, `n`, `r`, `t`, `\\`, `"`, `/`]! // used for transforming escapes into valid unicode (eg. n => \n) const unicode_transform_escapes = { 98: `\b` @@ -59,34 +66,6 @@ const unicode_transform_escapes = { } const exp_signs = [u8(`-`), `+`]! -@[inline] -fn is_important_escapable_char(ch u8) bool { - return match ch { - `\b`, `\f`, `\n`, `\r`, `\t` { true } - else { false } - } -} - -@[inline] -fn important_escapable_escape(ch u8) u8 { - return match ch { - `\b` { `b` } - `\f` { `f` } - `\n` { `n` } - `\r` { `r` } - `\t` { `t` } - else { ch } - } -} - -@[inline] -fn is_valid_unicode_escape(ch u8) bool { - return match ch { - `b`, `f`, `n`, `r`, `t`, `\\`, `"`, `/` { true } - else { false } - } -} - // move_pos proceeds to the next position. fn (mut s Scanner) move() { s.move_pos(true, true) @@ -151,8 +130,8 @@ fn (mut s Scanner) text_scan() Token { if ch == `"` { has_closed = true break - } else if is_important_escapable_char(ch) { - return s.error('character must be escaped with a backslash, replace with: \\${important_escapable_escape(ch).ascii_str()}') + } else if ch in important_escapable_chars { + return s.error('character must be escaped with a backslash, replace with: \\${valid_unicode_escapes[important_escapable_chars.index(ch)]}') } else if ch < 0x20 { return s.error('character must be escaped with a unicode escape, replace with: \\u${ch:04x}') } else if ch == `\\` { @@ -161,7 +140,7 @@ fn (mut s Scanner) text_scan() Token { } peek := s.text[s.pos + 1] - if is_valid_unicode_escape(peek) { + if peek in valid_unicode_escapes { chrs << unicode_transform_escapes[int(peek)] s.pos++ s.col++ -- 2.39.5