vlang / v

Simple, fast, safe, compiled language for developing maintainable software. Compiles itself in <1s with zero library dependencies. Supports automatic C => V translation. https://vlang.io
MIT License
35.65k stars 2.15k forks source link

If use `sort_with_compare`, it will report an error #20436

Closed xiusin closed 8 months ago

xiusin commented 8 months ago

Describe the bug


pub fn (mut api App) api_account_position() vweb.Result {
    mut postions := []AccountPosition{} // some items

    for i, mut postion in postions {
        postion.recid = postion.inst_id
        if postion.mgn_ratio.f64() * 100 <= 300 {
            postion.w2ui.style = 'background-color: #f3d6e3'
        } else if postion.upl_ratio.f64() * 100 < -50 {
            postion.w2ui.style = 'background-color: rgb(242, 250, 140)'
        } else {
            mut ratio := 0
            if postion.lever.f64() >= 20 {
                ratio = 50
            } else if postion.lever.f64() >= 10 {
                ratio = 30
            } else if postion.lever.f64() >= 3 {
                ratio = 20
            }
            if postion.upl_ratio.f64() * 100 > ratio {
                postion.w2ui.style = 'background-color: #C2F5B4'
            }
        }
        postions[i] = postion
    }

    request := api.query['request'] or { '{}' }
    dto := json.decode(RequestDto, request) or { RequestDto{} }
      // If you use sort_with_compare, it will report an error on the last line
    postions.sort_with_compare(fn [dto] (a &AccountPosition, b &AccountPosition) int {
        if dto.sort.len == 0 {
            return 0
        }

        mut left := ''
        mut right := ''
        left, right = match dto.sort[0].field {
            'uplRatio' {
                a.upl_ratio, b.upl_ratio
            }
            'upl' {
                a.upl, b.upl
            }
            else {
                '', ''
            }
        }
        if left == right {
            return 0
        }

        if dto.sort[0].direction == 'desc' {
            return if left < right { -1 } else { 1 }
        } else {
            return if left > right { -1 } else { 1 }
        }
    })

    return api.json(postions) <- here 
}
image

It will work properly after the annotation sorting function is implemented

image

Is it a bug or my usage error? How can I fix this problem?

Reproduction Steps

--

Expected Behavior

success compile

Current Behavior

failed

Possible Solution

No response

Additional Information/Context

No response

V version

V 0.4.3 e2334d8

Environment details (OS name and version, etc.)

V full version: V 0.4.3 f705897.e2334d8
OS: macos, macOS, 13.5.1, 22G90
Processor: 8 cpus, 64bit, little endian, Intel(R) Core(TM) i5-8279U CPU @ 2.40GHz

getwd: /Users/xiusin/projects/
vexe: /opt/v/v
vexe mtime: 2024-01-05 10:50:47

vroot: OK, value: /opt/v
VMODULES: OK, value: /Users/xiusin/.vmodules
VTMP: OK, value: /tmp/v_501

Git version: git version 2.23.0
Git vroot status: weekly.2023.50-176-ge2334d8b
.git/config present: true

CC version: Apple clang version 14.0.0 (clang-1400.0.29.202)
thirdparty/tcc status: thirdparty-macos-amd64 46662e20-dirty

[!NOTE] You can use the πŸ‘ reaction to increase the issue's priority for developers.

Please note that only the πŸ‘ reaction to the issue itself counts as a vote. Other reactions and those to comments will not be taken into account.

JalonSolov commented 8 months ago

Does it fail when compiling? Or is this just a question about what's displayed in vscode/codeium/whatever editor that is?

xiusin commented 8 months ago

@JalonSolov Compilation can also fail, reminding and editors should be consistent.

shove70 commented 8 months ago

Make sure you didn't make a typo: AccountPostion AccountPosition

pub fn (mut api App) api_account_position() vweb.Result {
    mut postions := []AccountPostion{} // some items   // <==  AccountPostion ???

    for i, mut postion in postions {
        postion.recid = postion.inst_id
        if postion.mgn_ratio.f64() * 100 <= 300 {
            postion.w2ui.style = 'background-color: #f3d6e3'
        } else if postion.upl_ratio.f64() * 100 < -50 {
            postion.w2ui.style = 'background-color: rgb(242, 250, 140)'
        } else {
            mut ratio := 0
            if postion.lever.f64() >= 20 {
                ratio = 50
            } else if postion.lever.f64() >= 10 {
                ratio = 30
            } else if postion.lever.f64() >= 3 {
                ratio = 20
            }
            if postion.upl_ratio.f64() * 100 > ratio {
                postion.w2ui.style = 'background-color: #C2F5B4'
            }
        }
        postions[i] = postion
    }

    request := api.query['request'] or { '{}' }
    dto := json.decode(RequestDto, request) or { RequestDto{} }
      // If you use sort_with_compare, it will report an error on the last line
    postions.sort_with_compare(fn [dto] (a &AccountPosition, b &AccountPosition) int {  // <==  AccountPosition ???
        if dto.sort.len == 0 {
            return 0
        }

        mut left := ''
        mut right := ''
        left, right = match dto.sort[0].field {
            'uplRatio' {
                a.upl_ratio, b.upl_ratio
            }
            'upl' {
                a.upl, b.upl
            }
            else {
                '', ''
            }
        }
        if left == right {
            return 0
        }

        if dto.sort[0].direction == 'desc' {
            return if left < right { -1 } else { 1 }
        } else {
            return if left > right { -1 } else { 1 }
        }
    })

    return api.json(postions) <- here 
}
xiusin commented 8 months ago

@shove70 The code above is written manually by me, and there are some mistakes in it.

struct AccountPosition {
    adl               string @[omitempty]
        ...
}

v run main.v

main.v:236:9: error: undefined ident: `api`
234 |     })
235 | 
236 |     return api.json(postions)
|            ~~~
237 | }
238 |
main.v:236:13: error: cannot call a method using an invalid expression
234 |     })
235 | 
236 |     return api.json(postions)
|                ~~~~~~~~~~~~~~
237 | }
238 |
main.v:236:2: error: `api.json(postions)` used as value
234 |     })
235 | 
236 |     return api.json(postions)
|     ~~~~~~~~~~~~~~~~~~~~~~~~~
237 | }
238 |
shove70 commented 8 months ago

Whether this word is wrong or not, take a look at the 2 inconsistencies I've highlighted in your code.

image

With the correct change, 'postions.sort_with_compare' does not affect compilation!

xiusin commented 8 months ago

@shove70 Oh, it might be my version issue. Let me try again. Thank you.

xiusin commented 8 months ago

@shove70 If you use the following code, you will encounter compilation errors

postions.sort_with_compare(fn [dto] (a &AccountPosition, b &AccountPosition) int  // failed
postions.sort_with_compare(fn (a &AccountPosition, b &AccountPosition) int  // success

It will report the error of the next function

  243 | 
  244 | @['/api/account/balance'; get]
  245 | pub fn (mut api App) api_account_balance() vweb.Result {
      |             ~~~
  246 |     data := api.get_account_balance() or { return api.failed(err) }
  247 |     return api.json(data)
xiusin commented 8 months ago

Currently, there seems to be a compilation issue when capturing external variables.

shove70 commented 8 months ago

Can you reproduce bugs by making your code as simple as possible? And provide the error information of V report as much as possible, thank you.

In general, when reporting a BUG, you should try to provide the simplest but complete code and pinpoint the core problem so that the developer can fix it quickly.

On the other hand, if the developer has to guess, progress can be very slow.

import vweb
import json

struct App {
    vweb.Context
}

struct AccountPosition {}
struct RequestDto {}
struct Balance {}

pub fn (mut api App) api_account_position() vweb.Result {
    mut postions := []AccountPosition{}

    request := api.query['request'] or { '{}' }
    dto := json.decode(RequestDto, request) or { RequestDto{} }

    postions.sort_with_compare(fn [dto] (a &AccountPosition, b &AccountPosition) int {
        println(dto)
        return 0
    })

    return api.json(postions)
}

@['/api/account/balance'; get]
pub fn (mut api App) api_account_balance() vweb.Result {
    data := Balance {}
    return api.json(data)
}

This code does not report any issues with the closure parameter capture, compiles correctly, and does not affect the next function.

xiusin commented 8 months ago

@shove70 The following code cannot be compiled

module main

import os
import net.http
import json
import vweb
import db.sqlite

struct Api {
    vweb.Context
    domain     string = 'https://demo.baidu.com'
    key        string @[vweb_global]
    secret     string @[vweb_global]
    passpharse string @[vweb_global]
pub mut:
    db sqlite.DB
}

@[table: 'okx_position_items']
struct OkxPositionItemModel {
    id        int    @[primary; sql: serial]
    inst_id   string @[sql_type: 'TEXT']
    inst_type string @[sql_type: 'TEXT']
    star      bool   @[sql_type: 'INTEGER']
}

pub fn Api.new(key string, secret string, passphrase string) !&Api {
    mut api := &Api{
        key: key
        secret: secret
        passpharse: passphrase
        db: sqlite.connect(':memory:')!
    }

    api.db.synchronization_mode(sqlite.SyncMode.off)!
    api.db.journal_mode(sqlite.JournalMode.memory)!

    sql api.db {
        create table OkxPositionItemModel
    }!

    return api
}

fn (mut api Api) get_account_balance() ![]AccountBalance {
    uri := '/api/v5/account/balance'
    return api.request[Response[[]AccountBalance]](uri, .get)!.data
}

fn (mut api Api) get_account_positions() ![]AccountPosition {
    uri := '/api/v5/account/positions'
    return api.request[Response[[]AccountPosition]](uri, .get)!.data
}

fn (mut api Api) request[T](path string, method http.Method, params ...map[string]string) !T {
    url_ := '${api.domain}${path}'

    mut request := http.new_request(method, url_, '')
    mut headers := http.new_header()

    if method == .post {
        headers.add(.content_type, 'application/json')
        headers.add(.accept, 'application/json')
    }

    if params.len > 0 {
        request.data = json.encode(params[0])
    }

    request.header = headers
    $if !windows {
        proxy := os.getenv_opt('https_proxy') or { os.getenv('HTTPS_PROXY') }
        if proxy.len > 0 {
            request.proxy = http.new_http_proxy(proxy)!
        }
    }

    resp := request.do()!

    if resp.status() != .ok && resp.status() != .no_content {
        err := json.decode(ErrMsg, resp.body) or {
            ErrMsg{
                msg: '${err}'
            }
        }
        return error(err.msg)
    }

    return json.decode(T, resp.body)!
}

@['/api/account/positions'; get]
pub fn (mut api Api) api_account_position() vweb.Result {
    mut postions := api.get_account_positions() or { return api.failed(err) }
    for i, mut postion in postions {
        postion.recid = postion.inst_id
        if postion.mgn_ratio.f64() * 100 <= 300 {
            postion.w2ui.style = 'background-color: #f3d6e3'
        } else if postion.upl_ratio.f64() * 100 < -50 {
            postion.w2ui.style = 'background-color: rgb(242, 250, 140)'
        } else {
            mut ratio := 0
            if postion.lever.f64() >= 20 {
                ratio = 50
            } else if postion.lever.f64() >= 10 {
                ratio = 30
            } else if postion.lever.f64() >= 3 {
                ratio = 20
            }
            if postion.upl_ratio.f64() * 100 > ratio {
                postion.w2ui.style = 'background-color: #C2F5B4'
            }
        }
        postions[i] = postion
    }

    request := api.query['request'] or { '{}' }
    dto := json.decode(RequestDto, request) or { RequestDto{} }

    postions.sort_with_compare(fn [dto] (a &AccountPosition, b &AccountPosition) int {
        if dto.sort.len == 0 {
            return 0
        }
        mut left := ''
        mut right := ''
        left, right = match dto.sort[0].field {
            'uplRatio' {
                a.upl_ratio, b.upl_ratio
            }
            'upl' {
                a.upl, b.upl
            }
            else {
                '', ''
            }
        }
        if left == right {
            return 0
        }

        if dto.sort[0].direction == 'desc' {
            return if left < right { -1 } else { 1 }
        } else {
            return if left > right { -1 } else { 1 }
        }
    })

    return api.json(postions)
}

@['/api/account/balance'; get]
pub fn (mut api Api) api_account_balance() vweb.Result {
    data := api.get_account_balance() or { return api.failed(err) }
    return api.json(data)
}

pub fn (mut api Api) failed(err IError) vweb.Result {
    return api.json(ErrMsg{ message: err.msg(), status: 'error' })
}

fn (mut api Api) serve() {
    vweb.run(&api, 8090)
}

fn main() {
    key := os.getenv('OKX_API_KEY')
    secret := os.getenv('OKX_API_SECRET')
    passphrase := os.getenv('OKX_PASSPHRASE')

    mut api := Api.new(key, secret, passphrase)!
    api.serve()
}

struct Response[T] {
    code string @[omitempty]
    msg  string @[omitempty]
    data T      @[omitempty]
}

struct AccountBalanceDetail {
    avail_bal       string @[json: 'availBal'; omitempty]
    avail_eq        string @[json: 'availEq'; omitempty]
    cash_bal        string @[json: 'cashBal'; omitempty]
    ccy             string @[omitempty]
    cross_liab      string @[json: 'crossLiab'; omitempty]
    dis_eq          string @[json: 'disEq'; omitempty]
    eq              string @[omitempty]
    eq_usd          string @[json: 'eqUsd'; omitempty]
    fixed_bal       string @[json: 'fixedBal'; omitempty]
    frozen_bal      string @[json: 'frozenBal'; omitempty]
    interest        string @[omitempty]
    iso_eq          string @[json: 'isoEq'; omitempty]
    iso_liab        string @[json: 'isoLiab'; omitempty]
    iso_upl         string @[json: 'isoUpl'; omitempty]
    liab            string @[omitempty]
    max_loan        string @[json: 'maxLoan'; omitempty]
    mgn_ratio       string @[json: 'mgnRatio'; omitempty]
    notional_lever  string @[json: 'notionalLever'; omitempty]
    ord_frozen      string @[json: 'ordFrozen'; omitempty]
    twap            string @[omitempty]
    u_time          string @[json: 'uTime'; omitempty]
    upl             string @[omitempty]
    upl_liab        string @[json: 'uplLiab'; omitempty]
    stgy_eq         string @[json: 'stgyEq'; omitempty]
    spot_in_use_amt string @[json: 'spotInUseAmt'; omitempty]
    borrow_froz     string @[json: 'borrowFroz'; omitempty]
    spot_iso_bal    string @[json: 'spotIsoBal'; omitempty]
}

struct AccountBalance {
    adj_eq       string                 @[json: 'adjEq'; omitempty]
    borrow_froz  string                 @[json: 'borrowFroz'; omitempty]
    details      []AccountBalanceDetail @[omitempty]
    imr          string                 @[omitempty]
    iso_eq       string                 @[json: 'isoEq'; omitempty]
    mgn_ratio    string                 @[json: 'mgnRatio'; omitempty]
    mmr          string                 @[omitempty]
    notional_usd string                 @[json: 'notionalUsd'; omitempty]
    ord_froz     string                 @[json: 'ordFroz'; omitempty]
    total_eq     string                 @[json: 'totalEq'; omitempty]
    u_time       string                 @[json: 'uTime'; omitempty]
}

struct ErrMsg {
    msg     string @[omitempty]
    message string @[omitempty]
    status  string @[omitempty]
}

struct W2ui {
pub mut:
    style   string
    class   string
    summary bool
    event   string
}

struct AccountPosition {
    adl               string @[omitempty]
    avail_pos         string @[json: 'availPos'; omitempty]
    avg_px            string @[json: 'avgPx'; omitempty]
    c_time            string @[json: 'cTime'; omitempty]
    ccy               string @[omitempty]
    delta_b_s         string @[json: 'deltaBS'; omitempty]
    delta_p_a         string @[json: 'deltaPA'; omitempty]
    gamma_b_s         string @[json: 'gammaBS'; omitempty]
    gamma_p_a         string @[json: 'gammaPA'; omitempty]
    imr               string @[omitempty]
    inst_id           string @[json: 'instId'; omitempty]
    inst_type         string @[json: 'instType'; omitempty]
    interest          string @[omitempty]
    idx_px            string @[json: 'idxPx'; omitempty]
    last              string @[omitempty]
    usd_px            string @[json: 'usdPx'; omitempty]
    be_px             string @[json: 'bePx'; omitempty]
    lever             string @[omitempty]
    liab              string @[omitempty]
    liab_ccy          string @[json: 'liabCcy'; omitempty]
    liq_px            string @[json: 'liqPx'; omitempty]
    mark_px           string @[json: 'markPx'; omitempty]
    margin            string @[omitempty]
    mgn_mode          string @[json: 'mgnMode'; omitempty]
    mgn_ratio         string @[json: 'mgnRatio'; omitempty]
    mmr               string @[omitempty]
    notional_usd      string @[json: 'notionalUsd'; omitempty]
    opt_val           string @[json: 'optVal'; omitempty]
    p_time            string @[json: 'pTime'; omitempty]
    pos               string @[omitempty]
    pos_ccy           string @[json: 'posCcy'; omitempty]
    pos_id            string @[json: 'posId'; omitempty]
    pos_side          string @[json: 'posSide'; omitempty]
    spot_in_use_amt   string @[json: 'spotInUseAmt'; omitempty]
    spot_in_use_ccy   string @[json: 'spotInUseCcy'; omitempty]
    theta_b_s         string @[json: 'thetaBS'; omitempty]
    theta_p_a         string @[json: 'thetaPA'; omitempty]
    trade_id          string @[json: 'tradeId'; omitempty]
    biz_ref_id        string @[json: 'bizRefId'; omitempty]
    biz_ref_type      string @[json: 'bizRefType'; omitempty]
    quote_bal         string @[json: 'quoteBal'; omitempty]
    base_bal          string @[json: 'baseBal'; omitempty]
    base_borrowed     string @[json: 'baseBorrowed'; omitempty]
    base_interest     string @[json: 'baseInterest'; omitempty]
    quote_borrowed    string @[json: 'quoteBorrowed'; omitempty]
    quote_interest    string @[json: 'quoteInterest'; omitempty]
    u_time            string @[json: 'uTime'; omitempty]
    upl               string @[omitempty]
    upl_last_px       string @[json: 'uplLastPx'; omitempty]
    upl_ratio         string @[json: 'uplRatio'; omitempty]
    upl_ratio_last_px string @[json: 'uplRatioLastPx'; omitempty]
    vega_b_s          string @[json: 'vegaBS'; omitempty]
    vega_p_a          string @[json: 'vegaPA'; omitempty]
    realized_pnl      string @[json: 'realizedPnl'; omitempty]
    pnl               string @[omitempty]
    fee               string @[omitempty]
    funding_fee       string @[json: 'fundingFee'; omitempty]
    liq_penalty       string @[json: 'liqPenalty'; omitempty]
pub mut:
    w2ui  W2ui   @[json: 'w2ui'; omitempty]
    recid string @[omitempty]
}

struct RequestDto {
pub mut:
    limit  i64
    offset i64
    sort   []struct {
        field     string
        direction string
    }
}
PS C:\Users\Administrator\Desktop\xiusin-zig-examples> v run .\main_bad.v
main_bad.v:148:9: error: undefined ident: `api`
  146 |     })
  147 |
  148 |     return api.json(postions)
      |            ~~~
  149 | }
  150 |
main_bad.v:148:13: error: cannot call a method using an invalid expression
  146 |     })
  147 |
  148 |     return api.json(postions)
      |                ~~~~~~~~~~~~~~
  149 | }
  150 |
main_bad.v:148:2: error: `api.json(postions)` used as value
  146 |     })
  147 |
  148 |     return api.json(postions)
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~
  149 | }
  150 |
shove70 commented 8 months ago
struct RequestDto {
pub mut:
    limit  i64
    offset i64
    sort   []struct {   // <-----   the field name: sort
        field     string
        direction string
    }
}

The struct's field name 'sort' causes problems, so you can temporarily change the name to compile.

Whether sort can be used as a field name needs to be discussed, but I think it should be allowed and fixed.

Thank you for your feedback.