Getting started
Learn about the v-analyzer extension in
README.
Learning V
If you're new to the V programming language,
`;
}
}
function joinPath(uri: vscode.Uri, ...pathFragment: string[]): vscode.Uri {
// Reimplementation of
// https://github.com/microsoft/vscode/blob/b251bd952b84a3bdf68dad0141c37137dac55d64/src/vs/base/common/uri.ts#L346-L357
// with Node.JS path. This is a temporary workaround for https://github.com/eclipse-theia/theia/issues/8752.
if (!uri.path) {
throw new Error("[UriError]: cannot call joinPaths on URI without path");
}
return uri.with({
path: vscode.Uri.file(path.join(uri.fsPath, ...pathFragment)).path,
});
}
function showGoWelcomePage() {
// Update this list of versions when there is a new version where we want to
// show the welcome page on update.
const showVersions: string[] = ["0.0.2"];
let vExtensionVersion = "0.0.2";
let vExtensionVersionKey = "v-analyzer.extensionVersion111";
const savedVExtensionVersion = getFromGlobalState(vExtensionVersionKey, "0.0.0");
if (
shouldShowGoWelcomePage(showVersions, vExtensionVersion, savedVExtensionVersion)
) {
vscode.commands.executeCommand("v-analyzer.showWelcome");
}
if (vExtensionVersion !== savedVExtensionVersion) {
updateGlobalState(vExtensionVersionKey, vExtensionVersion);
}
}
export function shouldShowGoWelcomePage(
showVersions: string[],
newVersion: string,
oldVersion: string,
): boolean {
if (newVersion === oldVersion) {
return false;
}
const coercedNew = semver.coerce(newVersion);
const coercedOld = semver.coerce(oldVersion);
if (!coercedNew || !coercedOld) {
return true;
}
// Both semver.coerce(0.22.0) and semver.coerce(0.22.0-rc.1) will be 0.22.0.
return (
semver.gte(coercedNew, coercedOld) && showVersions.includes(coercedNew.toString())
);
}
================================================
FILE: editors/code/syntaxes/stree.tmGrammar.json
================================================
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"scopeName": "source.stree",
"patterns": [
{
"include": "#node_type"
},
{
"include": "#node_range_index"
},
{
"include": "#token_text"
}
],
"repository": {
"node_type": {
"match": "^\\s*([a-z_][a-z_0-9]*?) (at)",
"captures": {
"1": {
"name": "entity.name.function"
},
"2": {
"name": "keyword"
}
}
},
"node_range_index": {
"match": "\\d+",
"name": "constant.numeric"
},
"token_text": {
"match": "\".+\"",
"name": "string"
}
},
"fileTypes": [
"stree"
]
}
================================================
FILE: editors/code/syntaxes/tests/accessor.vv
================================================
// SYNTAX TEST "source.v" "method accessor"
hello.world
// ^ punctuation.accessor.v
================================================
FILE: editors/code/syntaxes/tests/comma.vv
================================================
// SYNTAX TEST "source.v" "comma"
[1, 2, 3, 4]
// ^ punctuation.separator.comma.v
// ^ punctuation.separator.comma.v
// ^ punctuation.separator.comma.v
================================================
FILE: editors/code/syntaxes/tests/comment.vv
================================================
// SYNTAX TEST "source.v" "comment"
#!/usr/bin/env -S v
// ^^ punctuation.definition.comment.shebang.v
// ^^^^^^^^^^^^^^^^^ meta.shebang.v
// ^^^^^^^^^^^^^^^^^^^ comment.line.number-sign.v
================================================
FILE: editors/code/syntaxes/tests/comparison.vv
================================================
// SYNTAX TEST "source.v" "comparison"
0 == 0
//^^ keyword.operator.relation.v
// ^ constant.numeric.integer.v
================================================
FILE: editors/code/syntaxes/tests/dot.int.vv
================================================
// SYNTAX TEST "source.v"
hello.int()
// ^^^ entity.name.function.v
================================================
FILE: editors/code/syntaxes/tests/escape.vv
================================================
// SYNTAX TEST "source.v"
@type
// ^^^^^ source.v - keyword.type.v
================================================
FILE: editors/code/syntaxes/tests/hashtag.vv
================================================
// SYNTAX TEST "source.v" "hashtag"
#include
// ^^^^^^^^^^^^^^^ markup.bold.v
#define foo bar
// ^^^^^^^^^^^^ markup.bold.v
#some javascript line
// ^^^^^^^^^^^^^^^^^^ markup.bold.v
================================================
FILE: editors/code/syntaxes/tests/method.vv
================================================
// SYNTAX TEST "source.v" "method accessor"
hello.method()
// ^^^^^^ entity.name.function.v
================================================
FILE: editors/code/syntaxes/tests/numbers.vv
================================================
// SYNTAX TEST "source.v" "numbers"
_ := 1_000_000
// ^^^^^^^^^ constant.numeric.integer.v
_ := 3_122.55
// ^^^^^^^^ constant.numeric.float.v
_ := 3.14e11
// ^^^^^^^ constant.numeric.exponential.v
_ := 0xF_F
// ^^^^^ constant.numeric.hex.v
_ := 0o17_3
// ^^^^^^ constant.numeric.octal.v
_ := 0b0_11
// ^^^^^^ constant.numeric.binary.v
================================================
FILE: editors/code/syntaxes/tests/optional.vv
================================================
// SYNTAX TEST "source.v" "optional"
fn f(url string) ?string {
// ^ keyword.operator.optional.v
================================================
FILE: editors/code/syntaxes/tests/pubfn.vv
================================================
// SYNTAX TEST "source.v"
pubfn
// ^^^ - storage.modifier.v
// ^^ - keyword.fn.v
pub fn
// ^^^ storage.modifier.v
// ^^ keyword.fn.v
pub fn
// test
// ^^ comment.line.double-slash.v
================================================
FILE: editors/code/syntaxes/tests/string.vv
================================================
// SYNTAX TEST "source.v" "string"
_ := 'test'
// ^^^^ string.quoted.v
a := 1
_ := '${a}'
// ^^ variable.other.interpolated.v
_ := '\\'
// ^^ constant.character.escape.v
_ := c'test'
// ^ storage.type.string.v
_ := `r`
// ^^^ string.quoted.rune.v
_ := r'\'
// ^ storage.type.string.v
// ^^^ string.quoted.raw.v
_ := r'\'
// ^ storage.type.string.v
// ^^^ string.quoted.raw.v
================================================
FILE: editors/code/syntaxes/tests/type.vv
================================================
// SYNTAX TEST "source.v"
struct Foo {}
// ^^^ entity.name.type.v
union Foo {}
// ^^^ entity.name.type.v
pub interface Foo {}
// ^^^ storage.modifier.pub.v
// ^^^^^^^^^ storage.type.interface.v
// ^^^ entity.name.type.v
type Foo = int
// ^^^ entity.name.type.v
================================================
FILE: editors/code/syntaxes/tests/variable.vv
================================================
// SYNTAX TEST "source.v"
abc := ''
// ^^^ variable.other.assignment.v
mut abc := ''
// ^^^ variable.other.assignment.v
abc = ''
// ^^^ variable.other.assignment.v
abc, foo := '', ''
// ^^^ variable.other.assignment.v
// ^^ - variable.other.assignment.v
// ^^^ variable.other.assignment.v
variable2 := 2
// ^^^^^^^^^ variable.other.assignment.v
// ^ - constant.numeric.integer.v
================================================
FILE: editors/code/syntaxes/v.mod.tmLanguage.json
================================================
{
"scopeName": "source.v.mod",
"patterns": [
{
"include": "#module-decl"
},
{
"include": "#brackets"
}
],
"repository": {
"module-decl": {
"name": "keyword.module.v.mod",
"match": "\\bModule\\b"
},
"brackets": {
"patterns": [
{
"begin": "{",
"beginCaptures": {
"0": {
"name": "punctuation.definition.bracket.curly.begin.v.mod"
}
},
"patterns": [
{
"include": "#field"
},
{
"include": "#string"
}
],
"end": "}",
"endCaptures": {
"0": {
"name": "punctuation.definition.bracket.curly.end.v.mod"
}
}
}
]
},
"field": {
"name": "meta.definition.field.v.mod",
"match": "\\b(\\w+):"
},
"string": {
"name": "string.v.mod",
"begin": "'|\"",
"beginCaptures": {
"0": {
"name": "string.v.mod"
}
},
"end": "'|\"",
"endCaptures": {
"0": {
"name": "string.v.mod"
}
}
}
}
}
================================================
FILE: editors/code/syntaxes/v.tmLanguage.json
================================================
{
"name": "V",
"scopeName": "source.v",
"fileTypes": [
".v",
".vsh",
".vv"
],
"patterns": [
{
"include": "#comments"
},
{
"include": "#as-is"
},
{
"include": "#attributes"
},
{
"include": "#assignment"
},
{
"include": "#module-decl"
},
{
"include": "#import-decl"
},
{
"include": "#hash-decl"
},
{
"include": "#brackets"
},
{
"include": "#builtin-fix"
},
{
"include": "#escaped-fix"
},
{
"include": "#operators"
},
{
"include": "#function-exist"
},
{
"include": "#generic"
},
{
"include": "#constants"
},
{
"include": "#type"
},
{
"include": "#enum"
},
{
"include": "#interface"
},
{
"include": "#struct"
},
{
"include": "#keywords"
},
{
"include": "#storage"
},
{
"include": "#numbers"
},
{
"include": "#strings"
},
{
"include": "#types"
},
{
"include": "#punctuations"
},
{
"include": "#variable-assign"
}
],
"repository": {
"as-is": {
"begin": "\\s+(as|is)\\s+",
"beginCaptures": {
"1": {
"name": "keyword.$1.v"
}
},
"end": "([\\w.]*)",
"endCaptures": {
"1": {
"name": "entity.name.alias.v"
}
}
},
"assignment": {
"name": "meta.definition.variable.v",
"match": "\\s+((?:\\:|\\+|\\-|\\*|/|\\%|\\&|\\||\\^)?=)\\s+",
"captures": {
"1": {
"patterns": [
{
"include": "#operators"
}
]
}
}
},
"attributes": {
"name": "meta.definition.attribute.v",
"match": "^\\s*((\\[)(deprecated|unsafe|console|heap|manualfree|typedef|live|inline|flag|ref_only|direct_array_access|callconv)(\\]))",
"captures": {
"1": {
"name": "meta.function.attribute.v"
},
"2": {
"name": "punctuation.definition.begin.bracket.square.v"
},
"3": {
"name": "storage.modifier.attribute.v"
},
"4": {
"name": "punctuation.definition.end.bracket.square.v"
}
}
},
"variable-assign": {
"match": "[a-zA-Z_]\\w*(?:,\\s*[a-zA-Z_]\\w*)*(?=\\s*(?:=|:=))",
"captures": {
"0": {
"patterns": [
{
"match": "[a-zA-Z_]\\w*",
"name": "variable.other.assignment.v"
},
{
"include": "#punctuation"
}
]
}
}
},
"module-decl": {
"name": "meta.module.v",
"begin": "^\\s*(module)\\s+",
"beginCaptures": {
"1": {
"name": "keyword.module.v"
}
},
"end": "([\\w.]+)",
"endCaptures": {
"1": {
"name": "entity.name.module.v"
}
}
},
"import-decl": {
"name": "meta.import.v",
"begin": "^\\s*(import)\\s+",
"beginCaptures": {
"1": {
"name": "keyword.import.v"
}
},
"end": "([\\w.]+)",
"endCaptures": {
"1": {
"name": "entity.name.import.v"
}
}
},
"hash-decl": {
"name": "markup.bold.v",
"begin": "^\\s*(#)",
"end": "$"
},
"brackets": {
"patterns": [
{
"begin": "{",
"beginCaptures": {
"0": {
"name": "punctuation.definition.bracket.curly.begin.v"
}
},
"end": "}",
"endCaptures": {
"0": {
"name": "punctuation.definition.bracket.curly.end.v"
}
},
"patterns": [
{
"include": "$self"
}
]
},
{
"begin": "\\(",
"beginCaptures": {
"0": {
"name": "punctuation.definition.bracket.round.begin.v"
}
},
"end": "\\)",
"endCaptures": {
"0": {
"name": "punctuation.definition.bracket.round.end.v"
}
},
"patterns": [
{
"include": "$self"
}
]
},
{
"begin": "\\[",
"beginCaptures": {
"0": {
"name": "punctuation.definition.bracket.square.begin.v"
}
},
"end": "\\]",
"endCaptures": {
"0": {
"name": "punctuation.definition.bracket.square.end.v"
}
},
"patterns": [
{
"include": "$self"
}
]
}
]
},
"builtin-fix": {
"patterns": [
{
"patterns": [
{
"name": "storage.modifier.v",
"match": "(const)(?=\\s*\\()"
},
{
"name": "keyword.$1.v",
"match": "\\b(fn|type|enum|struct|union|interface|map|assert|sizeof|typeof|__offsetof)\\b(?=\\s*\\()"
}
]
},
{
"patterns": [
{
"name": "keyword.control.v",
"match": "(\\$if|\\$else)(?=\\s*\\()"
},
{
"name": "keyword.control.v",
"match": "\\b(as|in|is|or|break|continue|unsafe|match|if|else|for|go|spawn|goto|defer|return|shared|select|rlock|lock|atomic|asm)\\b(?=\\s*\\()"
}
]
},
{
"patterns": [
{
"match": "(?)",
"captures": {
"1": {
"name": "punctuation.definition.bracket.angle.begin.v"
},
"2": {
"patterns": [
{
"include": "#illegal-name"
},
{
"match": "\\w+",
"name": "entity.name.generic.v"
}
]
},
"3": {
"name": "punctuation.definition.bracket.angle.end.v"
}
}
}
]
},
"function-exist": {
"name": "meta.support.function.v",
"match": "(\\w+)((?<=[\\w\\s+])(\\[)([\\w, ]+)(\\]))?(?=\\s*\\()",
"captures": {
"0": {
"name": "meta.function.call.v"
},
"1": {
"patterns": [
{
"include": "#illegal-name"
},
{
"match": "\\w+",
"name": "entity.name.function.v"
}
]
},
"2": {
"patterns": [
{
"include": "#generic"
}
]
}
}
},
"type": {
"name": "meta.definition.type.v",
"match": "^\\s*(?:(pub)?\\s+)?(type)\\s+(\\w*)\\s+(?:\\w+\\.+)?(\\w*)",
"captures": {
"1": {
"name": "storage.modifier.$1.v"
},
"2": {
"name": "storage.type.type.v"
},
"3": {
"patterns": [
{
"include": "#illegal-name"
},
{
"include": "#types"
},
{
"name": "entity.name.type.v",
"match": "\\w+"
}
]
},
"4": {
"patterns": [
{
"include": "#illegal-name"
},
{
"include": "#types"
},
{
"name": "entity.name.type.v",
"match": "\\w+"
}
]
}
}
},
"enum": {
"name": "meta.definition.enum.v",
"match": "^\\s*(?:(pub)?\\s+)?(enum)\\s+(?:\\w+\\.)?(\\w*)",
"captures": {
"1": {
"name": "storage.modifier.$1.v"
},
"2": {
"name": "storage.type.enum.v"
},
"3": {
"name": "entity.name.enum.v"
}
}
},
"interface": {
"name": "meta.definition.interface.v",
"match": "^\\s*(?:(pub)?\\s+)?(interface)\\s+(\\w*)",
"captures": {
"1": {
"name": "storage.modifier.$1.v"
},
"2": {
"name": "storage.type.interface.v"
},
"3": {
"patterns": [
{
"include": "#illegal-name"
},
{
"name": "entity.name.type.v",
"match": "\\w+"
}
]
}
}
},
"struct": {
"patterns": [
{
"name": "meta.definition.struct.v",
"begin": "^\\s*(?:(mut|pub(?:\\s+mut)?|__global)\\s+)?(struct|union)\\s+([\\w.]+)\\s*|({)",
"beginCaptures": {
"1": {
"name": "storage.modifier.$1.v"
},
"2": {
"name": "storage.type.struct.v"
},
"3": {
"name": "entity.name.type.v"
},
"4": {
"name": "punctuation.definition.bracket.curly.begin.v"
}
},
"end": "\\s*|(})",
"endCaptures": {
"1": {
"name": "punctuation.definition.bracket.curly.end.v"
}
},
"patterns": [
{
"include": "#struct-access-modifier"
},
{
"match": "\\b(\\w+)\\s+([\\w\\[\\]\\*&.]+)(?:\\s*(=)\\s*((?:.(?=$|//|/\\*))*+))?",
"captures": {
"1": {
"name": "variable.other.property.v"
},
"2": {
"patterns": [
{
"include": "#numbers"
},
{
"include": "#brackets"
},
{
"include": "#types"
},
{
"match": "\\w+",
"name": "storage.type.other.v"
}
]
},
"3": {
"name": "keyword.operator.assignment.v"
},
"4": {
"patterns": [
{
"include": "$self"
}
]
}
}
},
{
"include": "#types"
},
{
"include": "$self"
}
]
},
{
"name": "meta.definition.struct.v",
"match": "^\\s*(?:(mut|pub(?:\\s+mut)?|__global))\\s+?(struct)\\s+(?:\\s+([\\w.]+))?",
"captures": {
"1": {
"name": "storage.modifier.$1.v"
},
"2": {
"name": "storage.type.struct.v"
},
"3": {
"name": "entity.name.struct.v"
}
}
}
]
},
"struct-access-modifier": {
"match": "(?<=\\s|^)(mut|pub(?:\\s+mut)?|__global)(:|\\b)",
"captures": {
"1": {
"name": "storage.modifier.$1.v"
},
"2": {
"name": "punctuation.separator.struct.key-value.v"
}
}
},
"punctuation": {
"patterns": [
{
"name": "punctuation.delimiter.period.dot.v",
"match": "\\."
},
{
"name": "punctuation.delimiter.comma.v",
"match": ","
},
{
"name": "punctuation.separator.key-value.colon.v",
"match": ":"
},
{
"name": "punctuation.definition.other.semicolon.v",
"match": ";"
},
{
"name": "punctuation.definition.other.questionmark.v",
"match": "\\?"
},
{
"name": "punctuation.hash.v",
"match": "#"
}
]
},
"keywords": {
"patterns": [
{
"name": "keyword.control.v",
"match": "(\\$if|\\$else|\\$for)"
},
{
"name": "keyword.control.v",
"match": "(?\\>|\\<\\<)"
},
{
"name": "keyword.operator.relation.v",
"match": "(\\=\\=|\\!\\=|\\>|\\<|\\>\\=|\\<\\=)"
},
{
"name": "keyword.operator.assignment.v",
"match": "(\\:\\=|\\=|\\+\\=|\\-\\=|\\*\\=|\\/\\=|\\%\\=|\\&\\=|\\|\\=|\\^\\=|\\~\\=|\\&\\&\\=|\\|\\|\\=|\\>\\>\\=|\\<\\<\\=)"
},
{
"name": "keyword.operator.bitwise.v",
"match": "(\\&|\\||\\^|\\~|<(?!<)|>(?!>))"
},
{
"name": "keyword.operator.logical.v",
"match": "(\\&\\&|\\|\\||\\!)"
},
{
"name": "keyword.operator.optional.v",
"match": "\\?"
}
]
},
"numbers": {
"patterns": [
{
"name": "constant.numeric.exponential.v",
"match": "(? Download from url: ${asset.browser_download_url} ...')
print('Downloading ${term.bold('v-analyzer')} archive')
os.flush()
archive_temp_dir := os.join_path(os.temp_dir(), 'v-analyzer', 'archive')
os.mkdir_all(archive_temp_dir) or {
println('Failed to create temp directory for archive: ${archive_temp_dir}')
return
}
archive_temp_path := os.join_path(archive_temp_dir, 'v-analyzer.zip')
download_file_with_progress(asset.browser_download_url, archive_temp_path)
println('${term.green('✓')} Successfully downloaded ${term.bold('v-analyzer')} archive')
println('Extracting ${term.bold('v-analyzer')} archive...')
os.mkdir_all(analyzer_bin_dir_path) or {
println('Failed to create directory: ${analyzer_bin_dir_path}')
return
}
szip.extract_zip_to_dir(archive_temp_path, analyzer_bin_dir_path) or {
println('Failed to extract archive: ${err}')
return
}
println('${term.green('✓')} Successfully extracted ${term.bold('v-analyzer')} archive')
if update {
println('${term.green('✓')} ${term.bold('v-analyzer')} successfully updated to ${term.bold(asset.tag_name)}')
}
show_info_about_binary(analyzer_bin_file_path)
if !update {
show_hint_about_path_if_needed(analyzer_bin_file_path)
}
os.mkdir_all(analyzer_sources_dir_path) or {
println('Failed to create directory: ${analyzer_sources_dir_path}')
return
}
}
fn find_latest_asset(release_type string) !ReleaseAsset {
text := http.get_text('https://api.github.com/repos/vlang/v-analyzer/releases/latest')
res := json.decode(ReleaseInfo, text) or {
errorln('Failed to decode JSON response from GitHub: ${err}')
return error('Failed to decode JSON response from GitHub: ${err}')
}
os_ := os_name() or { return error('Unsupported OS') }
arch := arch_name() or { return error('Unsupported architecture') }
mut filename := build_os_arch(os_, arch)
if release_type != '' {
filename += '-${release_type}'
}
asset := res.assets.filter(it.os_arch() == filename)[0] or {
return error('Unsupported OS or architecture')
}
return ReleaseAsset{
...asset
tag_name: res.tag_name
}
}
// download_file downloads file from the given URL to the given path.
// Returns channel that will be closed when the download is finished.
// If the download fails, the channel will be closed with false value.
fn download_file(path string, to string) chan bool {
ch := chan bool{}
spawn fn [ch, path, to] () {
http.download_file(path, to) or {
println('Failed to download file: ${err}')
ch <- false
ch.close()
return
}
ch <- true
ch.close()
}()
return ch
}
fn download_file_with_progress(path string, to string) {
ch := download_file(path, to)
for {
select {
_ := <-ch {
println('')
break
}
500 * time.millisecond {
print('.')
os.flush()
}
}
}
}
fn build_os_arch(os_name string, arch string) string {
return '${os_name}-${arch}'
}
fn update_from_sources(update bool, nightly bool) ! {
mut need_pull := true
if !already_cloned() {
clone_repository()!
need_pull = false
}
if need_pull {
println('Updating ${term.bold('v-analyzer')} sources...')
res := os.execute('git -C ${analyzer_sources_dir_path} pull')
if res.exit_code != 0 {
errorln('Failed to update sources: ${res.output}')
return
}
println('${term.green('✓')} Successfully updated ${term.bold('v-analyzer')} sources')
}
build_from_sources()!
if update {
hash := get_latest_commit_hash() or {
errorln(err.str())
return
}
updated_version := if nightly {
'nightly (${hash})'
} else {
hash
}
println('${term.green('✓')} ${term.bold('v-analyzer')} successfully updated to ${updated_version}')
}
show_info_about_binary(analyzer_bin_file_path)
return
}
fn show_info_about_binary(analyzer_bin_file_path string) {
println('Path to the binary: ${term.bold(analyzer_bin_file_path)}')
println('Size of the binary: ${term.bold(os.file_size(analyzer_bin_file_path).str())}')
bversion := os.execute('${os.quoted_path(analyzer_bin_file_path)} version')
println('Binary version: ${term.bold(bversion.output.trim_space())}')
}
fn get_latest_commit_hash() !string {
hash_res := os.execute('git -C ${analyzer_sources_dir_path} log -1 --format=%H')
if hash_res.exit_code != 0 {
return error('Failed to get hash of the latest commit: ${hash_res.output}')
}
return hash_res.output.trim_space()
}
const git_clone_options = '--filter=blob:none --recursive --shallow-submodules'
fn install_from_sources(no_interaction bool) ! {
println('${term.yellow('[WARNING]')} Currently ${term.bold('v-analyzer')} has no prebuilt binaries for your platform')
// Used primarily for VS Code extension
if !(is_github_job || no_interaction) {
mut answer := os.input('Do you want to build it from sources? (y/n) ')
if answer != 'y' {
println('')
println('Ending the update process')
warnln('${term.bold('v-analyzer')} is not installed!')
println('')
println('${term.bold('[NOTE]')} If you want to build it from sources manually, run the following commands:')
println('git clone ${git_clone_options} https://github.com/vlang/v-analyzer.git')
println('cd v-analyzer')
println('v build.vsh')
println(term.gray('# Optionally you can move the binary to the standard location:'))
println('mkdir -p ${analyzer_bin_dir_path}')
println('cp ./bin/v-analyzer ${analyzer_bin_dir_path}')
return
}
}
println('... building from source ...')
if already_cloned() {
println('... removing already cloned folder ...')
os.rmdir_all(analyzer_sources_dir_path) or {
errorln('Failed to remove directory: ${analyzer_sources_dir_path}: ${err}')
return
}
}
clone_repository()!
build_from_sources()!
show_info_about_binary(analyzer_bin_file_path)
show_hint_about_path_if_needed(analyzer_bin_file_path)
}
fn clone_repository() ! {
println('Cloning ${term.bold('v-analyzer')} repository...')
exit_code := run_command('git clone ${git_clone_options} https://github.com/vlang/v-analyzer.git ${analyzer_sources_dir_path} 2>&1') or {
errorln('Failed to clone v-analyzer repository: ${err}')
return
}
if exit_code != 0 {
errorln('Failed to clone v-analyzer repository')
return
}
println('${term.green('✓')} ${term.bold('v-analyzer')} repository cloned successfully')
}
fn build_from_sources() ! {
println('Building ${term.bold('v-analyzer')}...')
chdir(analyzer_sources_dir_path)!
install_deps_cmd := os.execute('v install')
if install_deps_cmd.exit_code != 0 {
errorln('Failed to install dependencies for ${term.bold('v-analyzer')}')
eprintln(install_deps_cmd.output)
return
}
println('${term.green('✓')} Dependencies for ${term.bold('v-analyzer')} installed successfully')
chdir(analyzer_sources_dir_path)!
exit_code := run_command('v build.vsh 1>/dev/null') or {
errorln('Failed to build ${term.bold('v-analyzer')}: ${err}')
return
}
if exit_code != 0 {
errorln('Failed to build ${term.bold('v-analyzer')}')
return
}
println('Moving ${term.bold('v-analyzer')} binary to the standard location...')
os.mkdir_all(analyzer_bin_dir_path) or {
println('Failed to create directory: ${analyzer_bin_dir_path}')
return
}
os.cp_all('${analyzer_sources_dir_path}/bin/v-analyzer' + $if windows { '.exe' } $else { '' },
analyzer_bin_dir_path, true) or {
println('Failed to copy ${term.bold('v-analyzer')} binary to ${analyzer_bin_dir_path}: ${err}')
return
}
println('${term.green('✓')} Successfully moved ${term.bold('v-analyzer')} binary to ${analyzer_bin_dir_path}')
println('${term.green('✓')} ${term.bold('v-analyzer')} built successfully')
}
fn already_cloned() bool {
if !os.exists(analyzer_sources_dir_path) {
return false
}
files := os.ls(analyzer_sources_dir_path) or { return false }
return files.len > 0
}
fn show_hint_about_path_if_needed(abs_path string) {
if !need_show_hint_about_path(abs_path) {
return
}
quoted_abs_path := '"${abs_path}"'
print('Add it to your ${term.bold('PATH')} to use it from anywhere or ')
println('specify the full path to the binary in your editor settings')
println('')
print('For example in VS Code ')
println(term.bold('settings.json:'))
println('${term.bold('{')}')
println(' ${term.yellow('"v-analyzer.serverPath"')}: ${term.green(quoted_abs_path)}')
println('${term.bold('}')}')
}
fn need_show_hint_about_path(abs_path string) bool {
dir := os.dir(abs_path)
path := os.getenv('PATH')
paths := path.split(os.path_delimiter)
return paths.filter(it == dir).len == 0
}
fn os_name() ?string {
$if macos {
return 'darwin'
}
name := os.user_os()
if name == 'unknown' {
return none
}
return name
}
fn arch_name() ?string {
$if arm64 {
return 'arm64'
}
$if amd64 || x64 {
return 'x86_64'
}
return none
}
fn run_command(cmd string) !int {
$if windows {
fixed_command := cmd
.trim_string_right('2>&1')
.trim_string_right('1>/dev/null')
res := os.execute(fixed_command)
println(res.output)
return res.exit_code
}
mut command := os.Command{
path: cmd
redirect_stdout: true
}
command.start()!
for !command.eof {
println(command.read_line())
}
command.close()!
return command.exit_code
}
pub fn errorln(msg string) {
eprintln('${term.red('[ERROR]')} ${msg}')
}
pub fn warnln(msg string) {
println('${term.yellow('[WARNING]')} ${msg}')
}
pub fn get_release_type(cmd cli.Command) string {
return cmd.flags.get_string('debug') or {
return cmd.flags.get_string('dev') or {
if cmd.flags.get_string('release') or { return '' } != '' {
return ''
}
return ''
}
}
}
fn main() {
println('Installer version: ${term.bold(installer_version)}')
mut cmd := cli.Command{
name: 'v-analyzer-installer-updated'
version: installer_version
description: 'Install and update v-analyzer'
posix_mode: true
execute: fn (cmd cli.Command) ! {
no_interaction := cmd.flags.get_bool('no-interaction') or { is_github_job }
release_type := get_release_type(cmd)
install(no_interaction, release_type)!
}
flags: [
cli.Flag{
flag: .bool
name: 'no-interaction' // Used primarily for VS Code extension, to install v-analyzer from sources
description: 'Do not ask any questions, use default values'
},
]
}
cmd.add_command(cli.Command{
name: 'up'
description: 'Update v-analyzer to the latest version'
posix_mode: true
execute: fn (cmd cli.Command) ! {
nightly := cmd.flags.get_bool('nightly') or { false }
release_type := get_release_type(cmd)
update(nightly, release_type)!
}
flags: [
cli.Flag{
flag: .bool
name: 'nightly'
description: 'Install the latest nightly build'
},
]
})
cmd.add_command(cli.Command{
name: 'check-availability'
description: 'Check if v-analyzer binary is available for the current platform (service command for editors)'
posix_mode: true
execute: fn (cmd cli.Command) ! {
release_type := get_release_type(cmd)
find_latest_asset(release_type) or {
println('Prebuild v-analyzer binary is not available for your platform')
return
}
println('${term.green('✓')} Prebuild v-analyzer binary is available for your platform')
}
})
cmd.add_command(cli.Command{
name: 'check-updates'
description: 'Checks for v-analyzer updates.'
posix_mode: true
execute: fn (cmd cli.Command) ! {
release_type := get_release_type(cmd)
check_updates(release_type)!
}
})
cmd.parse(os.args)
}
================================================
FILE: src/analyzer/Indexer.v
================================================
module analyzer
import os
import time
import loglib
import analyzer.index
// IndexingRootsStatus describes the indexing status of all roots.
pub enum IndexingRootsStatus {
all_indexed
needs_ensure_indexed // when at least one of the indexes was taken from the cache
}
// Indexer encapsulates the indexing logic and provides an interface for working with the index.
pub struct Indexer {
pub mut:
roots []&index.IndexingRoot
no_save bool
}
pub fn new_indexer() &Indexer {
return &Indexer{}
}
pub fn (mut i Indexer) set_no_save(value bool) {
i.no_save = value
for mut root in i.roots {
root.no_save = value
}
}
pub fn (i Indexer) count_roots() int {
return i.roots.len
}
pub fn (mut i Indexer) add_indexing_root(root string, kind index.IndexingRootKind, cache_dir string) {
loglib.with_fields({
'root': root
}).info('Adding indexing root')
i.roots << index.new_indexing_root(root, kind, cache_dir)
}
pub fn (mut i Indexer) index(on_start fn (root index.IndexingRoot, index int)) IndexingRootsStatus {
now := time.now()
loglib.info('Indexing ${i.roots.len} roots')
mut need_ensure_indexed := false
for index, mut indexing_root in i.roots {
on_start(*indexing_root, index + 1)
status := indexing_root.index()
if status == .from_cache {
// If at least one of the indexes was taken from the cache,
// then we need to make sure that all indexes are up to date.
need_ensure_indexed = true
}
}
loglib.with_duration(time.since(now)).info('Indexing all roots')
return if need_ensure_indexed {
.needs_ensure_indexed
} else {
.all_indexed
}
}
pub fn (mut i Indexer) ensure_indexed() {
now := time.now()
loglib.info('Ensure indexed of ${i.roots.len} roots')
for mut indexing_root in i.roots {
indexing_root.ensure_indexed()
}
loglib.with_duration(time.since(now)).info('Ensure indexed of all roots')
}
pub fn (mut i Indexer) save_indexes() ! {
if i.no_save {
return
}
for mut indexing_root in i.roots {
indexing_root.save_index() or {
loglib.with_fields({
'root': indexing_root.root
'err': err.str()
}).error('Failed to save index')
return err
}
}
}
pub fn (mut i Indexer) mark_as_dirty(filepath string, new_content string) ! {
for mut indexing_root in i.roots {
indexing_root.mark_as_dirty(filepath, new_content)!
}
}
pub fn (mut i Indexer) add_file(path string) ?index.FileIndex {
content := os.read_file(path) or {
loglib.with_fields({
'path': path
'err': err.str()
}).error('Failed to read new file')
return none
}
for mut root in i.roots {
if root.contains(path) {
return root.add_file(path, content) or {
loglib.with_fields({
'root': root.root
'path': path
'err': err.str()
}).error('Failed to add new file')
return none
}
}
}
return none
}
pub fn (mut i Indexer) rename_file(old_path string, new_path string) ?index.FileIndex {
for mut root in i.roots {
if root.contains(old_path) {
return root.rename_file(old_path, new_path) or {
loglib.with_fields({
'root': root.root
'old_path': old_path
'new_path': new_path
'err': err.str()
}).error('Failed to rename file')
return none
}
}
}
return none
}
pub fn (mut i Indexer) remove_file(path string) ?index.FileIndex {
for mut root in i.roots {
if root.contains(path) {
return root.remove_file(path) or {
loglib.with_fields({
'root': root.root
'path': path
'err': err.str()
}).error('Failed to remove file')
return none
}
}
}
return none
}
================================================
FILE: src/analyzer/IndexingManager.v
================================================
module analyzer
import analyzer.psi
pub struct IndexingManager {
pub mut:
indexer &Indexer = unsafe { nil }
stub_index psi.StubIndex
}
pub fn IndexingManager.new() &IndexingManager {
indexer := new_indexer()
return &IndexingManager{
indexer: indexer
}
}
pub fn (mut a IndexingManager) setup_empty_indexes() {
a.stub_index = psi.new_stubs_index([])
stubs_index = a.stub_index
}
pub fn (mut a IndexingManager) setup_stub_indexes() {
mut sinks := a.all_sinks()
a.stub_index = psi.new_stubs_index(sinks)
stubs_index = a.stub_index
}
pub fn (mut a IndexingManager) update_stub_indexes_from_sinks(changed_sinks []psi.StubIndexSink) {
all_sinks := a.all_sinks()
stubs_index.update_stubs_index(changed_sinks, all_sinks)
}
pub fn (mut a IndexingManager) update_stub_indexes(changed_files []&psi.PsiFile) {
all_sinks := a.all_sinks()
mut changed_sinks := []psi.StubIndexSink{cap: changed_files.len}
for root in a.indexer.roots {
for file in changed_files {
file_cache := root.index.per_file.data[file.path] or { continue }
changed_sinks << file_cache.sink
}
}
stubs_index.update_stubs_index(changed_sinks, all_sinks)
}
fn (mut a IndexingManager) all_sinks() []psi.StubIndexSink {
mut sinks := []psi.StubIndexSink{cap: a.indexer.roots.len * 30}
for root in a.indexer.roots {
sinks << root.index.per_file.get_sinks()
}
return sinks
}
================================================
FILE: src/analyzer/OpenedFile.v
================================================
module analyzer
import lsp
import utils
import analyzer.psi
pub struct OpenedFile {
pub mut:
uri lsp.DocumentUri
version int
psi_file &psi.PsiFile
}
pub fn (f OpenedFile) find_offset(pos lsp.Position) u32 {
return u32(utils.compute_offset(f.psi_file.text(), pos.line, pos.character))
}
================================================
FILE: src/analyzer/README.md
================================================
# Description
`analyzer` module describes all the functionality related to code analysis.
`server` module uses the `analyzer` module to implement all the features related to code
analysis.
================================================
FILE: src/analyzer/index/FileIndex.v
================================================
module index
import analyzer.psi
// FileIndex describes the cache of a single file.
// By splitting the cache into files, we can index files in parallel
// without the need for synchronization.
@[heap]
pub struct FileIndex {
pub mut:
kind IndexingRootKind // root where the file is located
// file_last_modified stores the time the file was last modified
//
// Thanks to it, while checking the cache, we can understand whether the
// file has been changed or not.
// If the file has been modified, then we reindex the file.
file_last_modified i64
// stub_list is a list of all stubs in the file.
// Storing stubs as a table makes it easy and compact to save them to disk and load them back.
stub_list &psi.StubList = unsafe { nil }
// sink describes the indexed stubs of the current file.
// So, for example, by the '.functions' key, you can get the stubs of all functions defined inside the current file.
// See also 'StubIndexKey'.
sink &psi.StubIndexSink = unsafe { nil }
}
pub fn (f &FileIndex) path() string {
if f.stub_list == unsafe { nil } {
return ''
}
return f.stub_list.path
}
================================================
FILE: src/analyzer/index/Index.v
================================================
module index
import time
// IndexNotFoundError is returned if the index is not found.
pub struct IndexNotFoundError {
Error
}
// NeedReindexedError is returned if the index needs to be rebuilt.
pub struct NeedReindexedError {
Error
}
// IndexVersionMismatchError is returned if the index version does not match the latest.
pub struct IndexVersionMismatchError {
Error
}
// Index encapsulates the index storage logic.
pub struct Index {
pub:
version string = '33'
pub mut:
updated_at time.Time // time of last index update
per_file PerFileIndex
}
// decode encapsulates the index decoding logic.
// If the index was corrupted and could not be decoded, an error is returned.
// If the index version does not match the latest, an `IndexVersionMismatchError` is returned.
pub fn (mut i Index) decode(data []u8) ! {
mut d := new_index_deserializer(data)
index := d.deserialize_index(i.version)!
i.per_file = index.per_file
}
// encode encapsulates the logic for encoding an index.
pub fn (i &Index) encode() []u8 {
mut s := IndexSerializer{}
s.serialize_index(i)
return s.s.data
}
================================================
FILE: src/analyzer/index/IndexDeserializer.v
================================================
module index
import analyzer.psi
import bytes
import time
pub struct IndexDeserializer {
mut:
d bytes.Deserializer
}
pub fn new_index_deserializer(data []u8) IndexDeserializer {
return IndexDeserializer{
d: bytes.new_deserializer(data)
}
}
pub fn (mut d IndexDeserializer) deserialize_index(expected_version string) !Index {
version := d.d.read_string()
if version != expected_version {
// Due to the fact that the structure of the index can change, we cannot simply
// restore the index if the version does not match, therefore, if there is a mismatch,
// we stop the decoding of the index immediately.
return IndexVersionMismatchError{}
}
updated_at_unix := d.d.read_i64()
file_indexes := d.deserialize_file_indexes()
return Index{
version: version
updated_at: time.unix(updated_at_unix)
per_file: PerFileIndex{
data: file_indexes
}
}
}
pub fn (mut d IndexDeserializer) deserialize_file_indexes() map[string]FileIndex {
len := d.d.read_int()
mut file_indexes := map[string]FileIndex{}
for _ in 0 .. len {
file_index := d.deserialize_file_index()
file_indexes[file_index.path()] = file_index
}
return file_indexes
}
pub fn (mut d IndexDeserializer) deserialize_file_index() FileIndex {
kind := unsafe { IndexingRootKind(d.d.read_u8()) }
file_last_modified := d.d.read_i64()
stub_list := d.deserialize_stub_list()
stub_index_sink := d.deserialize_stub_index_sink(stub_list, kind)
return FileIndex{
kind: kind
file_last_modified: file_last_modified
stub_list: stub_list
sink: stub_index_sink
}
}
pub fn (mut d IndexDeserializer) deserialize_stub_index_sink(stub_list &psi.StubList, kind IndexingRootKind) &psi.StubIndexSink {
len := d.d.read_int()
mut sink := &psi.StubIndexSink{
stub_list: stub_list
kind: unsafe { psi.StubIndexLocationKind(u8(kind)) }
}
for _ in 0 .. len {
key := d.d.read_int()
mut sink_map := d.deserialize_stub_index_sink_map()
sink.data[key] = sink_map.move()
}
count_imported_modules := d.d.read_int()
mut imported_modules := []string{cap: count_imported_modules}
for _ in 0 .. count_imported_modules {
imported_modules << d.d.read_string()
}
sink.imported_modules = imported_modules
return sink
}
pub fn (mut d IndexDeserializer) deserialize_stub_index_sink_map() map[string][]psi.StubId {
len := d.d.read_int()
mut sink_map := map[string][]psi.StubId{}
for _ in 0 .. len {
key := d.d.read_string()
stub_ids_len := d.d.read_int()
mut stub_ids := []psi.StubId{cap: stub_ids_len}
for _ in 0 .. stub_ids_len {
stub_ids << d.d.read_int()
}
sink_map[key] = stub_ids
}
return sink_map
}
pub fn (mut d IndexDeserializer) deserialize_stub_list() &psi.StubList {
filepath := d.d.read_string()
module_fqn := d.d.read_string()
mut child_map := map[psi.StubId][]int{}
len := d.d.read_int()
for _ in 0 .. len {
id := d.d.read_int()
children_len := d.d.read_int()
mut children := []int{cap: children_len}
for _ in 0 .. children_len {
children << d.d.read_int()
}
child_map[id] = children
}
stubs_count := d.d.read_int()
mut stubs := []&psi.StubBase{cap: stubs_count}
for _ in 0 .. stubs_count {
stubs << d.deserialize_stub()
}
mut index_map := map[psi.StubId]&psi.StubBase{}
for stub in stubs {
index_map[stub.id] = stub
}
mut list := &psi.StubList{}
list.module_fqn = module_fqn
list.path = filepath
list.index_map = index_map.move()
list.child_map = child_map.move()
for _, mut stub in list.index_map {
stub.stub_list = list
}
return list
}
pub fn (mut d IndexDeserializer) deserialize_stub() &psi.StubBase {
text := d.d.read_string()
comment := d.d.read_string()
receiver := d.d.read_string()
additional := d.d.read_string()
name := d.d.read_string()
identifier_line := d.d.read_int()
identifier_column := d.d.read_int()
identifier_end_line := d.d.read_int()
identifier_end_column := d.d.read_int()
line := d.d.read_int()
column := d.d.read_int()
end_line := d.d.read_int()
end_column := d.d.read_int()
parent_id := d.d.read_int()
stub_type := unsafe { psi.StubType(d.d.read_u8()) }
id := d.d.read_int()
return &psi.StubBase{
text: text
comment: comment
receiver: receiver
additional: additional
name: name
identifier_text_range: psi.TextRange{
line: identifier_line
column: identifier_column
end_line: identifier_end_line
end_column: identifier_end_column
}
text_range: psi.TextRange{
line: line
column: column
end_line: end_line
end_column: end_column
}
parent_id: parent_id
stub_list: unsafe { nil } // will be set later
stub_type: stub_type
id: id
}
}
================================================
FILE: src/analyzer/index/IndexSerializer.v
================================================
module index
import analyzer.psi
import bytes
pub struct IndexSerializer {
mut:
s bytes.Serializer
}
pub fn (mut s IndexSerializer) serialize_index(index Index) {
s.s.write_string(index.version)
s.s.write_i64(index.updated_at.unix())
s.serialize_file_indexes(index.per_file.data)
}
pub fn (mut s IndexSerializer) serialize_file_indexes(indexes map[string]FileIndex) {
s.s.write_int(indexes.len)
for _, index in indexes {
s.serialize_file_index(index)
}
}
pub fn (mut s IndexSerializer) serialize_file_index(index FileIndex) {
s.s.write_u8(u8(index.kind))
s.s.write_i64(index.file_last_modified)
s.serialize_stub_list(index.stub_list)
s.serialize_stub_index_sink(index.sink)
}
pub fn (mut s IndexSerializer) serialize_stub_index_sink(sink &psi.StubIndexSink) {
s.s.write_int(sink.data.len)
for key, datum in sink.data {
s.s.write_int(key)
s.serialize_stub_index_sink_map(datum)
}
s.s.write_int(sink.imported_modules.len)
for module_ in sink.imported_modules {
s.s.write_string(module_)
}
}
pub fn (mut s IndexSerializer) serialize_stub_index_sink_map(sink_map map[string][]psi.StubId) {
s.s.write_int(sink_map.len)
for key, stub_ids in sink_map {
s.s.write_string(key)
s.s.write_int(stub_ids.len)
for id in stub_ids {
s.s.write_int(id)
}
}
}
pub fn (mut s IndexSerializer) serialize_stub_list(list psi.StubList) {
s.s.write_string(list.path)
s.s.write_string(list.module_fqn)
s.s.write_int(list.child_map.len)
for id, children in list.child_map {
s.s.write_int(id)
s.s.write_int(children.len)
for child in children {
s.s.write_int(child)
}
}
// serialize stubs as array
s.s.write_int(list.index_map.len)
for stub in list.index_map.values() {
s.serialize_stub(stub)
}
}
pub fn (mut s IndexSerializer) serialize_stub(stub psi.StubBase) {
s.s.write_string(stub.text)
s.s.write_string(stub.comment)
s.s.write_string(stub.receiver)
s.s.write_string(stub.additional)
s.s.write_string(stub.name)
s.s.write_int(stub.identifier_text_range.line)
s.s.write_int(stub.identifier_text_range.column)
s.s.write_int(stub.identifier_text_range.end_line)
s.s.write_int(stub.identifier_text_range.end_column)
s.s.write_int(stub.text_range.line)
s.s.write_int(stub.text_range.column)
s.s.write_int(stub.text_range.end_line)
s.s.write_int(stub.text_range.end_column)
s.s.write_int(stub.parent_id)
s.s.write_u8(u8(stub.stub_type))
s.s.write_int(stub.id)
}
================================================
FILE: src/analyzer/index/IndexingRoot.v
================================================
module index
import time
import os
import sync
import runtime
import math
import loglib
import lsp
import crypto.md5
import analyzer.psi
import analyzer.parser
// BuiltIndexStatus describes the status of the built index.
pub enum BuiltIndexStatus {
from_cache // index was loaded from cache
from_scratch // index was built from scratch
}
// IndexingRootKind describes the type of root that is being indexed.
// Same as `StubIndexKind`.
pub enum IndexingRootKind as u8 {
standard_library
modules
stubs
workspace
}
pub fn (k IndexingRootKind) readable_name() string {
return match k {
.standard_library { 'Standard Library' }
.modules { 'Modules' }
.stubs { 'Stubs' }
.workspace { 'Workspace' }
}
}
// IndexingRoot encapsulates the logic of indexing/reindexing a particular root of the file system.
//
// Separation into separate roots is necessary in order to process the standard library and user code separately.
@[noinit]
pub struct IndexingRoot {
pub:
root string // root that is indexed
kind IndexingRootKind // type of root that is indexed
pub mut:
cache_dir string // path to the directory where the index is stored
updated_at time.Time // when the index was last updated
index Index // index itself
cache_file string // path to the file where the index is stored
need_save bool // whether the index needs to be saved
no_save bool // for tests
}
// new_indexing_root creates a new indexing root with the given root and kind.
pub fn new_indexing_root(root string, kind IndexingRootKind, cache_dir string) &IndexingRoot {
cache_file := 'v_analyzer_index_${md5.hexhash(root)}'
return &IndexingRoot{
root: root
kind: kind
cache_dir: cache_dir
cache_file: cache_file
}
}
fn (mut i IndexingRoot) cache_file() string {
return os.join_path(i.cache_dir, i.cache_file)
}
pub fn (mut i IndexingRoot) load_index() ! {
now := time.now()
if !os.exists(i.cache_file()) {
loglib.with_fields({
'root': i.root
}).info('Index not found, start indexing')
return IndexNotFoundError{}
}
data := os.read_bytes(i.cache_file()) or {
loglib.with_fields({
'file': i.cache_file()
'error': err.str()
}).error('Failed to read index')
return IndexNotFoundError{}
}
i.index.decode(data) or {
if err is IndexVersionMismatchError {
loglib.info('Index version mismatch')
} else {
loglib.with_fields({
'file': i.cache_file()
'error': err.str()
}).error('Error load index')
}
return NeedReindexedError{}
}
loglib.info('Loaded index in ${time.since(now)}')
}
pub fn (mut i IndexingRoot) save_index() ! {
if !i.need_save || i.no_save {
return
}
i.need_save = false
data := i.index.encode()
os.write_file_array(i.cache_file(), data) or {
loglib.with_fields({
'file': i.cache_file()
'error': err.str()
}).error('Failed to write analyzer index file')
return err
}
}
// need_index returns true if the file needs to be indexed.
//
// We deliberately do not index some of test files to speed up the indexing and searching process.
fn (mut _ IndexingRoot) need_index(path string) bool {
if path.ends_with('.vsh') {
return true
}
if !path.ends_with('.v') {
return false
}
return !path.contains('/tests/') && !path.contains('/slow_tests/')
&& !path.contains('/.vmodules/cache/')
&& !path.contains('/builtin/wasm/') // TODO: index this folder too
&& !path.contains('/builtin/js/') // TODO: index this folder too
&& !path.contains('/builtin/linux_bare/') // TODO: index this folder too
&& !path.ends_with('.js.v') && !path.contains('/.git/') && !path.ends_with('_test.v')
}
pub fn (mut i IndexingRoot) index() BuiltIndexStatus {
now := time.now()
loglib.with_fields({
'root': i.root
}).info('Indexing root')
if _ := i.load_index() {
loglib.with_duration(time.since(now)).info('Index loaded from cache')
return .from_cache
}
file_chan := chan string{cap: 1000}
cache_chan := chan FileIndex{cap: 1000}
spawn fn [mut i, file_chan] () {
path := i.root
os.walk(path, fn [mut i, file_chan] (path string) {
if i.need_index(path) {
file_chan <- path
}
})
file_chan.close()
}()
spawn i.spawn_indexing_workers(cache_chan, file_chan)
mut caches := []FileIndex{cap: 100}
for {
cache := <-cache_chan or { break }
caches << cache
}
for cache in caches {
i.index.per_file.data[cache.path()] = cache
}
i.updated_at = time.now()
i.need_save = true
loglib.with_duration(time.since(now)).info('Indexing finished')
return .from_scratch
}
pub fn (mut i IndexingRoot) index_file(path string, content string, mut p parser.Parser) !FileIndex {
last_modified := os.file_last_mod_unix(path)
res := p.parse_code(content)
psi_file := psi.new_psi_file(path, res.tree, content)
module_fqn := psi.module_qualified_name(psi_file, i.root)
mut cache := FileIndex{
kind: i.kind
file_last_modified: last_modified
sink: &psi.StubIndexSink{
kind: unsafe { psi.StubIndexLocationKind(u8(i.kind)) }
stub_list: unsafe { nil }
}
stub_list: unsafe { nil }
}
stub_tree := build_stub_tree(psi_file, i.root)
stub_type := psi.StubbedElementType{}
mut stub_list := stub_tree.root.stub_list
stub_list.module_fqn = module_fqn
stub_list.path = path
cache.sink.imported_modules = stub_tree.get_imported_modules()
stubs := stub_list.index_map.values()
for stub in stubs {
cache.sink.stub_id = stub.id
cache.sink.stub_list = stub.stub_list
stub_type.index_stub(stub, mut cache.sink)
}
cache.stub_list = stub_list
unsafe { res.tree.free() }
return cache
}
pub fn (mut i IndexingRoot) spawn_indexing_workers(cache_chan chan FileIndex, file_chan chan string) {
mut wg := sync.new_waitgroup()
cpus := runtime.nr_cpus()
workers := math.max(cpus - 4, 1)
wg.add(workers)
for j := 0; j < workers; j++ {
spawn fn [file_chan, mut wg, mut i, cache_chan] () {
mut p := parser.Parser.new()
defer { p.free() }
for {
filepath := <-file_chan or { break }
content := os.read_file(filepath) or {
loglib.with_fields({
'uri': lsp.document_uri_from_path(filepath).str()
'error': err.str()
}).error('Error reading file for index')
continue
}
cache_chan <- i.index_file(filepath, content, mut p) or {
loglib.with_fields({
'uri': lsp.document_uri_from_path(filepath).str()
'error': err.str()
}).error('Error indexing file')
continue
}
}
wg.done()
}()
}
wg.wait()
cache_chan.close()
}
// ensure_indexed checks the index for freshness and re-indexes files if they have changed since the last indexing.
pub fn (mut i IndexingRoot) ensure_indexed() {
now := time.now()
loglib.with_fields({
'root': i.root
}).info('Ensuring indexed root')
reindex_files_chan := chan string{cap: 1000}
cache_chan := chan FileIndex{cap: 1000}
spawn fn [reindex_files_chan, mut i] () {
for filepath, datum in i.index.per_file.data {
last_modified := os.file_last_mod_unix(filepath)
if last_modified > datum.file_last_modified {
loglib.with_fields({
'uri': lsp.document_uri_from_path(filepath).str()
}).info('File was modified, reindexing')
i.index.per_file.data.delete(filepath)
reindex_files_chan <- filepath
}
}
reindex_files_chan.close()
}()
spawn i.spawn_indexing_workers(cache_chan, reindex_files_chan)
mut caches := []FileIndex{cap: 100}
for {
cache := <-cache_chan or { break }
caches << cache
}
for cache in caches {
i.index.per_file.data[cache.path()] = cache
}
if caches.len > 0 {
i.index.updated_at = time.now()
i.need_save = true
}
loglib.with_duration(time.since(now)).info('Reindexing finished')
}
pub fn (mut i IndexingRoot) mark_as_dirty(filepath string, new_content string) ! {
if filepath !in i.index.per_file.data {
// file does not belong to this index
return
}
loglib.with_fields({
'uri': lsp.document_uri_from_path(filepath).str()
}).info('Marking document as dirty')
i.index.per_file.data.delete(filepath)
mut p := parser.Parser.new()
defer { p.free() }
res := i.index_file(filepath, new_content, mut p) or {
return error('Error indexing dirty ${filepath}: ${err}')
}
i.index.per_file.data[filepath] = res
i.index.updated_at = time.now()
i.need_save = true
i.save_index() or { return err }
loglib.with_fields({
'uri': lsp.document_uri_from_path(filepath).str()
}).info('Finished reindexing document')
}
pub fn (mut i IndexingRoot) add_file(filepath string, content string) !FileIndex {
loglib.with_fields({
'uri': lsp.document_uri_from_path(filepath).str()
}).info('Adding new document')
mut p := parser.Parser.new()
defer { p.free() }
res := i.index_file(filepath, content, mut p) or {
return error('Error indexing added ${filepath}: ${err}')
}
i.index.per_file.data[filepath] = res
i.index.updated_at = time.now()
i.need_save = true
i.save_index() or { return err }
loglib.with_fields({
'uri': lsp.document_uri_from_path(filepath).str()
}).info('Finished indexing added document')
if isnil(res.sink) {
return error('Sink of added file is nil')
}
return res
}
pub fn (mut i IndexingRoot) rename_file(old string, new string) !FileIndex {
cache := i.index.per_file.rename_file(old, new) or {
return error('cannot find file index after rename, most likely rename was failed')
}
i.need_save = true
i.save_index() or { return err }
if isnil(cache.sink) {
return error('Sink of renamed file is nil')
}
return cache
}
pub fn (mut i IndexingRoot) remove_file(path string) !FileIndex {
cache := i.index.per_file.remove_file(path) or {
return error('cannot find file index after remove, most likely remove was failed')
}
i.need_save = true
i.save_index() or { return err }
if isnil(cache.sink) {
return error('Sink of removed file is nil')
}
return cache
}
pub fn (i &IndexingRoot) contains(path string) bool {
return path.starts_with(i.root)
}
================================================
FILE: src/analyzer/index/IndexingRoot_test.v
================================================
module index
fn test_git_files_do_not_need_indexed() {
mut ir := new_indexing_root('.', .workspace, '/tmp')
assert !ir.need_index('./.git/some_file.v')
}
fn test_v_test_files_do_not_need_indexed() {
mut ir := new_indexing_root('.', .workspace, '/tmp')
assert !ir.need_index('some_file_test.v')
}
================================================
FILE: src/analyzer/index/PerFileIndex.v
================================================
module index
import analyzer.psi
// PerFileIndex describes the cache of a group of files in the index.
pub struct PerFileIndex {
pub mut:
data map[string]FileIndex
}
pub fn (p &PerFileIndex) get_sinks() []psi.StubIndexSink {
mut res := []psi.StubIndexSink{cap: p.data.len}
for _, cache in p.data {
if !isnil(cache.sink) {
res << *cache.sink
}
}
return res
}
pub fn (mut p PerFileIndex) rename_file(old string, new string) ?FileIndex {
if old == new {
return none
}
if mut cache := p.data[old] {
cache.stub_list.path = new
p.data[new] = cache
p.data.delete(old)
return cache
}
return none
}
pub fn (mut p PerFileIndex) remove_file(path string) ?FileIndex {
if mut cache := p.data[path] {
p.data.delete(path)
return cache
}
return none
}
================================================
FILE: src/analyzer/index/README.md
================================================
## Description
`index` module describes index and indexing operations.
================================================
FILE: src/analyzer/index/StubTree.v
================================================
module index
import strings
import loglib
import analyzer.psi
// StubTree represents a tree of stubs for a file.
// This tree, unlike the AST, contains the nodes whose data we want to serialize to
// speed up the startup of the server.
// Such nodes implement the `psi.StubBasedPsiElement` interface.
//
// Unlike AST, `StubTree` trees are quite small, so they can be easily saved and fully loaded
// into RAM without taking up a lot of space.
//
// With the help of `StubTree`, stub indexes are also built, which allow us to quickly find
// the necessary elements in the workspace or standard library.
// See `StubbedElementType.index_stub()`.
pub struct StubTree {
root &psi.StubBase
}
pub fn (tree &StubTree) print() {
mut sb := strings.new_builder(100)
mut p := StubTreePrinter{
sb: &sb
}
p.print_stub(tree.root, 0)
loglib.trace(sb.str())
}
pub fn (tree &StubTree) print_to(mut sb strings.Builder) {
mut p := StubTreePrinter{
sb: unsafe { &sb }
}
p.print_stub(tree.root, 0)
}
pub fn (tree &StubTree) get_imported_modules() []string {
mut result := []string{}
children := tree.root.children_stubs()
for child in children {
if child.stub_type() == .import_list {
declarations := child.children_stubs()
for declaration in declarations {
import_spec := declaration.first_child() or { continue }
import_path := import_spec.first_child() or { continue }
if import_path.stub_type() == .import_path {
result << import_path.text()
}
}
}
}
return result
}
pub fn build_stub_tree(file &psi.PsiFile, indexing_root string) &StubTree {
mut walker := psi.new_tree_walker(file.tree.root_node())
defer { walker.free() }
stub_root := psi.new_root_stub(file.path())
module_fqn := psi.module_qualified_name(file, indexing_root)
if walker.current_node() != none {
build_stub_tree_recurse(mut walker, file, stub_root, module_fqn, false)
}
return &StubTree{
root: stub_root
}
}
fn build_stub_tree_recurse(mut tw psi.TreeWalker, file &psi.PsiFile, parent &psi.StubBase, module_fqn string, build_for_all_children bool) {
node := tw.current_node() or { return }
node_type := node.type_name
stub_type := psi.node_type_to_stub_type(node_type)
is_stubbable := stub_type != .root || psi.node_is_type(node_type)
mut effective_parent := unsafe { parent }
mut should_traverse_children := true
mut pass_down_build_all := false
if is_stubbable || build_for_all_children {
psi_element := psi.create_element(node, file)
element_type := psi.StubbedElementType{}
if stub := element_type.create_stub(psi_element, parent, module_fqn) {
effective_parent = unsafe { stub }
if node_type == .qualified_type {
pass_down_build_all = true
}
}
}
if should_traverse_children {
if tw.to_first_child() {
for {
build_stub_tree_recurse(mut tw, file, effective_parent, module_fqn,
build_for_all_children || pass_down_build_all)
if !tw.next_sibling() {
break
}
}
tw.to_parent()
}
}
}
struct NodeInfo {
node psi.PsiElement
parent &psi.StubBase
}
pub fn build_stub_tree_iterative(file &psi.PsiFile, mut nodes []NodeInfo) &StubTree {
root := file.root()
stub_root := psi.new_root_stub(file.path())
nodes = nodes[..0].clone()
nodes << NodeInfo{
node: root
parent: stub_root
}
element_type := psi.StubbedElementType{}
for nodes.len > 0 {
node := nodes.pop()
this_parent_stub := node.parent
parent_stub := if node.node is psi.StubBasedPsiElement {
if stub := element_type.create_stub(node.node as psi.PsiElement, this_parent_stub, '') {
stub
} else {
this_parent_stub
}
} else {
this_parent_stub
}
for child in node.node.children() {
nodes << NodeInfo{
node: child
parent: parent_stub
}
}
}
return &StubTree{
root: stub_root
}
}
pub struct StubTreePrinter {
mut:
sb &strings.Builder
}
pub fn (mut p StubTreePrinter) print_stub(stub psi.StubElement, indent int) {
for i := 0; i < indent; i++ {
p.sb.write_string(' ')
}
p.sb.write_string(stub.stub_type().str())
text_range := stub.text_range()
p.sb.write_string(' at ')
p.sb.write_string((text_range.line + 1).str())
text := stub.text()
if text.len != 0 {
p.sb.write_string(' ')
p.sb.write_string('"')
p.sb.write_string(text)
p.sb.write_string('"')
}
p.sb.write_string('\n')
for child in stub.children_stubs() {
p.print_stub(child, indent + 1)
}
}
================================================
FILE: src/analyzer/lang/utils.v
================================================
module lang
import analyzer.psi
import analyzer.psi.types
pub fn get_zero_value_for(typ types.Type) string {
return match typ {
types.PrimitiveType {
match typ.name {
'bool' { 'false' }
'rune' { '`0`' }
'char', 'u8' { '0' }
'voidptr', 'byteptr', 'charptr', 'nil' { 'unsafe { nil }' }
'f32', 'f64' { '0.0' }
else { '0' }
}
}
types.StructType {
match typ.name() {
'string' { "''" }
else { typ.readable_name() + '{}' }
}
}
types.ArrayType {
return '[]'
}
types.FixedArrayType {
return '[]!'
}
types.MapType {
return '{}'
}
types.ChannelType {
return 'chan ${typ.inner.readable_name()}{}'
}
types.FunctionType {
return '${typ.readable_name()} {}'
}
types.AliasType {
return get_zero_value_for(typ.inner)
}
types.GenericInstantiationType {
return get_zero_value_for(typ.inner)
}
types.InterfaceType, types.PointerType {
return 'unsafe { nil }'
}
types.OptionType {
return 'none'
}
types.ResultType {
if !typ.no_inner {
return get_zero_value_for(typ.inner)
}
return "error('')"
}
else {
return '0'
}
}
}
pub fn is_same_module(context psi.PsiElement, element psi.PsiElement) bool {
context_file := context.containing_file() or { return false }
element_file := element.containing_file() or { return false }
context_module_fqn := context_file.module_fqn()
element_module_fqn := element_file.module_fqn()
return context_module_fqn == element_module_fqn
}
================================================
FILE: src/analyzer/parser/README.md
================================================
## Description
`parser` module provides way to parse V code to AST.
Input may be provided in a variety of forms (see the various `parser_*` functions)
Output is an abstract syntax tree (AST) representing the V source.
The parser accepts a larger language than is syntactically permitted by the V spec,
for simplicity, and for improved robustness in the presence of syntax errors.
================================================
FILE: src/analyzer/parser/batch.v
================================================
module parser
import sync
pub fn parse_batch_files(files []string, count_workers int) []ParseResult {
effective_workers := if files.len < count_workers {
files.len
} else {
count_workers
}
file_chan := chan string{cap: 1000}
result_chan := chan ParseResult{cap: 1000}
spawn fn [file_chan, files] () {
for file in files {
file_chan <- file
}
file_chan.close()
}()
spawn spawn_parser_workers(result_chan, file_chan, effective_workers)
mut results := []ParseResult{cap: 100}
for {
result := <-result_chan or { break }
results << result
}
return results
}
fn spawn_parser_workers(result_chan chan ParseResult, file_chan chan string, count_workers int) {
mut wg := sync.new_waitgroup()
wg.add(count_workers)
for i := 0; i < count_workers; i++ {
spawn fn [file_chan, mut wg, result_chan] () {
mut p := Parser.new()
defer { p.free() }
for {
filepath := <-file_chan or { break }
mut result := p.parse_file(filepath) or { continue }
result.path = filepath
result_chan <- result
}
wg.done()
}()
}
wg.wait()
result_chan.close()
}
================================================
FILE: src/analyzer/parser/parser.v
================================================
module parser
import tree_sitter_v.bindings
import os
// ParseResult represents the result of a parsing operation.
pub struct ParseResult {
pub:
tree &bindings.Tree[bindings.NodeType] = unsafe { nil } // Resulting tree or nil if the source could not be parsed.
source_text string // Source code.
pub mut:
path string // Path of the file that was parsed.
}
// Source represent the possible types of V source code to parse.
type Source = []u8 | string
// Parser is a wrapper around the Tree-sitter V parser.
pub struct Parser {
mut:
binding_parser &bindings.Parser[bindings.NodeType] = unsafe { nil }
}
// new creates a new Parser instance.
pub fn Parser.new() &Parser {
mut bp := bindings.new_parser[bindings.NodeType](bindings.type_factory)
bp.set_language(bindings.language)
return &Parser{
binding_parser: bp
}
}
// free frees the Tree-sitter parser.
pub fn (p &Parser) free() {
unsafe {
p.binding_parser.free()
}
}
// parse_file parses a V source file and returns the corresponding `tree_sitter.Tree` and `Rope`.
// If the file could not be read, an error is returned.
// If the file was read successfully, but could not be parsed, the result
// is a partially AST.
//
// Example:
// ```
// import parser
//
// fn main() {
// mut p := parser.Parser.new()
// res := p.parse_file('foo.v') or {
// eprintln('Error: could not parse file: ${err}')
// return
// }
// println(res.tree)
// }
// ```
pub fn (mut p Parser) parse_file(filename string) !ParseResult {
content := os.read_file(filename) or { return error('could not read file ${filename}: ${err}') }
mut res := p.parse_source(content)
res.path = filename
return res
}
// parse_source parses a V code and returns the corresponding `tree_sitter.Tree` and `Rope`.
// Unlike `parse_file`, `parse_source` uses the source directly, without reading it from a file.
// See `parser.Source` for the possible types of `source`.
//
// Example:
// ```
// import parser
//
// fn main() {
// mut p := parser.Parser.new()
// res := p.parse_source('fn main() { println("Hello, World!") }') or {
// eprintln('Error: could not parse source: ${err}')
// return
// }
// println(res.tree)
// }
// ```
pub fn (mut p Parser) parse_source(source Source) ParseResult {
code := match source {
string {
source
}
[]u8 {
source.str()
}
}
return p.parse_code(code)
}
// parse_code parses a V code and returns the corresponding `tree_sitter.Tree` and `Rope`.
// Unlike `parse_file` and `parse_source`, `parse_code` don't return an error since
// the source is always valid.
pub fn (mut p Parser) parse_code(code string) ParseResult {
tree := p.binding_parser.parse_string(source: code)
return ParseResult{
tree: tree
source_text: code
}
}
// parse_code_with_tree parses a V code and returns the corresponding `tree_sitter.Tree` and `Rope`.
// This tree can be used to reparse the code with a some changes.
// This is useful for incremental parsing.
//
// Unlike `parse_file` and `parse_source`, `parse_code` don't return an error since
// the source is always valid.
//
// Example:
// ```
// import parser
//
// fn main() {
// mut p := parser.Parser.new()
// code := 'fn main() { println("Hello, World!") }'
// res := p.parse_code_with_tree(code, unsafe { nil })
// println(res.tree)
// // some changes in code
// code2 := 'fn foo() { println("Hello, World!") }'
// res2 = p.parse_code_with_tree(code2, res.tree)
// println(res2.tree
// }
pub fn (mut p Parser) parse_code_with_tree(code string, old_tree &bindings.Tree[bindings.NodeType]) ParseResult {
raw_tree := if isnil(old_tree) { unsafe { nil } } else { old_tree.raw_tree }
tree := p.binding_parser.parse_string(source: code, tree: raw_tree)
return ParseResult{
tree: tree
source_text: code
}
}
================================================
FILE: src/analyzer/psi/Argument.v
================================================
module psi
pub struct Argument {
PsiElementImpl
}
================================================
FILE: src/analyzer/psi/ArrayCreation.v
================================================
module psi
pub struct ArrayCreation {
PsiElementImpl
is_fixed bool
}
pub fn (n ArrayCreation) expressions() []PsiElement {
children := n.children()
return children.filter(it.element_type() != .unknown)
}
================================================
FILE: src/analyzer/psi/AstNode.v
================================================
module psi
import tree_sitter_v.bindings
pub fn (node AstNode) parent_of_type(typ bindings.NodeType) ?AstNode {
mut res := node
for {
res = res.parent()?
if res.type_name == typ {
return res
}
}
return none
}
================================================
FILE: src/analyzer/psi/Attribute.v
================================================
module psi
pub struct Attribute {
PsiElementImpl
}
fn (_ &Attribute) stub() {}
pub fn (n Attribute) expressions() []src.analyzer.psi.PsiElement {
if stub := n.get_stub() {
return stub.get_children_by_type(.attribute_expression).get_psi()
}
return n.find_children_by_type(.attribute_expression)
}
pub fn (n Attribute) keys() []string {
expressions := n.expressions()
if expressions.len == 0 {
return []
}
return expressions.map(fn (expr PsiElement) string {
if expr is AttributeExpression {
return expr.value()
}
return ''
}).filter(it != '')
}
================================================
FILE: src/analyzer/psi/AttributeExpression.v
================================================
module psi
pub struct AttributeExpression {
PsiElementImpl
}
pub fn (n &AttributeExpression) value() string {
if stub := n.get_stub() {
if first_child := stub.first_child() {
return first_child.text()
}
return ''
}
if first_child := n.first_child() {
if first_child is ValueAttribute {
return first_child.value()
}
}
return ''
}
fn (_ &AttributeExpression) stub() {}
================================================
FILE: src/analyzer/psi/Attributes.v
================================================
module psi
pub struct Attributes {
PsiElementImpl
}
fn (_ &Attributes) stub() {}
pub fn (n Attributes) attributes() []PsiElement {
return n.find_children_by_type_or_stub(.attribute)
}
================================================
FILE: src/analyzer/psi/AttributesOwner.v
================================================
module psi
pub interface AttributesOwner {
attributes() []PsiElement
}
================================================
FILE: src/analyzer/psi/BinaryExpression.v
================================================
module psi
pub struct BinaryExpression {
PsiElementImpl
}
pub fn (n BinaryExpression) operator() string {
operator_element := n.find_child_by_name('operator') or { return '' }
return operator_element.get_text()
}
pub fn (n BinaryExpression) left() ?PsiElement {
return n.find_child_by_name('left')
}
pub fn (n BinaryExpression) right() ?PsiElement {
return n.find_child_by_name('right')
}
================================================
FILE: src/analyzer/psi/Block.v
================================================
module psi
pub struct Block {
PsiElementImpl
}
pub fn (b Block) last_expression() ?PsiElement {
statements := b.find_children_by_type(.simple_statement)
if statements.len == 0 {
return none
}
return statements.last().first_child()
}
pub fn (b Block) process_declarations(mut processor PsiScopeProcessor, last_parent PsiElement) bool {
statements := b.find_children_by_type(.simple_statement)
for statement in statements {
if statement.is_equal(last_parent) {
return true
}
first_child := statement.first_child() or { continue }
if first_child is VarDeclaration {
for var in first_child.vars() {
if !processor.execute(var) {
return false
}
}
}
}
return true
}
================================================
FILE: src/analyzer/psi/CallExpression.v
================================================
module psi
import analyzer.psi.types
pub struct CallExpression {
PsiElementImpl
}
fn (c &CallExpression) get_type() types.Type {
return infer_type(c)
}
fn (c &CallExpression) caller_type() types.Type {
ref_expression := c.ref_expression() or { return types.unknown_type }
if qualifier := ref_expression.qualifier() {
return infer_type(qualifier)
}
return types.unknown_type
}
pub fn (c CallExpression) expression() ?PsiElement {
return c.first_child()
}
pub fn (c CallExpression) ref_expression() ?ReferenceExpressionBase {
if selector_expr := c.find_child_by_type(.selector_expression) {
if selector_expr is ReferenceExpressionBase {
return selector_expr
}
} else if ref_expr := c.find_child_by_type(.reference_expression) {
if ref_expr is ReferenceExpressionBase {
return ref_expr
}
}
return none
}
pub fn (c CallExpression) resolve() ?PsiElement {
expr := c.ref_expression()?
if expr is ReferenceExpressionBase {
resolved := expr.resolve()?
return resolved
}
return none
}
pub fn (c CallExpression) parameter_index_on_offset(offset u32) int {
argument_list := c.find_child_by_type(.argument_list) or { return -1 }
commas := argument_list.children().filter(it.get_text() == ',')
count_commas_before := commas.filter(it.node().start_byte() < offset).len
return count_commas_before
}
pub fn (c CallExpression) arguments() []PsiElement {
argument_list := c.find_child_by_type(.argument_list) or { return [] }
arguments := argument_list.find_children_by_type(.argument)
mut exprs := []PsiElement{cap: arguments.len}
for argument in arguments {
exprs << argument.first_child() or { continue }
}
return exprs
}
pub fn (c CallExpression) is_json_decode() bool {
return c.has_child_of_type(.special_argument_list)
}
pub fn (c &CallExpression) get_json_decode_type() types.Type {
list := c.find_child_by_type(.special_argument_list) or { return types.unknown_type }
typ := list.find_child_by_type(.plain_type) or { return types.unknown_type }
mut visited := map[string]types.Type{}
return TypeInferer{}.convert_type(typ, mut visited)
}
fn (c &CallExpression) type_arguments() ?&GenericTypeArguments {
type_parameters := c.find_child_by_name('type_parameters')?
if type_parameters is GenericTypeArguments {
return type_parameters
}
return none
}
================================================
FILE: src/analyzer/psi/Comment.v
================================================
module psi
pub struct LineComment {
PsiElementImpl
}
pub struct BlockComment {
PsiElementImpl
}
================================================
FILE: src/analyzer/psi/CompileTimeIfExpression.v
================================================
module psi
pub struct CompileTimeIfExpression {
PsiElementImpl
}
pub fn (n CompileTimeIfExpression) block() ?&Block {
block := n.find_child_by_type(.block)?
if block is Block {
return block
}
return none
}
pub fn (n CompileTimeIfExpression) else_branch() ?PsiElement {
return n.find_child_by_type(.else_branch)?.last_child()
}
================================================
FILE: src/analyzer/psi/ConstantDeclaration.v
================================================
module psi
pub struct ConstantDeclaration {
PsiElementImpl
}
pub fn (n ConstantDeclaration) constants() []PsiElement {
return n.find_children_by_type(.const_definition)
}
================================================
FILE: src/analyzer/psi/ConstantDefinition.v
================================================
module psi
import analyzer.parser
import analyzer.psi.types
pub struct ConstantDefinition {
PsiElementImpl
}
pub fn (c &ConstantDefinition) is_public() bool {
modifiers := c.visibility_modifiers() or { return false }
return modifiers.is_public()
}
pub fn (c &ConstantDefinition) get_type() types.Type {
expr := c.expression() or { return types.unknown_type }
res := infer_type(expr)
if c.stub_based() {
if mut file := expr.containing_file() {
file.free()
}
}
return res
}
fn (c &ConstantDefinition) identifier() ?PsiElement {
return c.find_child_by_type(.identifier)
}
pub fn (c ConstantDefinition) identifier_text_range() TextRange {
if stub := c.get_stub() {
return stub.identifier_text_range
}
identifier := c.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (c ConstantDefinition) name() string {
if stub := c.get_stub() {
return stub.name
}
identifier := c.identifier() or { return '' }
return identifier.get_text()
}
pub fn (c ConstantDefinition) doc_comment() string {
if stub := c.get_stub() {
return stub.comment
}
parent := c.parent() or { return '' }
return extract_doc_comment(parent)
}
pub fn (c ConstantDefinition) visibility_modifiers() ?&VisibilityModifiers {
if c.stub_based() {
modifiers := c.prev_sibling_of_type(.visibility_modifiers)?
if modifiers is VisibilityModifiers {
return modifiers
}
return none
}
decl := c.parent()?
modifiers := decl.find_child_by_type_or_stub(.visibility_modifiers)?
if modifiers is VisibilityModifiers {
return modifiers
}
return none
}
pub fn (c &ConstantDefinition) expression() ?PsiElement {
if stub := c.get_stub() {
file := c.containing_file() or { return none }
// pretty hacky but it works
mut p := parser.Parser.new()
defer { p.free() }
res := p.parse_code(stub.additional)
root := res.tree.root_node()
first_child := root.first_child()?
next_first_child := first_child.first_child()?
synthetic_file := new_psi_file(file.path, res.tree, res.source_text)
return create_element(AstNode(next_first_child), synthetic_file)
}
return c.last_child()
}
pub fn (_ ConstantDefinition) stub() {}
================================================
FILE: src/analyzer/psi/EmbeddedDefinition.v
================================================
module psi
import analyzer.psi.types
pub struct EmbeddedDefinition {
PsiElementImpl
}
pub fn (n &EmbeddedDefinition) owner() ?PsiElement {
if struct_ := n.parent_of_type(.struct_declaration) {
return struct_
}
return n.parent_of_type(.interface_declaration)
}
pub fn (n &EmbeddedDefinition) identifier_text_range() TextRange {
if stub := n.get_stub() {
return stub.identifier_text_range
}
identifier := n.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (n &EmbeddedDefinition) identifier() ?PsiElement {
if qualified_type := n.find_child_by_type_or_stub(.qualified_type) {
return qualified_type.last_child_or_stub()
}
if generic_type := n.find_child_by_type_or_stub(.generic_type) {
return generic_type.first_child_or_stub()
}
if ref_expression := n.find_child_by_type_or_stub(.type_reference_expression) {
return ref_expression.first_child_or_stub()
}
return none
}
pub fn (n &EmbeddedDefinition) name() string {
if stub := n.get_stub() {
return stub.name
}
identifier := n.identifier() or { return '' }
return identifier.get_text()
}
pub fn (_ &EmbeddedDefinition) is_public() bool {
return true
}
pub fn (n &EmbeddedDefinition) get_type() types.Type {
return infer_type(n)
}
fn (_ &EmbeddedDefinition) stub() {}
================================================
FILE: src/analyzer/psi/EnumDeclaration.v
================================================
module psi
import analyzer.psi.types
pub struct EnumDeclaration {
PsiElementImpl
}
pub fn (e &EnumDeclaration) is_public() bool {
modifiers := e.visibility_modifiers() or { return false }
return modifiers.is_public()
}
pub fn (e &EnumDeclaration) get_type() types.Type {
module_fqn := if file := e.containing_file() {
stubs_index.get_module_qualified_name(file.path)
} else {
''
}
return types.new_enum_type(e.name(), module_fqn)
}
pub fn (e EnumDeclaration) identifier() ?PsiElement {
return e.find_child_by_type(.identifier)
}
pub fn (e EnumDeclaration) identifier_text_range() TextRange {
if stub := e.get_stub() {
return stub.identifier_text_range
}
identifier := e.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (e EnumDeclaration) name() string {
if stub := e.get_stub() {
return stub.name
}
identifier := e.identifier() or { return '' }
return identifier.get_text()
}
pub fn (e EnumDeclaration) doc_comment() string {
if stub := e.get_stub() {
return stub.comment
}
return extract_doc_comment(e)
}
pub fn (e EnumDeclaration) visibility_modifiers() ?&VisibilityModifiers {
modifiers := e.find_child_by_type_or_stub(.visibility_modifiers)?
if modifiers is VisibilityModifiers {
return modifiers
}
return none
}
pub fn (e EnumDeclaration) fields() []PsiElement {
if stub := e.get_stub() {
return stub.get_children_by_type(.enum_field_definition).get_psi()
}
return e.find_children_by_type(.enum_field_definition)
}
pub fn (s &EnumDeclaration) attributes() []PsiElement {
attributes := s.find_child_by_type_or_stub(.attributes) or { return [] }
if attributes is Attributes {
return attributes.attributes()
}
return []
}
pub fn (e EnumDeclaration) is_flag() bool {
attributes := e.attributes()
for attr in attributes {
if attr is Attribute {
keys := attr.keys()
return 'flag' in keys
}
}
return false
}
pub fn (_ EnumDeclaration) stub() {}
================================================
FILE: src/analyzer/psi/EnumFieldDeclaration.v
================================================
@[translated]
module psi
import analyzer.parser
import analyzer.psi.types
__global enum_fields_cache = map[string]int{}
pub struct EnumFieldDeclaration {
PsiElementImpl
}
pub fn (_ &EnumFieldDeclaration) is_public() bool {
return true
}
pub fn (f &EnumFieldDeclaration) doc_comment() string {
if stub := f.get_stub() {
return stub.comment
}
if comment := f.find_child_by_type(.line_comment) {
return comment.get_text().trim_string_left('//').trim(' \t')
}
return extract_doc_comment(f)
}
pub fn (f &EnumFieldDeclaration) identifier() ?PsiElement {
return f.find_child_by_type(.identifier)
}
pub fn (f EnumFieldDeclaration) identifier_text_range() TextRange {
if stub := f.get_stub() {
return stub.identifier_text_range
}
identifier := f.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (f &EnumFieldDeclaration) name() string {
if stub := f.get_stub() {
return stub.name
}
identifier := f.identifier() or { return '' }
return identifier.get_text()
}
pub fn (f &EnumFieldDeclaration) get_type() types.Type {
owner := f.owner() or { return types.unknown_type }
return owner.get_type()
}
pub fn (f &EnumFieldDeclaration) fingerprint() string {
owner := f.owner() or { return '' }
file := f.containing_file() or { return '' }
return '${file.path}:${f.node.start_point()}${owner.name()}.${f.name()}'
}
pub fn (f &EnumFieldDeclaration) value() ?PsiElement {
if stub := f.get_stub() {
if stub.additional.len == 0 {
return none
}
file := f.containing_file() or { return none }
mut p := parser.Parser.new()
defer { p.free() }
res := p.parse_code(stub.additional)
root := res.tree.root_node()
first_child := root.first_child()?
next_first_child := first_child.first_child()?
synthetic_file := new_psi_file(file.path, res.tree, res.source_text)
return create_element(next_first_child, synthetic_file)
}
return f.find_child_by_name('value')
}
pub fn (f &EnumFieldDeclaration) owner() ?&EnumDeclaration {
if stub := f.get_stub() {
if parent := stub.parent_of_type(.enum_declaration) {
if is_valid_stub(parent) {
if psi := parent.get_psi() {
if psi is EnumDeclaration {
return psi
}
}
}
}
return none
}
psi := f.parent_of_type(.enum_declaration)?
if psi is EnumDeclaration {
return psi
}
return none
}
pub fn (f &EnumFieldDeclaration) value_presentation(with_dec_value bool) string {
owner := f.owner() or { return '' }
count_fields := owner.fields().len
is_flag := owner.is_flag()
value := f.get_value()
if is_flag {
mut bin := '0b' + f.add_padding('${value:b}', count_fields)
if with_dec_value {
bin += ' (${value})'
}
return bin
}
return value.str()
}
pub fn (f &EnumFieldDeclaration) get_value() i64 {
fingerprint := f.fingerprint()
if value := enum_fields_cache[fingerprint] {
return value
}
value := f.get_value_impl()
enum_fields_cache[fingerprint] = value
return value
}
fn (f &EnumFieldDeclaration) get_value_impl() i64 {
owner := f.owner() or { return 0 }
is_flag := owner.is_flag()
if !is_flag {
if value := f.value() {
val := f.calculate_value(value)
if f.stub_based() {
if mut file := value.containing_file() {
file.free()
}
}
if val != none {
return val
}
}
}
prev_field := f.prev_sibling_of_type(.enum_field_definition) or {
return if is_flag { 1 } else { 0 }
}
if prev_field is EnumFieldDeclaration {
prev_value := prev_field.get_value()
if is_flag {
return prev_value * 2
}
return prev_value + 1
}
return 0
}
fn (f &EnumFieldDeclaration) calculate_value(value PsiElement) ?i64 {
if value is Literal {
return value.value()
}
if value is UnaryExpression {
if value.operator() == '-' {
if expr := value.expression() {
if val := f.calculate_value(expr) {
return -val
}
}
}
}
if value is BinaryExpression {
operator := value.operator()
if operator == '<<' {
left := value.left()?
right := value.right()?
left_val := f.calculate_value(left)?
right_val := f.calculate_value(right)?
if left_val < 0 {
// -1 << 1 is prohibited by V
return none
}
return i64(u64(left_val) << right_val)
}
}
return none
}
fn (_ EnumFieldDeclaration) add_padding(value string, size int) string {
if size == -1 {
return value
}
len := value.len
if len >= size {
return value
}
return '0'.repeat(size - len) + value
}
pub fn (_ EnumFieldDeclaration) stub() {}
================================================
FILE: src/analyzer/psi/FieldDeclaration.v
================================================
module psi
import analyzer.psi.types
pub struct FieldDeclaration {
PsiElementImpl
}
pub fn (f &FieldDeclaration) is_embedded_definition() bool {
return f.has_child_of_type(.embedded_definition)
}
pub fn (f &FieldDeclaration) is_public() bool {
if owner := f.owner() {
if owner is InterfaceDeclaration {
return true // all interface fields are public by default
}
}
_, is_pub := f.is_mutable_public()
return is_pub
}
pub fn (f &FieldDeclaration) doc_comment() string {
if stub := f.get_stub() {
return stub.comment
}
if comment := f.find_child_by_type(.line_comment) {
return comment.get_text().trim_string_left('//').trim(' \t')
}
return extract_doc_comment(f)
}
pub fn (f &FieldDeclaration) identifier() ?PsiElement {
return f.find_child_by_type(.identifier)
}
pub fn (f FieldDeclaration) identifier_text_range() TextRange {
if stub := f.get_stub() {
return stub.identifier_text_range
}
identifier := f.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (f &FieldDeclaration) name() string {
if stub := f.get_stub() {
return stub.name
}
identifier := f.identifier() or { return '' }
return identifier.get_text()
}
pub fn (f &FieldDeclaration) get_type() types.Type {
return infer_type(f)
}
pub fn (f &FieldDeclaration) owner() ?PsiElement {
if struct_ := f.parent_of_type(.struct_declaration) {
return struct_
}
return f.parent_of_type(.interface_declaration)
}
pub fn (f &FieldDeclaration) scope() ?&StructFieldScope {
element := f.sibling_of_type_backward(.struct_field_scope)?
if element is StructFieldScope {
return element
}
return none
}
pub fn (f &FieldDeclaration) is_mutable_public() (bool, bool) {
scope := f.scope() or { return false, false }
return scope.is_mutable_public()
}
pub fn (_ FieldDeclaration) stub() {}
================================================
FILE: src/analyzer/psi/FieldName.v
================================================
module psi
pub struct FieldName {
PsiElementImpl
}
pub fn (n FieldName) reference_expression() ?&ReferenceExpression {
first_child := n.first_child()?
if first_child is ReferenceExpression {
return first_child
}
return none
}
================================================
FILE: src/analyzer/psi/ForStatement.v
================================================
module psi
pub struct ForStatement {
PsiElementImpl
}
pub fn (n ForStatement) var_definitions() []PsiElement {
if range_clause := n.find_child_by_type(.range_clause) {
var_definition_list := range_clause.find_child_by_type(.var_definition_list) or {
return []
}
return var_definition_list.find_children_by_type(.var_definition)
}
if for_clause := n.find_child_by_type(.for_clause) {
initializer := for_clause.first_child() or { return [] }
if initializer.element_type() == .simple_statement {
decl := initializer.first_child() or { return [] }
list := decl.first_child() or { return [] }
definition := list.first_child() or { return [] }
return [definition]
}
}
return []
}
================================================
FILE: src/analyzer/psi/FunctionLiteral.v
================================================
module psi
pub struct FunctionLiteral {
PsiElementImpl
}
pub fn (f FunctionLiteral) signature() ?&Signature {
signature := f.find_child_by_type_or_stub(.signature)?
if signature is Signature {
return signature
}
return none
}
================================================
FILE: src/analyzer/psi/FunctionOrMethodDeclaration.v
================================================
module psi
import analyzer.psi.types
pub struct FunctionOrMethodDeclaration {
PsiElementImpl
}
pub fn (f &FunctionOrMethodDeclaration) generic_parameters() ?&GenericParameters {
generic_parameters := f.find_child_by_type_or_stub(.generic_parameters)?
if generic_parameters is GenericParameters {
return generic_parameters
}
return none
}
pub fn (f &FunctionOrMethodDeclaration) is_public() bool {
modifiers := f.visibility_modifiers() or { return false }
return modifiers.is_public()
}
fn (f &FunctionOrMethodDeclaration) get_type() types.Type {
signature := f.signature() or { return types.unknown_type }
return signature.get_type()
}
pub fn (f FunctionOrMethodDeclaration) identifier() ?PsiElement {
return f.find_child_by_type(.identifier)
}
pub fn (f FunctionOrMethodDeclaration) identifier_text_range() TextRange {
if stub := f.get_stub() {
return stub.identifier_text_range
}
identifier := f.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (f FunctionOrMethodDeclaration) signature() ?&Signature {
signature := f.find_child_by_type_or_stub(.signature)?
if signature is Signature {
return signature
}
return none
}
pub fn (f FunctionOrMethodDeclaration) name() string {
if stub := f.get_stub() {
return stub.name
}
identifier := f.identifier() or { return '' }
return identifier.get_text()
}
pub fn (f FunctionOrMethodDeclaration) doc_comment() string {
if stub := f.get_stub() {
return stub.comment
}
return extract_doc_comment(f)
}
pub fn (f FunctionOrMethodDeclaration) is_method() bool {
return f.has_child_of_type(.receiver)
}
pub fn (f FunctionOrMethodDeclaration) receiver_type() types.Type {
receiver := f.receiver() or { return types.unknown_type }
return receiver.get_type()
}
pub fn (f FunctionOrMethodDeclaration) receiver() ?&Receiver {
element := f.find_child_by_type_or_stub(.receiver)?
if element is Receiver {
return element
}
return none
}
pub fn (f FunctionOrMethodDeclaration) visibility_modifiers() ?&VisibilityModifiers {
modifiers := f.find_child_by_type_or_stub(.visibility_modifiers)?
if modifiers is VisibilityModifiers {
return modifiers
}
return none
}
pub fn (f FunctionOrMethodDeclaration) owner() ?PsiElement {
receiver := f.receiver()?
typ := receiver.get_type()
unwrapped := types.unwrap_generic_instantiation_type(types.unwrap_pointer_type(typ))
if unwrapped is types.StructType {
return *find_struct(unwrapped.qualified_name())?
}
if unwrapped is types.AliasType {
return *find_alias(unwrapped.qualified_name())?
}
return none
}
pub fn (f FunctionOrMethodDeclaration) fingerprint() string {
signature := f.signature() or { return '' }
count_params := signature.parameters().len
has_return_type := if _ := signature.result() { true } else { false }
return '${f.name()}:${count_params}:${has_return_type}'
}
pub fn (_ FunctionOrMethodDeclaration) stub() {}
================================================
FILE: src/analyzer/psi/GenericArgumentsOwner.v
================================================
module psi
pub interface GenericArgumentsOwner {
type_arguments() ?&GenericTypeArguments
}
================================================
FILE: src/analyzer/psi/GenericParameter.v
================================================
module psi
@[heap]
pub struct GenericParameter {
PsiElementImpl
}
pub fn (n &GenericParameter) identifier_text_range() TextRange {
if stub := n.get_stub() {
return stub.identifier_text_range
}
identifier := n.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (n &GenericParameter) identifier() ?PsiElement {
return n.find_child_by_type(.identifier)
}
pub fn (n &GenericParameter) name() string {
if stub := n.get_stub() {
return stub.name
}
identifier := n.identifier() or { return '' }
return identifier.get_text()
}
pub fn (_ &GenericParameter) is_public() bool {
return true
}
fn (_ &GenericParameter) stub() {}
================================================
FILE: src/analyzer/psi/GenericParameters.v
================================================
module psi
import strings
pub struct GenericParameters {
PsiElementImpl
}
fn (_ &GenericParameters) stub() {}
pub fn (n &GenericParameters) parameters() []PsiElement {
return n.find_children_by_type_or_stub(.generic_parameter)
}
pub fn (n &GenericParameters) text_presentation() string {
parameters := n.parameters()
if parameters.len == 0 {
return ''
}
mut sb := strings.new_builder(5)
sb.write_string('[')
for index, parameter in parameters {
if parameter is PsiNamedElement {
sb.write_string(parameter.name())
}
if index < parameters.len - 1 {
sb.write_string(', ')
}
}
sb.write_string(']')
return sb.str()
}
pub fn (n &GenericParameters) parameter_names() []string {
parameters := n.parameters()
if parameters.len == 0 {
return []
}
mut result := []string{cap: parameters.len}
for parameter in parameters {
if parameter is PsiNamedElement {
result << parameter.name()
}
}
return result
}
================================================
FILE: src/analyzer/psi/GenericParametersOwner.v
================================================
module psi
pub interface GenericParametersOwner {
generic_parameters() ?&GenericParameters
}
================================================
FILE: src/analyzer/psi/GenericTypeArguments.v
================================================
module psi
import analyzer.psi.types
pub struct GenericTypeArguments {
PsiElementImpl
}
pub fn (n GenericTypeArguments) types() []types.Type {
plain_types := n.find_children_by_type(.plain_type)
inferer := TypeInferer{}
mut visited := map[string]types.Type{}
mut arg_types := []types.Type{cap: plain_types.len}
for plain_type in plain_types {
arg_types << inferer.convert_type(plain_type, mut visited)
}
return arg_types
}
================================================
FILE: src/analyzer/psi/GenericTypeInferer.v
================================================
module psi
import analyzer.psi.types
import math
struct GenericTypeInferer {}
fn (g &GenericTypeInferer) infer_generic_call(arg_owner GenericArgumentsOwner, params_owner GenericParametersOwner,
result_type types.Type) types.Type {
generic_ts_map := g.infer_generic_ts_map(arg_owner, params_owner)
return result_type.substitute_generics(generic_ts_map)
}
fn (g &GenericTypeInferer) infer_generic_fetch(resolved PsiElement, expr SelectorExpression, generic_type types.Type) types.Type {
if resolved !is FieldDeclaration {
return generic_type
}
qualifier := expr.qualifier() or { return generic_type }
qualifier_type := infer_type(qualifier)
instantiation := g.extract_instantiation(qualifier_type) or { return generic_type }
qualifier_specialization_map := g.infer_qualifier_generic_ts_map(instantiation)
if qualifier_specialization_map.len == 0 {
return generic_type
}
return generic_type.substitute_generics(qualifier_specialization_map)
}
fn (g &GenericTypeInferer) infer_generic_ts_map(arg_owner GenericArgumentsOwner, params_owner GenericParametersOwner) map[string]types.Type {
if arg_owner is CallExpression {
if ref_expression := arg_owner.ref_expression() {
if qualifier := ref_expression.qualifier() {
qualifier_type := infer_type(qualifier)
if instantiation := g.extract_instantiation(qualifier_type) {
qualifier_specialization_map := g.infer_qualifier_generic_ts_map(instantiation)
return g.infer_simple_generic_ts_map(arg_owner, params_owner,
qualifier_specialization_map)
}
}
}
}
return g.infer_simple_generic_ts_map(arg_owner, params_owner, map[string]types.Type{})
}
fn (g &GenericTypeInferer) infer_simple_generic_ts_map(arg_owner GenericArgumentsOwner, params_owner GenericParametersOwner,
additional map[string]types.Type) map[string]types.Type {
generic_parameters := g.generics_parameter_names(params_owner)
// No data for inference, call is not generic.
if generic_parameters.len == 0 && additional.len == 0 {
return map[string]types.Type{}
}
// foo[int, string]()
// ^^^^^^^^^^^^^ explicit generic arguments
// Array[string]{}
// ^^^^^^^^ explicit generic arguments
if generic_arguments := arg_owner.type_arguments() {
generic_arguments_types := generic_arguments.types()
if generic_arguments_types.len > 0 {
// T: int
// U: string
mut mapping := map[string]types.Type{}
for i in 0 .. math.min(generic_parameters.len, generic_arguments_types.len) {
mapping[generic_parameters[i]] = generic_arguments_types[i]
}
for key, value in additional {
mapping[key] = value
}
return mapping
}
}
if arg_owner is CallExpression {
if params_owner is SignatureOwner {
arguments := arg_owner.arguments()
signature := params_owner.signature() or { return map[string]types.Type{} }
parameters := signature.parameters()
arguments_types := arguments.map(infer_type(it))
parameters_types := parameters.map(infer_type(it))
mut reifier := GenericTypeReifier{}
reifier.reify_generic_ts(parameters_types, arguments_types)
mut mapping := map[string]types.Type{}
for key, type_ in reifier.implicit_specialization_types_map {
mapping[key] = type_
}
for key, value in additional {
mapping[key] = value
}
return mapping
}
}
return additional
}
fn (g &GenericTypeInferer) infer_qualifier_generic_ts_map(typ types.GenericInstantiationType) map[string]types.Type {
qualifier_generic_ts := g.extract_instantiation_ts(typ)
if qualifier_generic_ts.len == 0 {
return map[string]types.Type{}
}
specialization := typ.specialization
mut mapping := map[string]types.Type{}
for i in 0 .. math.min(qualifier_generic_ts.len, specialization.len) {
mapping[qualifier_generic_ts[i]] = specialization[i]
}
return mapping
}
fn (g &GenericTypeInferer) extract_instantiation(typ types.Type) ?&types.GenericInstantiationType {
if typ is types.GenericInstantiationType {
return typ
}
if typ is types.AliasType {
return g.extract_instantiation(typ.inner)
}
if typ is types.PointerType {
return g.extract_instantiation(typ.inner)
}
if typ is types.OptionType {
return g.extract_instantiation(typ.inner)
}
if typ is types.ResultType {
return g.extract_instantiation(typ.inner)
}
return none
}
pub fn (_ &GenericTypeInferer) extract_instantiation_ts(typ types.GenericInstantiationType) []string {
inner_name := typ.inner.qualified_name()
elements := stubs_index.get_any_elements_by_name(inner_name)
if elements.len == 0 {
return []
}
resolved := elements.first()
if resolved is GenericParametersOwner {
if generic_parameters := resolved.generic_parameters() {
return generic_parameters.parameter_names()
}
}
return []
}
fn (_ &GenericTypeInferer) generics_parameter_names(params_owner GenericParametersOwner) []string {
params := params_owner.generic_parameters() or { return [] }
return params.parameter_names()
}
================================================
FILE: src/analyzer/psi/GenericTypeReifier.v
================================================
module psi
import analyzer.psi.types
import math
pub struct GenericTypeReifier {
mut:
implicit_specialization_types_map map[string]types.Type
}
pub fn (mut g GenericTypeReifier) reify_generic_ts(param_types []types.Type, arg_types []types.Type) {
for i in 0 .. math.min(param_types.len, arg_types.len) {
g.reify_generic_t(param_types[i], arg_types[i])
}
}
fn (mut g GenericTypeReifier) reify_generic_t(param_type types.Type, arg_type types.Type) {
if param_type is types.GenericType {
g.implicit_specialization_types_map[param_type.name()] = arg_type
}
if param_type is types.GenericInstantiationType {
if arg_type is types.GenericInstantiationType {
for i in 0 .. math.min(param_type.specialization.len, arg_type.specialization.len) {
g.reify_generic_t(param_type.specialization[i], arg_type.specialization[i])
}
}
}
if param_type is types.MapType {
if arg_type is types.MapType {
g.reify_generic_t(param_type.key, arg_type.key)
g.reify_generic_t(param_type.value, arg_type.value)
}
}
if param_type is types.ResultType {
if arg_type is types.ResultType {
g.reify_generic_t(param_type.inner, arg_type.inner)
} else {
g.reify_generic_t(param_type.inner, arg_type)
}
}
if param_type is types.OptionType {
if arg_type is types.OptionType {
g.reify_generic_t(param_type.inner, arg_type.inner)
} else {
g.reify_generic_t(param_type.inner, arg_type)
}
}
if param_type is types.PointerType {
if arg_type is types.PointerType {
g.reify_generic_t(param_type.inner, arg_type.inner)
} else {
g.reify_generic_t(param_type.inner, arg_type)
}
}
if param_type is types.ChannelType {
if arg_type is types.ChannelType {
g.reify_generic_t(param_type.inner, arg_type.inner)
} else {
g.reify_generic_t(param_type.inner, arg_type)
}
}
if param_type is types.ArrayType {
if arg_type is types.ArrayType {
g.reify_generic_t(param_type.inner, arg_type.inner)
} else {
g.reify_generic_t(param_type.inner, arg_type)
}
}
if param_type is types.FixedArrayType {
if arg_type is types.FixedArrayType {
g.reify_generic_t(param_type.inner, arg_type.inner)
} else {
g.reify_generic_t(param_type.inner, arg_type)
}
}
if param_type is types.FunctionType {
if arg_type is types.FunctionType {
for i in 0 .. math.min(param_type.params.len, arg_type.params.len) {
g.reify_generic_t(param_type.params[i], arg_type.params[i])
}
g.reify_generic_t(param_type.result, arg_type.result)
}
}
}
================================================
FILE: src/analyzer/psi/GlobalVarDefinition.v
================================================
module psi
import analyzer.psi.types
pub struct GlobalVarDefinition {
PsiElementImpl
}
fn (_ &GlobalVarDefinition) stub() {}
pub fn (_ &GlobalVarDefinition) is_public() bool {
return true
}
pub fn (n &GlobalVarDefinition) identifier() ?PsiElement {
if node := n.find_child_by_name('name') {
return node
}
return n.find_child_by_type(.identifier)
}
pub fn (n &GlobalVarDefinition) identifier_text_range() TextRange {
if stub := n.get_stub() {
return stub.identifier_text_range
}
identifier := n.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (n &GlobalVarDefinition) name() string {
if stub := n.get_stub() {
return stub.name
}
identifier := n.identifier() or { return '' }
return identifier.get_text()
}
pub fn (n &GlobalVarDefinition) get_type() types.Type {
return infer_type(n)
}
================================================
FILE: src/analyzer/psi/Identifier.v
================================================
module psi
pub struct Identifier {
PsiElementImpl
}
pub fn (i Identifier) value() string {
return i.get_text()
}
pub fn (i Identifier) name() string {
return i.get_text()
}
pub fn (i Identifier) qualifier() ?PsiElement {
parent := i.parent()?
if parent is SelectorExpression {
left := parent.left()?
if left.is_equal(i) {
return none
}
return left
}
return none
}
pub fn (i Identifier) reference() PsiReference {
file := i.containing_file()
return new_reference(file, i, false)
}
pub fn (i Identifier) resolve() ?PsiElement {
if parent := i.parent() {
if parent is PsiNamedElement {
if ident := parent.identifier() {
if ident.is_equal(i) {
return parent as PsiElement
}
}
}
}
return i.reference().resolve()
}
================================================
FILE: src/analyzer/psi/IfExpression.v
================================================
module psi
pub struct IfExpression {
PsiElementImpl
}
pub fn (n IfExpression) var_definition() ?&VarDefinition {
decl := n.find_child_by_type(.var_declaration)?
list := decl.first_child()?
expr := list.first_child()?
if expr is VarDefinition {
return expr
}
if expr is MutExpression {
var := expr.last_child()?
if var is VarDefinition {
return var
}
}
return none
}
pub fn (n IfExpression) block() ?&Block {
block := n.find_child_by_type(.block)?
return if block is Block { block } else { none }
}
pub fn (n IfExpression) else_branch() ?PsiElement {
return n.find_child_by_type(.else_branch)?.last_child()
}
================================================
FILE: src/analyzer/psi/ImportAlias.v
================================================
module psi
pub struct ImportAlias {
PsiElementImpl
}
fn (n &ImportAlias) stub() {}
================================================
FILE: src/analyzer/psi/ImportDeclaration.v
================================================
module psi
pub struct ImportDeclaration {
PsiElementImpl
}
pub fn (n &ImportDeclaration) spec() ?&ImportSpec {
spec := n.find_child_by_type(.import_spec)?
if spec is ImportSpec {
return spec
}
return none
}
fn (n &ImportDeclaration) stub() {}
================================================
FILE: src/analyzer/psi/ImportList.v
================================================
module psi
pub struct ImportList {
PsiElementImpl
}
fn (n &ImportList) stub() {}
================================================
FILE: src/analyzer/psi/ImportName.v
================================================
module psi
pub struct ImportName {
PsiElementImpl
}
fn (n &ImportName) stub() {}
================================================
FILE: src/analyzer/psi/ImportPath.v
================================================
module psi
pub struct ImportPath {
PsiElementImpl
}
fn (n &ImportPath) stub() {}
================================================
FILE: src/analyzer/psi/ImportSpec.v
================================================
module psi
pub struct ImportSpec {
PsiElementImpl
}
fn (_ &ImportSpec) stub() {}
pub fn (_ &ImportSpec) is_public() bool {
return true
}
fn (n &ImportSpec) identifier_text_range() TextRange {
identifier := n.identifier() or { return TextRange{} }
return identifier.text_range()
}
fn (n &ImportSpec) identifier() ?PsiElement {
last_part := n.last_part()?
if last_part is ImportName {
return last_part
}
return none
}
fn (n &ImportSpec) name() string {
return n.import_name()
}
pub fn (n &ImportSpec) qualified_name() string {
path := n.path() or { return '' }
return path.get_text()
}
pub fn (n ImportSpec) alias() ?PsiElement {
return n.find_child_by_type_or_stub(.import_alias)
}
pub fn (n ImportSpec) path() ?PsiElement {
return n.find_child_by_type_or_stub(.import_path)
}
pub fn (n ImportSpec) last_part() ?PsiElement {
path := n.path()?
return path.last_child_or_stub()
}
pub fn (n ImportSpec) import_name() string {
if alias := n.alias() {
if identifier := alias.last_child_or_stub() {
return identifier.get_text()
}
}
if last_part := n.last_part() {
return last_part.get_text()
}
return ''
}
pub fn (n ImportSpec) alias_name() string {
if alias := n.alias() {
if identifier := alias.last_child() {
return identifier.get_text()
}
}
return ''
}
pub fn (n ImportSpec) resolve_directory() string {
fqn := n.qualified_name()
if fqn == '' {
return ''
}
return stubs_index.get_module_root(fqn)
}
pub fn (n &ImportSpec) selective_list() ?&SelectiveImportList {
list := n.find_child_by_type_or_stub(.selective_import_list)?
if list is SelectiveImportList {
return list
}
return none
}
================================================
FILE: src/analyzer/psi/IndexExpression.v
================================================
module psi
import analyzer.psi.types
pub struct IndexExpression {
PsiElementImpl
}
fn (c &IndexExpression) get_type() types.Type {
return infer_type(c)
}
pub fn (c IndexExpression) expression() ?PsiElement {
return c.first_child()
}
================================================
FILE: src/analyzer/psi/InterfaceDeclaration.v
================================================
module psi
import analyzer.psi.types
pub struct InterfaceDeclaration {
PsiElementImpl
}
pub fn (s &InterfaceDeclaration) generic_parameters() ?&GenericParameters {
generic_parameters := s.find_child_by_type_or_stub(.generic_parameters)?
if generic_parameters is GenericParameters {
return generic_parameters
}
return none
}
pub fn (s &InterfaceDeclaration) is_public() bool {
modifiers := s.visibility_modifiers() or { return false }
return modifiers.is_public()
}
pub fn (s &InterfaceDeclaration) module_name() string {
file := s.containing_file() or { return '' }
return stubs_index.get_module_qualified_name(file.path)
}
pub fn (s &InterfaceDeclaration) get_type() types.Type {
return types.new_interface_type(s.name(), s.module_name())
}
pub fn (s &InterfaceDeclaration) attributes() []PsiElement {
attributes := s.find_child_by_type(.attributes) or { return [] }
if attributes is Attributes {
return attributes.attributes()
}
return []
}
pub fn (s InterfaceDeclaration) identifier() ?PsiElement {
return s.find_child_by_type(.identifier)
}
pub fn (s InterfaceDeclaration) identifier_text_range() TextRange {
if stub := s.get_stub() {
return stub.identifier_text_range
}
identifier := s.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (s InterfaceDeclaration) name() string {
if stub := s.get_stub() {
return stub.name
}
identifier := s.identifier() or { return '' }
return identifier.get_text()
}
pub fn (s InterfaceDeclaration) doc_comment() string {
if stub := s.get_stub() {
return stub.comment
}
return extract_doc_comment(s)
}
pub fn (s InterfaceDeclaration) visibility_modifiers() ?&VisibilityModifiers {
modifiers := s.find_child_by_type_or_stub(.visibility_modifiers)?
if modifiers is VisibilityModifiers {
return modifiers
}
return none
}
pub fn (s InterfaceDeclaration) fields() []PsiElement {
mut fields := s.own_fields()
embedded_types := s.embedded_definitions()
.map(types.unwrap_alias_type(it.get_type()))
.filter(it is types.InterfaceType)
for embedded_type in embedded_types {
if interface_ := find_interface(embedded_type.qualified_name()) {
fields << interface_.fields()
}
}
return fields
}
pub fn (s InterfaceDeclaration) own_fields() []PsiElement {
field_declarations := s.find_children_by_type_or_stub(.struct_field_declaration)
mut result := []PsiElement{cap: field_declarations.len}
for field_declaration in field_declarations {
if first_child := field_declaration.first_child_or_stub() {
if first_child.element_type() != .embedded_definition {
result << field_declaration
}
}
}
return result
}
pub fn (s InterfaceDeclaration) embedded_definitions() []&EmbeddedDefinition {
field_declarations := s.find_children_by_type_or_stub(.struct_field_declaration)
mut result := []&EmbeddedDefinition{cap: field_declarations.len}
for field_declaration in field_declarations {
if embedded_definition := field_declaration.find_child_by_type_or_stub(.embedded_definition) {
if embedded_definition is EmbeddedDefinition {
result << embedded_definition
}
}
}
return result
}
pub fn (s InterfaceDeclaration) methods() []PsiElement {
return s.find_children_by_type_or_stub(.interface_method_definition)
}
pub fn (s InterfaceDeclaration) find_method(name string) ?&InterfaceMethodDeclaration {
methods := s.methods()
for method in methods {
if method is InterfaceMethodDeclaration {
if name == method.name() {
return method
}
}
}
return none
}
pub fn (_ InterfaceDeclaration) stub() {}
================================================
FILE: src/analyzer/psi/InterfaceMethodDeclaration.v
================================================
module psi
pub struct InterfaceMethodDeclaration {
PsiElementImpl
}
pub fn (_ InterfaceMethodDeclaration) is_public() bool {
return true
}
pub fn (m InterfaceMethodDeclaration) identifier() ?PsiElement {
return m.find_child_by_type(.identifier)
}
pub fn (m InterfaceMethodDeclaration) identifier_text_range() TextRange {
if stub := m.get_stub() {
return stub.identifier_text_range
}
identifier := m.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (m InterfaceMethodDeclaration) signature() ?&Signature {
signature := m.find_child_by_type_or_stub(.signature)?
if signature is Signature {
return signature
}
return none
}
pub fn (m InterfaceMethodDeclaration) name() string {
if stub := m.get_stub() {
return stub.name
}
identifier := m.identifier() or { return '' }
return identifier.get_text()
}
pub fn (m &InterfaceMethodDeclaration) owner() ?&InterfaceDeclaration {
parent := m.parent_of_type(.interface_declaration)?
if parent is InterfaceDeclaration {
return parent
}
return none
}
pub fn (m &InterfaceMethodDeclaration) scope() ?&StructFieldScope {
element := m.sibling_of_type_backward(.struct_field_scope)?
if element is StructFieldScope {
return element
}
return none
}
pub fn (m InterfaceMethodDeclaration) doc_comment() string {
if stub := m.get_stub() {
return stub.comment
}
return extract_doc_comment(m)
}
pub fn (m InterfaceMethodDeclaration) fingerprint() string {
signature := m.signature() or { return '' }
count_params := signature.parameters().len
has_return_type := if _ := signature.result() { true } else { false }
return '${m.name()}:${count_params}:${has_return_type}'
}
pub fn (_ InterfaceMethodDeclaration) stub() {}
================================================
FILE: src/analyzer/psi/KeyedElement.v
================================================
module psi
pub struct KeyedElement {
PsiElementImpl
}
pub fn (n KeyedElement) field() ?&FieldName {
first_child := n.first_child()?
if first_child is FieldName {
return first_child
}
return none
}
pub fn (n KeyedElement) value() ?PsiElement {
return n.last_child()
}
================================================
FILE: src/analyzer/psi/Literal.v
================================================
module psi
import strconv
import analyzer.psi.types
pub struct Literal {
PsiElementImpl
}
fn (n &Literal) get_type() types.Type {
return infer_type(n)
}
fn (n &Literal) value() i64 {
text := n.get_text()
if text.starts_with('0x') {
return strconv.parse_int(text[2..], 16, 64) or { 0 }
} else if text.starts_with('0b') {
return strconv.parse_int(text[2..], 2, 64) or { 0 }
} else if text.starts_with('0o') {
return strconv.parse_int(text[2..], 8, 64) or { 0 }
}
return text.int()
}
================================================
FILE: src/analyzer/psi/MapInitExpression.v
================================================
module psi
pub struct MapInitExpression {
PsiElementImpl
}
pub fn (n MapInitExpression) key_values() []PsiElement {
return n.find_children_by_type(.map_keyed_element)
}
================================================
FILE: src/analyzer/psi/MapKeyedElement.v
================================================
module psi
pub struct MapKeyedElement {
PsiElementImpl
}
pub fn (n MapKeyedElement) key() ?PsiElement {
return n.first_child()
}
pub fn (n MapKeyedElement) value() ?PsiElement {
return n.last_child()
}
================================================
FILE: src/analyzer/psi/MatchExpression.v
================================================
module psi
pub struct MatchExpression {
PsiElementImpl
}
pub fn (n MatchExpression) expression() ?PsiElement {
return n.find_child_by_name('condition')
}
pub fn (n MatchExpression) arms() []PsiElement {
arms := n.find_child_by_type(.match_arms) or { return [] }
mut arm_list := arms.find_children_by_type(.match_arm)
arm_list << arms.find_children_by_type(.match_else_arm_clause)
return arm_list
}
pub fn (n MatchExpression) else_branch() ?PsiElement {
return n.find_child_by_name('else_branch')
}
================================================
FILE: src/analyzer/psi/ModuleClause.v
================================================
module psi
import os
pub struct ModuleClause {
PsiElementImpl
}
fn (_ &ModuleClause) stub() {}
pub fn (_ &ModuleClause) is_public() bool {
return true
}
fn (n &ModuleClause) identifier() ?PsiElement {
return n.find_child_by_type(.identifier)
}
pub fn (n &ModuleClause) identifier_text_range() TextRange {
if stub := n.get_stub() {
return stub.identifier_text_range
}
identifier := n.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (n ModuleClause) name() string {
if stub := n.get_stub() {
return stub.name
}
identifier := n.identifier() or { return '' }
return identifier.get_text()
}
pub fn module_qualified_name(file &PsiFile, indexing_root string) string {
module_name := file.module_name() or { '' }
if module_name in ['main', 'builtin'] {
return module_name
}
if module_name == '' && file.is_test_file() {
return ''
}
mut root_dirs := [indexing_root]
src_dir := os.join_path(indexing_root, 'src')
if os.exists(src_dir) {
root_dirs << src_dir
}
root_modules_dir := os.join_path(indexing_root, 'modules')
if os.exists(root_modules_dir) {
root_dirs << root_modules_dir
}
src_modules_dir := os.join_path(indexing_root, 'src', 'modules')
if os.exists(src_modules_dir) {
root_dirs << src_modules_dir
}
containing_dir := os.dir(file.path)
mut module_names := []string{}
mut dir := containing_dir
for dir != '' && dir !in root_dirs {
module_names << os.file_name(dir)
dir = os.dir(dir)
}
module_names.reverse_in_place()
if module_names.len == 0 {
return module_name
}
if module_names.first() == 'builtin' {
module_names = module_names[1..].clone()
}
if module_names.len != 0 && module_names.last() == module_name {
module_names = module_names[..module_names.len - 1].clone()
}
if module_names.len >= 2 && module_names[module_names.len - 1] == 'src'
&& module_names[module_names.len - 2] == module_name
&& os.is_file(os.join_path(dir, module_names[0..module_names.len - 1].join(os.path_separator), 'v.mod')) {
module_names = module_names[..module_names.len - 2].clone()
}
qualifier := module_names.join('.')
if qualifier == '' {
return module_name
}
if module_name == '' {
return qualifier
}
return qualifier + '.' + module_name
}
================================================
FILE: src/analyzer/psi/MutExpression.v
================================================
module psi
pub struct MutExpression {
PsiElementImpl
}
================================================
FILE: src/analyzer/psi/MutabilityModifiers.v
================================================
module psi
pub struct MutabilityModifiers {
PsiElementImpl
}
pub fn (n MutabilityModifiers) is_mutable() bool {
children := n.children()
return children.any(it.get_text() == 'mut')
}
================================================
FILE: src/analyzer/psi/MutabilityOwner.v
================================================
module psi
pub interface MutabilityOwner {
is_mutable() bool
mutability_modifiers() ?&MutabilityModifiers
}
================================================
FILE: src/analyzer/psi/OptionPropagationExpression.v
================================================
module psi
import analyzer.psi.types
pub struct OptionPropagationExpression {
PsiElementImpl
}
fn (c &OptionPropagationExpression) get_type() types.Type {
return infer_type(c)
}
pub fn (c OptionPropagationExpression) expression() ?PsiElement {
return c.first_child()
}
================================================
FILE: src/analyzer/psi/OrBlockExpression.v
================================================
module psi
import analyzer.psi.types
pub struct OrBlockExpression {
PsiElementImpl
}
fn (c &OrBlockExpression) get_type() types.Type {
return infer_type(c)
}
pub fn (c OrBlockExpression) expression() ?PsiElement {
return c.first_child()
}
================================================
FILE: src/analyzer/psi/ParameterDeclaration.v
================================================
module psi
import analyzer.psi.types
pub struct ParameterDeclaration {
PsiElementImpl
}
fn (p &ParameterDeclaration) stub() {}
pub fn (_ &ParameterDeclaration) is_public() bool {
return true
}
pub fn (p &ParameterDeclaration) get_type() types.Type {
return infer_type(p)
}
pub fn (p &ParameterDeclaration) identifier() ?PsiElement {
return p.find_child_by_type(.identifier)
}
pub fn (p &ParameterDeclaration) identifier_text_range() TextRange {
if stub := p.get_stub() {
return stub.identifier_text_range
}
identifier := p.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (p &ParameterDeclaration) name() string {
if stub := p.get_stub() {
return stub.name
}
identifier := p.identifier() or { return '' }
return identifier.get_text()
}
pub fn (p &ParameterDeclaration) mutability_modifiers() ?&MutabilityModifiers {
modifiers := p.find_child_by_type_or_stub(.mutability_modifiers)?
if modifiers is MutabilityModifiers {
return modifiers
}
return none
}
pub fn (p &ParameterDeclaration) is_mutable() bool {
mods := p.mutability_modifiers() or { return false }
return mods.is_mutable()
}
================================================
FILE: src/analyzer/psi/ParameterList.v
================================================
module psi
pub struct ParameterList {
PsiElementImpl
}
fn (_ &ParameterList) stub() {}
================================================
FILE: src/analyzer/psi/PlainType.v
================================================
module psi
@[heap]
pub struct PlainType {
PsiElementImpl
}
fn (_ &PlainType) stub() {}
================================================
FILE: src/analyzer/psi/Position.v
================================================
module psi
pub struct Position {
pub:
line int
character int
}
================================================
FILE: src/analyzer/psi/PrinterVisitor.v
================================================
module psi
import arrays
pub struct PrinterVisitor {
mut:
indent int
lines []string
text_lines []string
}
fn (mut r PrinterVisitor) visit_element(element PsiElement) {
if !r.visit_element_impl(element) {
return
}
mut child := element.first_child() or { return }
r.indent += 1
for {
child.accept_mut(mut r)
child = child.next_sibling() or { break }
}
r.indent -= 1
}
fn (mut r PrinterVisitor) visit_element_impl(element PsiElement) bool {
r.lines << ' '.repeat(r.indent) + '${element.node().type_name}'
r.text_lines << '${element.get_text()}'
return true
}
pub fn (r &PrinterVisitor) print() {
max_line_width := arrays.max(r.lines.map(it.len)) or { 0 }
for i, line in r.lines {
text_lines := r.text_lines[i].split_into_lines()
if text_lines.len == 0 {
println('')
} else if text_lines.len == 1 {
println(line + ' '.repeat(max_line_width - line.len) + ' | ' + r.text_lines[i])
} else {
println(line + ' '.repeat(max_line_width - line.len) + ' | ' + text_lines[0])
for j in 1 .. text_lines.len {
println(' '.repeat(max_line_width) + ' | ' + text_lines[j].trim_string_left(' '))
}
}
}
}
================================================
FILE: src/analyzer/psi/PsiDocCommentOwner.v
================================================
module psi
pub interface PsiDocCommentOwner {
doc_comment() string
}
================================================
FILE: src/analyzer/psi/PsiElement.v
================================================
module psi
import tree_sitter_v.bindings
pub type ID = int
pub type AstNode = bindings.Node[bindings.NodeType]
pub interface PsiElement {
// node returns the base node from Tree Sitter.
// In stub-based elements, this might be a zero-value node.
node() AstNode
// element_type returns the specific type of the element.
element_type() bindings.NodeType
// containing_file returns the file where the element is located.
// Returns none if the element is synthetic or the file context is lost.
containing_file() ?&PsiFile
// is_equal returns true if the element represents the same underlying node/stub as other.
is_equal(other PsiElement) bool
// return the slot id of the stub, or non_stubbed_element if the element is not stub-based
stub_id() StubId
// return the stub associated with the element, or none if the element is not stub-based
get_stub() ?&StubBase
// stub_list return the stub list associated with the element, or none if the element is not stub-based
stub_list() ?&StubList
// text_range returns the range of the node in the source file.
text_range() TextRange
// text_length returns the length of the node's text.
text_length() int
// get_text returns the text of the node.
get_text() string
// text_matches returns true if the text of the node matches the specified value.
// This method is more efficient than `get_text() == value`.
text_matches(value string) bool
// parent returns the parent node.
// If the node is the root, none is returned.
parent() ?PsiElement
// parent_nth returns the parent node at the specified nesting level.
// `parent_nth(0)` is equivalent to `parent()`.
// If no such node exists, none is returned.
parent_nth(depth int) ?PsiElement
// parent_of_type returns the parent node with the specified type.
// If no such node exists, none is returned.
parent_of_type(typ bindings.NodeType) ?PsiElement
// parent_of_any_type returns the parent node with one of the specified types.
// If no such node exists, none is returned.
parent_of_any_type(types ...bindings.NodeType) ?PsiElement
// parent_of_type_or_self returns the parent node with the specified type, or the
// node itself if its type matches the specified one.
// If no such node exists, none is returned.
parent_of_type_or_self(typ bindings.NodeType) ?PsiElement
// is_parent_of returns true if the passed node is a child of the given node.
is_parent_of(element PsiElement) bool
// inside returns true if the node is inside a node with the specified type.
inside(typ bindings.NodeType) bool
// children returns all child nodes.
children() []PsiElement
// named_children returns child nodes except unknown nodes.
named_children() []PsiElement
// first_child returns the first child node.
// If the node has no children, none is returned.
first_child() ?PsiElement
// first_child_or_stub returns the first child node or stub.
// If the node has no children or stub, none is returned.
first_child_or_stub() ?PsiElement
// last_child returns the last child of the node.
// If the node has no children, none is returned.
last_child() ?PsiElement
// last_child_or_stub returns the last child node or stub.
// If the node has no children or stub, none is returned.
last_child_or_stub() ?PsiElement
// next_sibling returns the next node at the same nesting level.
// If the node is the last child node, none is returned.
next_sibling() ?PsiElement
// next_sibling_or_stub returns the next node at the same nesting level or stub.
// If the node is the last child node or stub, none is returned.
next_sibling_or_stub() ?PsiElement
// prev_sibling returns the previous node at the same nesting level.
// If the node is the first child node, none is returned.
prev_sibling() ?PsiElement
// prev_sibling_of_type returns the previous node at the same nesting level with the specified type.
// If no such node exists, none is returned.
prev_sibling_of_type(typ bindings.NodeType) ?PsiElement
// prev_sibling_or_stub returns the previous node at the same nesting level or stub.
// If the node is the first child node or stub, none is returned.
prev_sibling_or_stub() ?PsiElement
// sibling_of_type_backward returns the previous node at the same nesting level with the specified type.
// If no such node exists, none is returned.
sibling_of_type_backward(typ bindings.NodeType) ?PsiElement
// find_element_at returns the leaf node at the specified position relative to the start of the node.
// If the node is not found, none is returned.
find_element_at(offset u32) ?PsiElement
// find_reference_at returns the reference node at the specified position relative to the start of the node.
// If the node is not found, none is returned.
find_reference_at(offset u32) ?PsiElement
// find_child_by_type returns the first child node with the specified type.
// If no such node is found, none is returned.
find_child_by_type(typ bindings.NodeType) ?PsiElement
// find_child_by_type_or_stub returns the first child node with the specified type or stub.
// If no such node is found, none is returned.
find_child_by_type_or_stub(typ bindings.NodeType) ?PsiElement
// find_child_by_name returns the first child node with the specified name.
// If no such node is found, none is returned.
find_child_by_name(name string) ?PsiElement
// find_children_by_type returns all child nodes with the specified type.
// If no such nodes are found, an empty array is returned.
find_children_by_type(typ bindings.NodeType) []PsiElement
// find_children_by_type_or_stub returns all child nodes with the specified type or stub.
// If no such nodes are found, an empty array is returned.
find_children_by_type_or_stub(typ bindings.NodeType) []PsiElement
// has_child_of_type returns true if the node has a child with the specified type.
has_child_of_type(typ bindings.NodeType) bool
// accept passes the element to the passed visitor.
accept(visitor PsiElementVisitor)
// accept_mut passes the element to the passed visitor.
// Unlike `accept()`, this method uses a visitor that can mutate its state.
accept_mut(mut visitor MutablePsiElementVisitor)
}
================================================
FILE: src/analyzer/psi/PsiElementImpl.v
================================================
module psi
import tree_sitter_v.bindings
pub struct PsiElementImpl {
pub:
node AstNode // base node from Tree Sitter
containing_file ?&PsiFile
// stubs related
stub_id StubId = non_stubbed_element
stubs_list ?&StubList
}
pub fn new_psi_node(containing_file ?&PsiFile, node AstNode) PsiElementImpl {
return PsiElementImpl{
node: node
containing_file: containing_file
}
}
fn new_psi_node_from_stub(id StubId, stubs_list &StubList) PsiElementImpl {
return PsiElementImpl{
node: AstNode{}
containing_file: new_stub_psi_file(stubs_list.path, stubs_list)
stub_id: id
stubs_list: stubs_list
}
}
fn (n &PsiElementImpl) is_valid_tree() bool {
if n.stub_based() {
return true
}
file := n.containing_file or { return true }
return !isnil(file.tree)
}
pub fn (n &PsiElementImpl) stub_id() StubId {
return n.stub_id
}
pub fn (n &PsiElementImpl) stub_based() bool {
return n.stubs_list != none
}
pub fn (n &PsiElementImpl) get_stub() ?&StubBase {
list := n.stub_list()?
return list.get_stub(n.stub_id)
}
pub fn (n &PsiElementImpl) stub_list() ?&StubList {
return n.stubs_list
}
pub fn (n &PsiElementImpl) node() AstNode {
return n.node
}
pub fn (n &PsiElementImpl) element_type() bindings.NodeType {
if stub := n.get_stub() {
return stub.element_type()
}
if !n.is_valid_tree() {
return .unknown
}
return n.node.type_name
}
pub fn (n &PsiElementImpl) containing_file() ?&PsiFile {
if list := n.stubs_list {
return new_stub_psi_file(list.path, list)
}
return n.containing_file
}
pub fn (n &PsiElementImpl) is_equal(other PsiElement) bool {
if n.element_type() != other.element_type() {
return false
}
if n.text_range() != other.text_range() {
return false
}
return n.get_text() == other.get_text()
}
pub fn (n &PsiElementImpl) accept(visitor PsiElementVisitor) {
visitor.visit_element(n)
}
pub fn (n &PsiElementImpl) accept_mut(mut visitor MutablePsiElementVisitor) {
visitor.visit_element(n)
}
pub fn (n &PsiElementImpl) find_element_at(offset u32) ?PsiElement {
if !n.is_valid_tree() {
return none
}
start_byte := if n.node.type_name == .source_file { u32(0) } else { n.node.start_byte() }
abs_offset := start_byte + offset
el := n.node.descendant_for_byte_range(abs_offset, abs_offset)?
return create_element(el, n.containing_file)
}
pub fn (n &PsiElementImpl) find_reference_at(offset u32) ?PsiElement {
element := n.find_element_at(offset)?
if element is Identifier {
parent := element.parent()?
if parent is ReferenceExpressionBase {
return parent as PsiElement
}
}
if element is ReferenceExpressionBase {
return element as PsiElement
}
return none
}
pub fn (n &PsiElementImpl) parent() ?PsiElement {
if stub := n.get_stub() {
if isnil(stub) {
return none
}
parent := stub.parent_stub()?
if isnil(parent) {
return none
}
if parent.stub_type() == .root {
return none
}
if is_valid_stub(parent) {
return parent.get_psi()
}
return none
}
if !n.is_valid_tree() {
return none
}
parent := n.node.parent()?
return create_element(parent, n.containing_file)
}
pub fn (n &PsiElementImpl) parent_nth(depth int) ?PsiElement {
if !n.is_valid_tree() {
return none
}
parent := n.node.parent_nth(depth)?
return create_element(parent, n.containing_file)
}
pub fn (n &PsiElementImpl) parent_of_type(typ bindings.NodeType) ?PsiElement {
mut res := PsiElement(n)
for {
res = res.parent()?
if res.element_type() == typ {
return res
}
}
return none
}
pub fn (n &PsiElementImpl) parent_of_any_type(types ...bindings.NodeType) ?PsiElement {
mut res := PsiElement(n)
for {
res = res.parent()?
element_type := res.element_type()
if element_type in types {
return res
}
}
return none
}
pub fn (n &PsiElementImpl) inside(typ bindings.NodeType) bool {
mut res := PsiElement(n)
for {
res = res.parent() or { return false }
if res.element_type() == typ {
return true
}
}
return false
}
pub fn (n &PsiElementImpl) is_parent_of(element PsiElement) bool {
if stub := n.get_stub() {
if element_stub := element.get_stub() {
if stub.stub_list.path != element_stub.stub_list.path {
return false
}
}
}
mut parent := element.parent() or { return false }
for {
if parent.is_equal(n) {
return true
}
parent = parent.parent() or { break }
}
return false
}
pub fn (n &PsiElementImpl) sibling_of_type_backward(typ bindings.NodeType) ?PsiElement {
mut res := PsiElement(n)
for {
res = res.prev_sibling_or_stub()?
if res.element_type() == typ {
return res
}
}
return none
}
pub fn (n &PsiElementImpl) parent_of_type_or_self(typ bindings.NodeType) ?PsiElement {
if !n.is_valid_tree() {
return none
}
if n.node.type_name == typ {
return create_element(n.node, n.containing_file)
}
mut parent := n.parent()?
if parent.element_type() == typ {
return parent
}
for {
parent = parent.parent()?
if parent.element_type() == typ {
return parent
}
}
return none
}
pub fn (n &PsiElementImpl) children() []PsiElement {
if stub := n.get_stub() {
children := stub.children_stubs()
return children.get_psi()
}
if !n.is_valid_tree() {
return []
}
mut result := []PsiElement{}
mut child := n.node.first_child() or { return [] }
for {
result << create_element(child, n.containing_file)
child = child.next_sibling() or { break }
}
return result
}
pub fn (n &PsiElementImpl) named_children() []PsiElement {
if !n.is_valid_tree() {
return []
}
if stub := n.get_stub() {
children := stub.children_stubs()
return children.get_psi()
}
mut result := []PsiElement{}
mut child := n.node.first_child() or { return [] }
for {
if child.type_name != .unknown {
result << create_element(child, n.containing_file)
}
child = child.next_sibling() or { break }
}
return result
}
pub fn (n &PsiElementImpl) first_child() ?PsiElement {
if !n.is_valid_tree() {
return none
}
child := n.node.first_child()?
return create_element(child, n.containing_file)
}
pub fn (n &PsiElementImpl) first_child_or_stub() ?PsiElement {
if stub := n.get_stub() {
child := stub.first_child()?
return child.get_psi()
}
if !n.is_valid_tree() {
return none
}
child := n.node.first_child()?
return create_element(child, n.containing_file)
}
pub fn (n &PsiElementImpl) last_child() ?PsiElement {
if !n.is_valid_tree() {
return none
}
child := n.node.last_child()?
return create_element(child, n.containing_file)
}
pub fn (n &PsiElementImpl) last_child_or_stub() ?PsiElement {
if stub := n.get_stub() {
child := stub.last_child()?
return child.get_psi()
}
if !n.is_valid_tree() {
return none
}
child := n.node.last_child()?
return create_element(child, n.containing_file)
}
pub fn (n &PsiElementImpl) next_sibling() ?PsiElement {
if !n.is_valid_tree() {
return none
}
sibling := n.node.next_sibling()?
return create_element(sibling, n.containing_file)
}
pub fn (n &PsiElementImpl) next_sibling_or_stub() ?PsiElement {
if stub := n.get_stub() {
sibling := stub.next_sibling()?
if is_valid_stub(sibling) {
return sibling.get_psi()
}
return none
}
if !n.is_valid_tree() {
return none
}
return n.next_sibling()
}
pub fn (n &PsiElementImpl) prev_sibling() ?PsiElement {
if !n.is_valid_tree() {
return none
}
sibling := n.node.prev_sibling()?
return create_element(sibling, n.containing_file)
}
pub fn (n &PsiElementImpl) prev_sibling_of_type(typ bindings.NodeType) ?PsiElement {
mut res := PsiElement(n)
for {
res = res.prev_sibling_or_stub()?
if res.element_type() == typ {
return res
}
}
return none
}
pub fn (n &PsiElementImpl) prev_sibling_or_stub() ?PsiElement {
if stub := n.get_stub() {
sibling := stub.prev_sibling()?
if is_valid_stub(sibling) {
return sibling.get_psi()
}
return none
}
return n.prev_sibling()
}
pub fn (n &PsiElementImpl) find_child_by_type(typ bindings.NodeType) ?PsiElement {
if !n.is_valid_tree() {
return none
}
ast_node := n.node.first_node_by_type(typ)?
return create_element(ast_node, n.containing_file)
}
pub fn (n &PsiElementImpl) has_child_of_type(typ bindings.NodeType) bool {
if stub := n.get_stub() {
return stub.has_child_of_type(node_type_to_stub_type(typ))
}
if !n.is_valid_tree() {
return false
}
if _ := n.node.first_node_by_type(typ) {
return true
}
return false
}
pub fn (n &PsiElementImpl) find_child_by_type_or_stub(typ bindings.NodeType) ?PsiElement {
if stub := n.get_stub() {
child := stub.get_child_by_type(node_type_to_stub_type(typ))?
return child.get_psi()
}
if !n.is_valid_tree() {
return none
}
ast_node := n.node.first_node_by_type(typ)?
return create_element(ast_node, n.containing_file)
}
pub fn (n &PsiElementImpl) find_child_by_name(name string) ?PsiElement {
if !n.is_valid_tree() {
return none
}
ast_node := n.node.child_by_field_name(name)?
return create_element(ast_node, n.containing_file)
}
pub fn (n &PsiElementImpl) find_children_by_type(typ bindings.NodeType) []PsiElement {
if !n.is_valid_tree() {
return []
}
mut result := []PsiElement{}
mut child := n.node.first_child() or { return [] }
for {
if child.type_name == typ {
result << create_element(child, n.containing_file)
}
child = child.next_sibling() or { break }
}
return result
}
pub fn (n &PsiElementImpl) find_children_by_type_or_stub(typ bindings.NodeType) []PsiElement {
if stub := n.get_stub() {
return stub.get_children_by_type(node_type_to_stub_type(typ)).get_psi()
}
if !n.is_valid_tree() {
return []
}
mut result := []PsiElement{}
mut child := n.node.first_child() or { return [] }
for {
if child.type_name == typ {
result << create_element(child, n.containing_file)
}
child = child.next_sibling() or { break }
}
return result
}
pub fn (n &PsiElementImpl) find_last_child_by_type(typ bindings.NodeType) ?PsiElement {
if !n.is_valid_tree() {
return none
}
ast_node := n.node.last_node_by_type(typ)?
return create_element(ast_node, n.containing_file)
}
pub fn (n &PsiElementImpl) get_text() string {
if stub := n.get_stub() {
return stub.text
}
if !n.is_valid_tree() {
return ''
}
if file := n.containing_file() {
return n.node.text(file.source_text)
}
return ''
}
pub fn (n &PsiElementImpl) text_matches(value string) bool {
if stub := n.get_stub() {
return stub.text == value
}
if !n.is_valid_tree() {
return false
}
if file := n.containing_file() {
return n.node.text_matches(file.source_text, value)
}
return false
}
pub fn (n &PsiElementImpl) text_range() TextRange {
if stub := n.get_stub() {
return stub.text_range
}
if !n.is_valid_tree() {
return TextRange{}
}
return TextRange{
line: int(n.node.start_point().row)
column: int(n.node.start_point().column)
end_line: int(n.node.end_point().row)
end_column: int(n.node.end_point().column)
}
}
pub fn (n &PsiElementImpl) text_length() int {
if stub := n.get_stub() {
range := stub.text_range
return range.end_column - range.column
}
if !n.is_valid_tree() {
return 0
}
return int(n.node.text_length())
}
================================================
FILE: src/analyzer/psi/PsiElementVisitor.v
================================================
module psi
pub interface PsiElementVisitor {
visit_element(element PsiElement)
visit_element_impl(element PsiElement) bool
}
pub interface MutablePsiElementVisitor {
mut:
visit_element(element PsiElement)
visit_element_impl(element PsiElement) bool
}
================================================
FILE: src/analyzer/psi/PsiFile.v
================================================
module psi
import lsp
import time
import utils
import loglib
import analyzer.parser
import tree_sitter_v.bindings
@[heap]
pub struct PsiFile {
pub:
path string
stub_list &StubList = unsafe { nil }
pub mut:
tree &bindings.Tree[bindings.NodeType] = unsafe { nil }
source_text string
root PsiElement
}
pub fn new_psi_file(path string, tree &bindings.Tree[bindings.NodeType], source_text string) &PsiFile {
mut file := &PsiFile{
path: path
tree: unsafe { tree }
source_text: source_text
stub_list: unsafe { nil }
root: unsafe { nil }
}
file.root = create_element(AstNode(tree.root_node()), file)
return file
}
pub fn new_stub_psi_file(path string, stub_list &StubList) &PsiFile {
return &PsiFile{
path: path
tree: unsafe { nil }
source_text: ''
stub_list: stub_list
root: unsafe { nil }
}
}
@[inline]
pub fn (p &PsiFile) is_stub_based() bool {
return isnil(p.tree)
}
@[inline]
pub fn (p &PsiFile) is_test_file() bool {
return p.path.ends_with('_test.v')
}
@[inline]
pub fn (p &PsiFile) is_shell_script() bool {
return p.path.ends_with('.vsh')
}
@[inline]
pub fn (p &PsiFile) index_sink() ?StubIndexSink {
return stubs_index.get_sink_for_file(p.path)
}
pub fn (mut f PsiFile) reparse(new_code string, mut p parser.Parser) {
now := time.now()
unsafe { f.tree.free() }
// TODO: for some reason if we pass the old tree then trying to get the text
// of the node gives the text at the wrong offset.
res := p.parse_code_with_tree(new_code, unsafe { nil })
f.tree = res.tree
f.source_text = res.source_text
f.root = create_element(AstNode(res.tree.root_node()), f)
loglib.with_duration(time.since(now)).with_fields({
'file': f.path
'length': f.source_text.len.str()
}).info('Reparsed file')
}
@[inline]
pub fn (p &PsiFile) path() string {
return p.path
}
@[inline]
pub fn (p &PsiFile) uri() string {
return lsp.document_uri_from_path(p.path)
}
@[inline]
pub fn (p &PsiFile) text() string {
return p.source_text
}
pub fn (mut p PsiFile) free() {
if !isnil(p.tree) {
unsafe { p.tree.free() }
p.tree = unsafe { nil }
}
}
pub fn (p &PsiFile) symbol_at(range TextRange) u8 {
lines := p.source_text.split_into_lines()
line := lines[range.line] or { return 0 }
return line[range.column - 1] or { return 0 }
}
pub fn (p &PsiFile) root() PsiElement {
if p.is_stub_based() {
return p.stub_list.root().get_psi() or { return p.root }
}
return p.root
}
@[inline]
pub fn (p &PsiFile) find_element_at(offset u32) ?PsiElement {
return p.root.find_element_at(offset)
}
@[inline]
pub fn (p &PsiFile) find_element_at_pos(pos Position) ?PsiElement {
offset := utils.compute_offset(p.source_text, pos.line, pos.character)
return p.root.find_element_at(u32(offset))
}
pub fn (p &PsiFile) find_reference_at(offset u32) ?ReferenceExpressionBase {
element := p.find_element_at(offset)?
if element is Identifier {
parent := element.parent()?
if parent is ReferenceExpressionBase {
return parent
}
}
if element is ReferenceExpressionBase {
return element
}
return none
}
@[inline]
pub fn (p &PsiFile) module_fqn() string {
return stubs_index.get_module_qualified_name(p.path)
}
pub fn (p &PsiFile) module_name() ?string {
module_clause := p.root().find_child_by_type_or_stub(.module_clause)?
if module_clause is ModuleClause {
return module_clause.name()
}
return none
}
pub fn (p &PsiFile) module_clause() ?&ModuleClause {
module_clause := p.root().find_child_by_type_or_stub(.module_clause)?
if module_clause is ModuleClause {
return module_clause
}
return none
}
pub fn (p &PsiFile) get_imports() []ImportSpec {
mut specs := []ImportSpec{}
if p.is_stub_based() {
for _, stub in p.stub_list.index_map {
if stub.stub_type == .import_spec {
if element := stub.get_psi() {
if element is ImportSpec {
specs << element
}
}
}
}
} else {
mut walker := new_psi_tree_walker(p.root())
defer { walker.free() }
for {
child := walker.next() or { break }
if child is ImportSpec {
specs << child
}
}
}
return specs
}
pub fn (p &PsiFile) resolve_import_spec(name string) ?ImportSpec {
specs := p.resolve_import_specs(name)
if specs.len > 0 {
return specs.first()
}
return none
}
pub fn (p &PsiFile) resolve_import_specs(name string) []ImportSpec {
imports := p.get_imports()
if imports.len == 0 {
return []
}
mut result := []ImportSpec{cap: 2}
for imp in imports {
if imp.import_name() == name {
result << imp
}
}
return result
}
pub fn (p &PsiFile) process_declarations(mut processor PsiScopeProcessor) bool {
children := p.root.children()
for child in children {
// if child is PsiNamedElement {
// if !processor.execute(child as PsiElement) {
// return false
// }
// }
if child is ConstantDeclaration {
for constant in child.constants() {
if constant is PsiNamedElement {
if !processor.execute(constant as PsiElement) {
return false
}
}
}
}
}
return true
}
pub fn (p &PsiFile) resolve_selective_import_symbol(name string) ?PsiElement {
imports := p.get_imports()
for spec in imports {
list := spec.selective_list() or { continue }
symbols := list.symbols()
for ref in symbols {
if ref.name() == name {
if found := p.resolve_symbol_in_import_spec(spec, name) {
return found
}
}
}
}
return none
}
pub fn (p &PsiFile) resolve_symbol_in_import_spec(spec ImportSpec, name string) ?PsiElement {
import_name := spec.qualified_name()
real_module_fqn := stubs_index.find_real_module_fqn(import_name)
if found := p.find_in_module(real_module_fqn, name) {
return found
}
return none
}
fn (p &PsiFile) find_in_module(module_fqn string, name string) ?PsiElement {
elements := stubs_index.get_all_declarations_from_module(module_fqn, false)
for element in elements {
if element is PsiNamedElement {
if element.name() == name {
return element as PsiElement
}
}
}
types := stubs_index.get_all_declarations_from_module(module_fqn, true)
for type_element in types {
if type_element is PsiNamedElement {
if type_element.name() == name {
return type_element as PsiElement
}
}
}
return none
}
================================================
FILE: src/analyzer/psi/PsiNamedElement.v
================================================
module psi
import tree_sitter_v.bindings
pub interface PsiNamedElement {
parent_of_type(typ bindings.NodeType) ?PsiElement
identifier_text_range() TextRange
identifier() ?PsiElement
name() string
is_public() bool
}
================================================
FILE: src/analyzer/psi/PsiReference.v
================================================
module psi
pub interface PsiReference {
element() PsiElement
resolve() ?PsiElement
multi_resolve() []PsiElement
}
================================================
FILE: src/analyzer/psi/PsiScopeProcessor.v
================================================
module psi
pub interface PsiScopeProcessor {
mut:
execute(element PsiElement) bool
}
================================================
FILE: src/analyzer/psi/PsiTreeWalker.v
================================================
module psi
struct PsiTreeWalker {
mut:
containing_file ?&PsiFile
tree_walker TreeWalker
}
pub fn (mut tw PsiTreeWalker) next() ?PsiElement {
value := tw.tree_walker.next()?
return create_element(value, tw.containing_file)
}
pub fn new_psi_tree_walker(root_node PsiElement) PsiTreeWalker {
return PsiTreeWalker{
tree_walker: new_tree_walker(root_node.node())
containing_file: root_node.containing_file()
}
}
@[inline]
pub fn (mut tw PsiTreeWalker) free() {
tw.tree_walker.free()
}
================================================
FILE: src/analyzer/psi/PsiTypedElement.v
================================================
module psi
import analyzer.psi.types
pub interface PsiTypedElement {
get_type() types.Type
}
================================================
FILE: src/analyzer/psi/QualifiedType.v
================================================
module psi
import analyzer.psi.types
pub struct QualifiedType {
PsiElementImpl
}
fn (n &QualifiedType) name() string {
return ''
}
fn (n &QualifiedType) qualifier() ?PsiElement {
return n.first_child_or_stub()
}
fn (n &QualifiedType) reference() ?PsiReference {
ref_expr := n.right()
reb := ref_expr? as ReferenceExpressionBase
res := new_reference(n.containing_file(), reb, true)
return res
}
fn (n &QualifiedType) resolve() ?PsiElement {
return n.reference()?.resolve()
}
fn (n &QualifiedType) get_type() types.Type {
right := n.right() or { return types.unknown_type }
if right is ReferenceExpressionBase {
resolved := right.resolve() or { return types.unknown_type }
if resolved is PsiTypedElement {
return resolved.get_type()
}
}
return types.unknown_type
}
pub fn (n QualifiedType) left() ?PsiElement {
return n.first_child_or_stub()
}
pub fn (n QualifiedType) right() ?PsiElement {
return n.last_child_or_stub()
}
================================================
FILE: src/analyzer/psi/README.md
================================================
## Description
`psi` module describes Program Structure Interface (PSI) for V language.
================================================
FILE: src/analyzer/psi/Range.v
================================================
module psi
pub struct Range {
PsiElementImpl
}
pub fn (n Range) left() ?PsiElement {
return n.first_child()
}
pub fn (n Range) right() ?PsiElement {
return n.last_child()
}
pub fn (n Range) inclusive() bool {
op := n.operator() or { return false }
return op.get_text() == '...'
}
pub fn (n Range) operator() ?PsiElement {
left := n.left()?
return left.next_sibling()
}
================================================
FILE: src/analyzer/psi/Receiver.v
================================================
module psi
import analyzer.psi.types
pub struct Receiver {
PsiElementImpl
}
pub fn (r &Receiver) is_public() bool {
return true
}
fn (r &Receiver) identifier_text_range() TextRange {
if stub := r.get_stub() {
return stub.identifier_text_range
}
identifier := r.identifier() or { return TextRange{} }
return identifier.text_range()
}
fn (r &Receiver) identifier() ?PsiElement {
return r.find_child_by_type(.identifier)
}
pub fn (r &Receiver) name() string {
if stub := r.get_stub() {
return stub.name
}
identifier := r.identifier() or { return '' }
return identifier.get_text()
}
pub fn (r &Receiver) type_element() ?PsiElement {
if stub := r.get_stub() {
if receiver_stub := stub.get_child_by_type(.plain_type) {
psi := receiver_stub.get_psi()?
if psi is PlainType {
return psi
}
}
return none
}
return r.find_child_by_type(.plain_type)
}
pub fn (r &Receiver) get_type() types.Type {
return infer_type(r)
}
pub fn (r &Receiver) mutability_modifiers() ?&MutabilityModifiers {
modifiers := r.find_child_by_type_or_stub(.mutability_modifiers)?
if modifiers is MutabilityModifiers {
return modifiers
}
return none
}
pub fn (r &Receiver) is_mutable() bool {
mods := r.mutability_modifiers() or { return false }
return mods.is_mutable()
}
fn (_ &Receiver) stub() {}
================================================
FILE: src/analyzer/psi/RecursiveVisitor.v
================================================
module psi
pub struct RecursiveVisitorBase {
}
fn (r &RecursiveVisitorBase) visit_element(element PsiElement) {
if !r.visit_element_impl(element) {
return
}
mut child := element.first_child() or { return }
for {
child.accept(r)
child = child.next_sibling() or { break }
}
}
fn (_ &RecursiveVisitorBase) visit_element_impl(element PsiElement) bool {
return true
}
================================================
FILE: src/analyzer/psi/ReferenceExpression.v
================================================
module psi
import analyzer.psi.types
pub struct ReferenceExpression {
PsiElementImpl
}
pub fn (r &ReferenceExpression) is_public() bool {
return true
}
pub fn (r ReferenceExpression) identifier() ?PsiElement {
return r.first_child()
}
pub fn (r &ReferenceExpression) identifier_text_range() TextRange {
if stub := r.get_stub() {
return stub.identifier_text_range
}
identifier := r.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (r &ReferenceExpression) name() string {
if stub := r.get_stub() {
return stub.text
}
identifier := r.identifier() or { return '' }
return identifier.get_text()
}
pub fn (r ReferenceExpression) qualifier() ?PsiElement {
parent := r.parent()?
if parent is SelectorExpression {
left := parent.left()?
if left.is_equal(r) {
return none
}
return left
}
return none
}
pub fn (r ReferenceExpression) reference() PsiReference {
if parent := r.parent() {
if parent is ValueAttribute {
return new_attribute_reference(r.containing_file(), r)
}
}
return new_reference(r.containing_file(), r, false)
}
pub fn (r ReferenceExpression) resolve() ?PsiElement {
return r.reference().resolve()
}
pub fn (r ReferenceExpression) get_type() types.Type {
return infer_type(r)
}
================================================
FILE: src/analyzer/psi/ReferenceExpressionBase.v
================================================
module psi
pub interface ReferenceExpressionBase {
get_text() string
text_range() TextRange
name() string
qualifier() ?PsiElement
reference() PsiReference
resolve() ?PsiElement
}
================================================
FILE: src/analyzer/psi/ReferenceImpl.v
================================================
module psi
import analyzer.psi.types
import utils
pub struct ReferenceImpl {
element ReferenceExpressionBase
file ?&PsiFile
for_types bool
for_attributes bool
}
pub fn new_reference(file ?&PsiFile, element ReferenceExpressionBase, for_types bool) &ReferenceImpl {
return &ReferenceImpl{
element: element
file: file
for_types: for_types
}
}
pub fn new_attribute_reference(file ?&PsiFile, element ReferenceExpressionBase) &ReferenceImpl {
return &ReferenceImpl{
element: element
file: file
for_attributes: true
}
}
fn (r &ReferenceImpl) element() PsiElement {
return r.element as PsiElement
}
pub fn (r &ReferenceImpl) resolve() ?PsiElement {
file := r.file or { return none }
sub := SubResolver{
containing_file: file
element: r.element
for_types: r.for_types
for_attributes: r.for_attributes
}
mut processor := ResolveProcessor{
containing_file: file
ref: r.element
ref_name: r.element.name()
}
if from_cache := resolve_cache.get(r.element()) {
return from_cache
}
sub.process_resolve_variants(mut processor)
if processor.result.len == 0 {
return none
}
result := processor.result.first()
resolve_cache.put(r.element(), result)
return result
}
pub fn (r &ReferenceImpl) multi_resolve() []PsiElement {
file := r.file or { return [] }
if res := r.resolve_as_import_spec() {
return res
}
sub := SubResolver{
containing_file: file
element: r.element
for_types: r.for_types
for_attributes: r.for_attributes
}
mut processor := ResolveProcessor{
containing_file: file
ref: r.element
ref_name: r.element.name()
collect_all: true
}
sub.process_resolve_variants(mut processor)
return processor.result
}
fn (r &ReferenceImpl) resolve_as_import_spec() ?[]PsiElement {
if r.element is Identifier {
parent := r.element.parent()?
if parent !is ImportName {
return none
}
spec := parent.parent()?.parent()?
if spec is ImportSpec {
if ident := spec.identifier() {
if ident.is_equal(parent) {
return [spec]
}
}
}
}
return none
}
pub struct SubResolver {
pub:
containing_file ?&PsiFile
element ReferenceExpressionBase
for_types bool
for_attributes bool
}
fn (r &SubResolver) element() PsiElement {
return r.element as PsiElement
}
pub fn (r &SubResolver) process_resolve_variants(mut processor PsiScopeProcessor) bool {
return if qualifier := r.element.qualifier() {
r.process_qualifier_expression(qualifier, mut processor)
} else {
r.process_unqualified_resolve(mut processor)
}
}
pub fn (r &SubResolver) process_qualifier_expression(qualifier PsiElement, mut processor PsiScopeProcessor) bool {
if qualifier is PsiTypedElement {
typ := infer_type(qualifier as PsiElement)
if typ !is types.UnknownType {
if !r.process_type(typ, mut processor) {
return false
}
}
}
if qualifier is ReferenceExpressionBase {
resolved := qualifier.resolve() or { return true }
if resolved is ImportSpec {
import_name := resolved.import_name()
file := r.containing_file or { return true }
specs := file.resolve_import_specs(import_name)
for _, spec in specs {
target_fqn := spec.qualified_name()
real_fqn := stubs_index.find_real_module_fqn(target_fqn)
elements := stubs_index.get_all_declarations_from_module(real_fqn, r.for_types)
if !r.process_elements(elements, mut processor) {
return false
}
}
return true
}
if resolved is ModuleClause {
file := r.containing_file or { return true }
module_name := stubs_index.get_module_qualified_name(file.path)
current_module_elements := stubs_index.get_all_declarations_from_module(module_name,
r.for_types)
for elem in current_module_elements {
if !processor.execute(elem) {
return false
}
}
}
if resolved is StructDeclaration {
methods := static_methods_list(resolved.get_type())
if !r.process_elements(methods, mut processor) {
return false
}
}
}
return true
}
pub fn (r &SubResolver) process_elements(elements []PsiElement, mut processor PsiScopeProcessor) bool {
for element in elements {
if !processor.execute(element) {
return false
}
}
return true
}
pub fn (r &SubResolver) process_type(typ types.Type, mut processor PsiScopeProcessor) bool {
if typ is types.StructType {
if struct_ := r.find_struct(stubs_index, typ.qualified_name()) {
is_method_ref := if grand := r.element().parent_nth(2) {
grand is CallExpression
} else {
false
}
// If it is a call, then most likely it is a method call, but it
// could be a function call that is stored in a structure field.
if is_method_ref {
if !r.process_methods(typ, mut processor) {
return false
}
if !r.process_elements(struct_.fields(), mut processor) {
return false
}
} else {
if !r.process_elements(struct_.fields(), mut processor) {
return false
}
if !r.process_methods(typ, mut processor) {
return false
}
}
for def in struct_.embedded_definitions() {
if !processor.execute(def) {
return false
}
}
}
if !r.process_any_type(mut processor) {
return false
}
return true
}
if typ is types.InterfaceType {
if interface_ := r.find_interface(stubs_index, typ.qualified_name()) {
if !r.process_methods(typ, mut processor) {
return false
}
if !r.process_elements(interface_.fields(), mut processor) {
return false
}
if !r.process_elements(interface_.methods(), mut processor) {
return false
}
for def in interface_.embedded_definitions() {
if !processor.execute(def) {
return false
}
}
}
if !r.process_any_type(mut processor) {
return false
}
return true
}
if typ is types.EnumType {
enum_ := r.find_enum(stubs_index, typ.qualified_name()) or { return true }
if !r.process_elements(enum_.fields(), mut processor) {
return false
}
if !r.process_methods(typ, mut processor) {
return false
}
if enum_.is_flag() {
if !r.process_type(types.flag_enum_type, mut processor) {
return false
}
}
if !r.process_any_type(mut processor) {
return false
}
return true
}
if typ is types.ArrayType {
if !r.process_methods(typ, mut processor) {
return false
}
if !r.process_type(types.builtin_array_type, mut processor) {
return false
}
if !r.process_any_type(mut processor) {
return false
}
return true
}
if typ is types.MapType {
if !r.process_methods(typ, mut processor) {
return false
}
if !r.process_type(types.builtin_map_type, mut processor) {
return false
}
if !r.process_any_type(mut processor) {
return false
}
return true
}
if typ is types.PointerType {
if !r.process_type(typ.inner, mut processor) {
return false
}
if !r.process_any_type(mut processor) {
return false
}
return true
}
if typ is types.OptionType {
if !r.process_type(typ.inner, mut processor) {
return false
}
if !r.process_any_type(mut processor) {
return false
}
return true
}
if typ is types.ResultType {
if !r.process_type(typ.inner, mut processor) {
return false
}
if !r.process_any_type(mut processor) {
return false
}
return true
}
if typ is types.AliasType {
if !r.process_methods(typ, mut processor) {
return false
}
if !r.process_type(typ.inner, mut processor) {
return false
}
if !r.process_any_type(mut processor) {
return false
}
return true
}
if typ is types.GenericInstantiationType {
if !r.process_type(typ.inner, mut processor) {
return false
}
if !r.process_any_type(mut processor) {
return false
}
return true
}
if !r.process_methods(typ, mut processor) {
return false
}
if !r.process_any_type(mut processor) {
return false
}
return true
}
pub fn (r &SubResolver) process_any_type(mut processor PsiScopeProcessor) bool {
return r.process_methods(types.any_type, mut processor)
}
pub fn (r &SubResolver) process_methods(typ types.Type, mut processor PsiScopeProcessor) bool {
return r.process_elements(methods_list(typ), mut processor)
}
pub fn (r &SubResolver) process_unqualified_resolve(mut processor PsiScopeProcessor) bool {
if r.for_attributes {
return r.resolve_attribute(mut processor)
}
if parent := r.element().parent() {
if parent is FieldName {
return r.process_type_initializer_field(mut processor)
}
if parent.element_type() == .enum_fetch {
return r.process_enum_fetch(parent, mut processor)
}
}
if !r.process_block(mut processor) {
return false
}
if !r.process_imported_modules(mut processor) {
return false
}
if !r.process_selective_imports(mut processor) {
return false
}
if !r.process_owner_generic_ts(mut processor) {
return false
}
if !r.process_os_module(mut processor) {
return false
}
builtin_elements := stubs_index.get_all_declarations_from_module('builtin', r.for_types)
for element in builtin_elements {
if !processor.execute(element) {
return false
}
}
if r.for_types {
stubs_elements := stubs_index.get_all_declarations_from_module('stubs', r.for_types)
for element in stubs_elements {
if !processor.execute(element) {
return false
}
}
}
module_name := if file := r.containing_file {
stubs_index.get_module_qualified_name(file.path)
} else {
''
}
element := r.element()
if element is PsiNamedElement {
fqn := if module_name.len != 0 {
module_name + '.' + element.name()
} else {
element.name()
}
if !r.for_types {
if func := r.find_function(stubs_index, fqn) {
if !processor.execute(func) {
return false
}
}
if constant := r.find_constant(stubs_index, fqn) {
if !processor.execute(constant) {
return false
}
}
}
if struct_ := r.find_struct(stubs_index, fqn) {
if !processor.execute(struct_) {
return false
}
}
if interface_ := r.find_interface(stubs_index, fqn) {
if !processor.execute(interface_) {
return false
}
}
if enum_ := r.find_enum(stubs_index, fqn) {
if !processor.execute(enum_) {
return false
}
}
if alias := r.find_type_alias(stubs_index, fqn) {
if !processor.execute(alias) {
return false
}
}
// global variable cannot have module name
if global_variable := r.find_global_variable(stubs_index, element.name()) {
if !processor.execute(global_variable) {
return false
}
}
}
mod_decls := stubs_index.get_all_declarations_from_module(module_name, r.for_types)
if !r.process_elements(mod_decls, mut processor) {
return false
}
if !r.process_module_clause(mut processor) {
return false
}
return true
}
pub fn (r &SubResolver) walk_up(element PsiElement, mut processor PsiScopeProcessor) bool {
mut run := element
mut last_parent := element
for {
if mut run is ForStatement {
vars := run.var_definitions()
for v in vars {
if !processor.execute(v) {
return false
}
}
}
if mut run is IfExpression {
if def := run.var_definition() {
if !processor.execute(def) {
return false
}
}
}
if mut run is Block {
if !run.process_declarations(mut processor, last_parent) {
return false
}
if !r.process_parameters(run, mut processor) {
return false
}
if !r.process_receiver(run, mut processor) {
return false
}
}
if mut run is SourceFile {
if !run.process_declarations(mut processor, last_parent) {
return false
}
}
if mut run is GenericParametersOwner {
if parameters := run.generic_parameters() {
params := parameters.parameters()
for param in params {
if !processor.execute(param) {
return false
}
}
}
}
last_parent = run
run = run.parent() or { break }
}
return true
}
pub fn (_ &SubResolver) process_parameters(b Block, mut processor PsiScopeProcessor) bool {
parent := b.parent() or { return true }
if parent is SignatureOwner {
signature := parent.signature() or { return true }
params := signature.parameters()
for param in params {
if !processor.execute(param) {
return false
}
}
}
return true
}
pub fn (_ &SubResolver) process_receiver(b Block, mut processor PsiScopeProcessor) bool {
parent := b.parent() or { return true }
if parent is FunctionOrMethodDeclaration {
receiver := parent.receiver() or { return true }
if !processor.execute(receiver) {
return false
}
}
return true
}
pub fn (r &SubResolver) process_block(mut processor PsiScopeProcessor) bool {
// if r.containing_file.is_stub_based() {
// return true
// }
// mut delegate := ResolveProcessor{
// ...processor
// }
// if delegate.result.len == 0 {
// return true
// }
//
// for result in delegate.result {
// processor.result << result
// }
return r.walk_up(r.element as PsiElement, mut processor)
}
pub fn (r &SubResolver) process_module_clause(mut processor PsiScopeProcessor) bool {
file := r.containing_file or { return true }
mod := file.module_clause() or { return true }
return processor.execute(mod)
}
pub fn (r &SubResolver) process_imported_modules(mut processor PsiScopeProcessor) bool {
file := r.containing_file or { return true }
search_name := r.element().get_text()
import_spec := file.resolve_import_spec(search_name) or { return true }
if !processor.execute(import_spec) {
return false
}
return true
}
pub fn (r &SubResolver) process_selective_imports(mut processor PsiScopeProcessor) bool {
element := r.element as PsiElement
name := r.element.name()
file := r.containing_file or { return true }
if parent_list := element.parent_of_type(.selective_import_list) {
if spec := parent_list.parent_of_type(.import_spec) {
if spec is ImportSpec {
if found := file.resolve_symbol_in_import_spec(spec, name) {
return processor.execute(found)
}
return true
}
}
}
if resolved := file.resolve_selective_import_symbol(name) {
if !processor.execute(resolved) {
return false
}
}
return true
}
pub fn (r &SubResolver) process_enum_fetch(parent PsiElement, mut processor PsiScopeProcessor) bool {
context_type := TypeInferer{}.infer_context_type(parent)
return r.process_type(context_type, mut processor)
}
pub fn (r &SubResolver) process_type_initializer_field(mut processor PsiScopeProcessor) bool {
if init_expr := r.element().parent_of_type(.type_initializer) {
if init_expr is PsiTypedElement {
typ :=
types.unwrap_generic_instantiation_type(types.unwrap_pointer_type(infer_type(init_expr as PsiElement)))
if typ is types.StructType {
if !r.process_struct_type_fields(typ, mut processor) {
return false
}
}
if typ is types.ArrayType {
if !r.process_struct_type_fields(types.array_init_type, mut processor) {
return false
}
}
if typ is types.ChannelType {
if !r.process_struct_type_fields(types.chan_init_type, mut processor) {
return false
}
}
}
}
if call_expr := r.element().parent_of_type(.call_expression) {
if call_expr is CallExpression {
resolved := call_expr.resolve() or { return true }
if resolved is SignatureOwner {
signature := resolved.signature() or { return true }
parameters := signature.parameters()
if parameters.len == 0 {
return true
}
last_parameter := parameters.last()
param_type := infer_type(last_parameter)
if param_type is types.StructType {
if !r.process_struct_type_fields(param_type, mut processor) {
return false
}
}
}
}
}
return true
}
pub fn (r &SubResolver) process_struct_type_fields(struct_type types.StructType, mut processor PsiScopeProcessor) bool {
if struct_ := r.find_struct(stubs_index, struct_type.qualified_name()) {
fields := struct_.fields()
for field in fields {
if !processor.execute(field) {
return false
}
}
}
return true
}
pub fn (r &SubResolver) process_os_module(mut processor PsiScopeProcessor) bool {
file := r.containing_file or { return true }
if !file.is_shell_script() {
return true
}
// In shell scripts OS module is imported implicitly, so we need to process it elements.
os_elements := stubs_index.get_all_declarations_from_module('os', r.for_types)
return r.process_elements(os_elements, mut processor)
}
pub fn (r &SubResolver) process_owner_generic_ts(mut processor PsiScopeProcessor) bool {
element := r.element()
if element.text_length() != 1 {
// for now V only support single char generic parameters
return true
}
method := element.parent_of_type(.function_declaration) or { return true }
if method is FunctionOrMethodDeclaration {
receiver := method.receiver() or { return true }
if receiver.is_parent_of(element) {
return true
}
receiver_type := types.unwrap_alias_type(types.unwrap_pointer_type(receiver.get_type()))
if receiver_type is types.GenericInstantiationType {
inner := receiver_type.inner
inner_name := inner.qualified_name()
elements := stubs_index.get_any_elements_by_name(inner_name)
if elements.len == 0 {
return true
}
for resolved in elements {
if resolved is GenericParametersOwner {
params := resolved.generic_parameters() or { continue }
parameters := params.parameters()
for param in parameters {
if !processor.execute(param) {
return false
}
}
}
}
}
}
return true
}
pub fn (_ &SubResolver) find_function(stubs_index StubIndex, name string) ?&FunctionOrMethodDeclaration {
found := stubs_index.get_elements_by_name(.functions, name)
if found.len != 0 {
first := found.first()
if first is FunctionOrMethodDeclaration {
return first
}
}
return none
}
pub fn (_ &SubResolver) find_struct(stubs_index StubIndex, name string) ?&StructDeclaration {
found := stubs_index.get_elements_by_name(.structs, name)
if found.len != 0 {
first := found.first()
if first is StructDeclaration {
return first
}
}
return none
}
pub fn (_ &SubResolver) find_interface(stubs_index StubIndex, name string) ?&InterfaceDeclaration {
found := stubs_index.get_elements_by_name(.interfaces, name)
if found.len != 0 {
first := found.first()
if first is InterfaceDeclaration {
return first
}
}
return none
}
pub fn (_ &SubResolver) find_enum(stubs_index StubIndex, name string) ?&EnumDeclaration {
found := stubs_index.get_elements_by_name(.enums, name)
if found.len != 0 {
first := found.first()
if first is EnumDeclaration {
return first
}
}
return none
}
pub fn (_ &SubResolver) find_constant(stubs_index StubIndex, name string) ?&ConstantDefinition {
found := stubs_index.get_elements_by_name(.constants, name)
if found.len != 0 {
first := found.first()
if first is ConstantDefinition {
return first
}
}
return none
}
pub fn (_ &SubResolver) find_type_alias(stubs_index StubIndex, name string) ?&TypeAliasDeclaration {
found := stubs_index.get_elements_by_name(.type_aliases, name)
if found.len != 0 {
first := found.first()
if first is TypeAliasDeclaration {
return first
}
}
return none
}
pub fn (_ &SubResolver) find_global_variable(stubs_index StubIndex, name string) ?&GlobalVarDefinition {
found := stubs_index.get_elements_by_name(.global_variables, name)
if found.len != 0 {
first := found.first()
if first is GlobalVarDefinition {
return first
}
}
return none
}
pub fn (_ &SubResolver) find_attribute(stubs_index StubIndex, name string) ?&StructDeclaration {
found := stubs_index.get_elements_by_name(.attributes, name)
if found.len != 0 {
first := found.first()
if first is StructDeclaration {
return first
}
}
return none
}
pub fn (r &SubResolver) resolve_attribute(mut processor PsiScopeProcessor) bool {
element := r.element()
if element is PsiNamedElement {
if attr := r.find_attribute(stubs_index, element.name()) {
if !processor.execute(attr) {
return false
}
}
}
return true
}
pub struct ResolveProcessor {
containing_file &PsiFile
ref ReferenceExpressionBase
ref_name string
mut:
result []PsiElement
collect_all bool
}
fn (mut r ResolveProcessor) execute(element PsiElement) bool {
if element.is_equal(r.ref as PsiElement) {
r.result << element
return false
}
if element is PsiNamedElement {
mut name := element.name()
if name.ends_with('Attribute') {
name = utils.pascal_case_to_snake_case(name.trim_string_right('Attribute'))
}
if name == r.ref_name {
r.result << element as PsiElement
if r.collect_all {
return true
}
return false
}
}
return true
}
pub fn find_element(fqn string) ?PsiElement {
found := stubs_index.get_any_elements_by_name(fqn)
if found.len != 0 {
return found.first()
}
return none
}
pub fn find_interface(fqn string) ?&InterfaceDeclaration {
found := stubs_index.get_elements_by_name(.interfaces, fqn)
if found.len != 0 {
first := found.first()
if first is InterfaceDeclaration {
return first
}
}
return none
}
pub fn find_struct(fqn string) ?&StructDeclaration {
found := stubs_index.get_elements_by_name(.structs, fqn)
if found.len != 0 {
first := found.first()
if first is StructDeclaration {
return first
}
}
return none
}
pub fn find_alias(fqn string) ?&TypeAliasDeclaration {
found := stubs_index.get_elements_by_name(.type_aliases, fqn)
if found.len != 0 {
first := found.first()
if first is TypeAliasDeclaration {
return first
}
}
return none
}
================================================
FILE: src/analyzer/psi/ResolveCache.v
================================================
@[translated]
module psi
import sync
import loglib
__global resolve_cache = ResolveCache{}
pub struct ResolveCache {
mut:
mutex sync.RwMutex
data map[string]PsiElement
}
pub fn (t &ResolveCache) get(element PsiElement) ?PsiElement {
t.mutex.@rlock()
defer {
t.mutex.runlock()
}
fingerprint := t.element_fingerprint(element)
return t.data[fingerprint] or { return none }
}
pub fn (mut t ResolveCache) put(element PsiElement, result PsiElement) PsiElement {
t.mutex.@lock()
defer {
t.mutex.unlock()
}
fingerprint := t.element_fingerprint(element)
t.data[fingerprint] = result
return result
}
pub fn (mut t ResolveCache) clear() {
t.mutex.@lock()
defer {
t.mutex.unlock()
}
loglib.with_fields({
'cache_size': t.data.len.str()
}).log_one(.info, 'Clearing resolve cache')
t.data = map[string]PsiElement{}
}
@[inline]
fn (_ &ResolveCache) element_fingerprint(element PsiElement) string {
file := element.containing_file() or { return '' }
range := element.text_range()
return '${file.path}:${element.node().type_name}:${range.line}:${range.column}:${range.end_column}:${range.end_line}'
}
================================================
FILE: src/analyzer/psi/ResultPropagationExpression.v
================================================
module psi
import analyzer.psi.types
pub struct ResultPropagationExpression {
PsiElementImpl
}
fn (c &ResultPropagationExpression) get_type() types.Type {
return infer_type(c)
}
pub fn (c ResultPropagationExpression) expression() ?PsiElement {
return c.first_child()
}
================================================
FILE: src/analyzer/psi/SelectiveImportList.v
================================================
module psi
pub struct SelectiveImportList {
PsiElementImpl
}
pub fn (n &SelectiveImportList) symbols() []ReferenceExpression {
children := n.find_children_by_type_or_stub(.reference_expression)
mut res := []ReferenceExpression{cap: children.len}
for child in children {
if child is ReferenceExpression {
res << child
}
}
return res
}
fn (n &SelectiveImportList) stub() {}
================================================
FILE: src/analyzer/psi/SelectorExpression.v
================================================
module psi
import analyzer.psi.types
pub struct SelectorExpression {
PsiElementImpl
}
fn (n &SelectorExpression) name() string {
return ''
}
fn (n &SelectorExpression) qualifier() ?PsiElement {
return n.first_child()
}
fn (n &SelectorExpression) reference() PsiReference {
right := n.right() or { panic('no right element for SelectorExpression') }
if right is FieldName {
if child := right.reference_expression() {
return new_reference(n.containing_file(), child, false)
}
}
if right is ReferenceExpression {
return new_reference(n.containing_file(), right, false)
}
if right is TypeReferenceExpression {
return new_reference(n.containing_file(), right, true)
}
if right is Identifier {
return new_reference(n.containing_file(), right, false)
}
ident := &Identifier{
PsiElementImpl: new_psi_node(n.containing_file(), right.node())
}
return new_reference(n.containing_file(), ident, false)
}
fn (n &SelectorExpression) resolve() ?PsiElement {
return n.reference().resolve()
}
fn (n &SelectorExpression) get_type() types.Type {
right := n.right() or { return types.unknown_type }
if right is ReferenceExpressionBase {
resolved := right.resolve() or { return types.unknown_type }
if resolved is PsiTypedElement {
return resolved.get_type()
}
}
return types.unknown_type
}
pub fn (n SelectorExpression) left() ?PsiElement {
return n.first_child()
}
pub fn (n SelectorExpression) right() ?PsiElement {
return n.last_child()
}
================================================
FILE: src/analyzer/psi/Signature.v
================================================
module psi
import analyzer.psi.types
pub struct Signature {
PsiElementImpl
}
pub fn (s &Signature) get_type() types.Type {
return infer_type(s)
}
pub fn (n Signature) parameters() []PsiElement {
mut parameters := []PsiElement{}
if list := n.find_child_by_type_or_stub(.parameter_list) {
parameters << list.find_children_by_type_or_stub(.parameter_declaration)
}
if list_type := n.find_child_by_type_or_stub(.type_parameter_list) {
parameters << list_type.find_children_by_type_or_stub(.type_parameter_declaration)
}
return parameters
}
pub fn (n Signature) result() ?PsiElement {
last := n.last_child_or_stub()?
if last is PlainType {
return last
}
return none
}
fn (_ &Signature) stub() {}
================================================
FILE: src/analyzer/psi/SignatureOwner.v
================================================
module psi
pub interface SignatureOwner {
signature() ?&Signature
}
================================================
FILE: src/analyzer/psi/SliceExpression.v
================================================
module psi
pub struct SliceExpression {
PsiElementImpl
}
pub fn (c SliceExpression) expression() ?PsiElement {
return c.first_child()
}
pub fn (c SliceExpression) resolve() ?PsiElement {
expr := if selector_expr := c.find_child_by_type(.selector_expression) {
selector_expr as ReferenceExpressionBase
} else if ref_expr := c.find_child_by_type(.reference_expression) {
ref_expr as ReferenceExpressionBase
} else {
return none
}
if expr is ReferenceExpressionBase {
resolved := expr.resolve()?
return resolved
}
return none
}
================================================
FILE: src/analyzer/psi/SourceFile.v
================================================
module psi
pub struct SourceFile {
PsiElementImpl
}
pub fn (b SourceFile) process_declarations(mut processor PsiScopeProcessor, last_parent PsiElement) bool {
statements := b.find_children_by_type(.simple_statement)
for statement in statements {
if statement.is_equal(last_parent) {
return true
}
first_child := statement.first_child() or { continue }
if first_child is VarDeclaration {
for var in first_child.vars() {
if !processor.execute(var) {
return false
}
}
}
}
return true
}
================================================
FILE: src/analyzer/psi/StaticMethodDeclaration.v
================================================
module psi
import analyzer.psi.types
pub struct StaticMethodDeclaration {
PsiElementImpl
}
pub fn (f &StaticMethodDeclaration) generic_parameters() ?&GenericParameters {
generic_parameters := f.find_child_by_type_or_stub(.generic_parameters)?
if generic_parameters is GenericParameters {
return generic_parameters
}
return none
}
pub fn (f &StaticMethodDeclaration) is_public() bool {
modifiers := f.visibility_modifiers() or { return false }
return modifiers.is_public()
}
fn (f &StaticMethodDeclaration) get_type() types.Type {
signature := f.signature() or { return types.unknown_type }
return signature.get_type()
}
pub fn (f StaticMethodDeclaration) identifier() ?PsiElement {
return f.find_child_by_type(.identifier)
}
pub fn (f StaticMethodDeclaration) identifier_text_range() TextRange {
if stub := f.get_stub() {
return stub.identifier_text_range
}
identifier := f.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (f StaticMethodDeclaration) signature() ?&Signature {
signature := f.find_child_by_type_or_stub(.signature)?
if signature is Signature {
return signature
}
return none
}
pub fn (f StaticMethodDeclaration) name() string {
if stub := f.get_stub() {
return stub.name
}
identifier := f.identifier() or { return '' }
return identifier.get_text()
}
pub fn (f StaticMethodDeclaration) doc_comment() string {
if stub := f.get_stub() {
return stub.comment
}
return extract_doc_comment(f)
}
pub fn (f StaticMethodDeclaration) receiver_type() types.Type {
receiver := f.receiver() or { return types.unknown_type }
return receiver.get_type()
}
pub fn (f StaticMethodDeclaration) receiver() ?&StaticReceiver {
element := f.find_child_by_type_or_stub(.static_receiver)?
if element is StaticReceiver {
return element
}
return none
}
pub fn (f StaticMethodDeclaration) visibility_modifiers() ?&VisibilityModifiers {
modifiers := f.find_child_by_type_or_stub(.visibility_modifiers)?
if modifiers is VisibilityModifiers {
return modifiers
}
return none
}
pub fn (f StaticMethodDeclaration) owner() ?PsiElement {
receiver := f.receiver()?
typ := receiver.get_type()
unwrapped := types.unwrap_generic_instantiation_type(types.unwrap_pointer_type(typ))
if unwrapped is types.InterfaceType {
return *find_interface(unwrapped.qualified_name())?
}
if unwrapped is types.StructType {
return *find_struct(unwrapped.qualified_name())?
}
if unwrapped is types.AliasType {
return *find_alias(unwrapped.qualified_name())?
}
return none
}
pub fn (f StaticMethodDeclaration) fingerprint() string {
signature := f.signature() or { return '' }
count_params := signature.parameters().len
has_return_type := if _ := signature.result() { true } else { false }
return '${f.name()}:${count_params}:${has_return_type}'
}
pub fn (_ StaticMethodDeclaration) stub() {}
================================================
FILE: src/analyzer/psi/StaticReceiver.v
================================================
module psi
import analyzer.psi.types
pub struct StaticReceiver {
PsiElementImpl
}
pub fn (_ &StaticReceiver) is_public() bool {
return true
}
fn (r &StaticReceiver) identifier_text_range() TextRange {
if stub := r.get_stub() {
return stub.identifier_text_range
}
identifier := r.identifier() or { return TextRange{} }
return identifier.text_range()
}
fn (r &StaticReceiver) identifier() ?PsiElement {
return r.find_child_by_type(.identifier)
}
pub fn (r &StaticReceiver) name() string {
if stub := r.get_stub() {
return stub.name
}
identifier := r.identifier() or { return '' }
return identifier.get_text()
}
pub fn (r &StaticReceiver) get_type() types.Type {
return infer_type(r.first_child())
}
fn (_ &StaticReceiver) stub() {}
================================================
FILE: src/analyzer/psi/StringLiteral.v
================================================
module psi
pub struct StringLiteral {
PsiElementImpl
}
pub fn (n StringLiteral) content() string {
text := n.get_text()
return text[1..text.len - 1]
}
================================================
FILE: src/analyzer/psi/StructDeclaration.v
================================================
module psi
import analyzer.psi.types
pub struct StructDeclaration {
PsiElementImpl
}
pub fn (s &StructDeclaration) generic_parameters() ?&GenericParameters {
generic_parameters := s.find_child_by_type_or_stub(.generic_parameters)?
if generic_parameters is GenericParameters {
return generic_parameters
}
return none
}
pub fn (s &StructDeclaration) is_public() bool {
modifiers := s.visibility_modifiers() or { return false }
return modifiers.is_public()
}
pub fn (s &StructDeclaration) module_name() string {
file := s.containing_file() or { return '' }
return stubs_index.get_module_qualified_name(file.path)
}
pub fn (s &StructDeclaration) get_type() types.Type {
return types.new_struct_type(s.name(), s.module_name())
}
pub fn (s &StructDeclaration) attributes() []PsiElement {
attributes := s.find_child_by_type_or_stub(.attributes) or { return [] }
if attributes is Attributes {
return attributes.attributes()
}
return []
}
pub fn (s StructDeclaration) identifier() ?PsiElement {
return s.find_child_by_type(.identifier)
}
pub fn (s StructDeclaration) identifier_text_range() TextRange {
if stub := s.get_stub() {
return stub.identifier_text_range
}
identifier := s.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (s StructDeclaration) name() string {
if stub := s.get_stub() {
return stub.name
}
identifier := s.identifier() or { return '' }
return identifier.get_text()
}
pub fn (s StructDeclaration) doc_comment() string {
if stub := s.get_stub() {
return stub.comment
}
return extract_doc_comment(s)
}
pub fn (s StructDeclaration) visibility_modifiers() ?&VisibilityModifiers {
modifiers := s.find_child_by_type_or_stub(.visibility_modifiers)?
if modifiers is VisibilityModifiers {
return modifiers
}
return none
}
pub fn (s StructDeclaration) fields() []PsiElement {
mut fields := s.own_fields()
embedded_types := s.embedded_definitions()
.map(types.unwrap_alias_type(it.get_type()))
.filter(it is types.StructType)
for struct_type in embedded_types {
struct_ := find_struct(struct_type.qualified_name()) or { continue }
fields << struct_.fields()
}
return fields
}
pub fn (s StructDeclaration) own_fields() []PsiElement {
field_declarations := s.find_children_by_type_or_stub(.struct_field_declaration)
mut result := []PsiElement{cap: field_declarations.len}
for field_declaration in field_declarations {
if first_child := field_declaration.first_child_or_stub() {
if first_child.element_type() != .embedded_definition {
result << field_declaration
}
}
}
return result
}
pub fn (s StructDeclaration) embedded_definitions() []&EmbeddedDefinition {
field_declarations := s.find_children_by_type_or_stub(.struct_field_declaration)
mut result := []&EmbeddedDefinition{cap: field_declarations.len}
for field_declaration in field_declarations {
if embedded_definition := field_declaration.find_child_by_type_or_stub(.embedded_definition) {
if embedded_definition is EmbeddedDefinition {
result << embedded_definition
}
}
}
return result
}
pub fn (s &StructDeclaration) is_attribute() bool {
attrs := s.attributes()
if attrs.len == 0 {
return false
}
attr := attrs.first()
if attr is Attribute {
keys := attr.keys()
return 'attribute' in keys
}
return false
}
pub fn (e StructDeclaration) is_heap() bool {
attributes := e.attributes()
for attr in attributes {
if attr is Attribute {
keys := attr.keys()
return 'heap' in keys
}
}
return false
}
pub fn (_ StructDeclaration) stub() {}
================================================
FILE: src/analyzer/psi/StructFieldScope.v
================================================
module psi
pub struct StructFieldScope {
PsiElementImpl
}
pub fn (n StructFieldScope) is_mutable_public() (bool, bool) {
text := n.get_text()
return text.contains('mut'), text.contains('pub')
}
fn (_ &StructFieldScope) stub() {}
================================================
FILE: src/analyzer/psi/StubBase.v
================================================
module psi
import tree_sitter_v.bindings
pub type StubId = int
const non_stubbed_element = StubId(-1)
@[params]
pub struct StubData {
pub:
text string
comment string
receiver string
additional string
}
@[heap]
pub struct StubBase {
StubData
pub:
name string
identifier_text_range TextRange
text_range TextRange
parent_id StubId
stub_type StubType
pub mut:
stub_list &StubList = unsafe { nil }
id StubId
}
pub fn new_stub_base(parent &StubElement, stub_type StubType, name string, identifier_text_range TextRange,
text_range TextRange, data StubData) &StubBase {
mut stub_list := if parent is StubBase {
if !isnil(parent.stub_list) { parent.stub_list } else { &StubList{} }
} else {
&StubList{}
}
parent_id := if !isnil(parent) { parent.id() } else { non_stubbed_element }
mut stub := &StubBase{
name: name
text: data.text
comment: data.comment
receiver: data.receiver
additional: data.additional
identifier_text_range: identifier_text_range
text_range: text_range
stub_list: stub_list
parent_id: parent_id
stub_type: stub_type
}
stub_list.add_stub(mut stub, parent)
return stub
}
pub fn new_root_stub(path string) &StubBase {
mut stub_list := &StubList{
path: path
}
mut stub := &StubBase{
name: ''
stub_list: stub_list
parent_id: -1
stub_type: .root
}
stub_list.add_stub(mut stub, unsafe { nil })
return stub
}
pub fn (s &StubBase) id() StubId {
return s.id
}
pub fn (s &StubBase) stub_type() StubType {
return s.stub_type
}
pub fn (s &StubBase) element_type() bindings.NodeType {
return match s.stub_type {
.root { .unknown }
.function_declaration { .function_declaration }
.method_declaration { .function_declaration }
.static_method_declaration { .static_method_declaration }
.static_receiver { .static_receiver }
.receiver { .receiver }
.signature { .signature }
.parameter_list { .parameter_list }
.parameter_declaration { .parameter_declaration }
.struct_declaration { .struct_declaration }
.interface_declaration { .interface_declaration }
.interface_method_declaration { .interface_method_definition }
.field_declaration { .struct_field_declaration }
.constant_declaration { .const_definition }
.type_alias_declaration { .type_declaration }
.enum_declaration { .enum_declaration }
.enum_field_definition { .enum_field_definition }
.struct_field_scope { .struct_field_scope }
.attributes { .attributes }
.attribute { .attribute }
.attribute_expression { .attribute_expression }
.value_attribute { .value_attribute }
// types
.plain_type { .plain_type }
.type_reference_expression { .type_reference_expression }
.qualified_type { .qualified_type }
.pointer_type { .pointer_type }
.wrong_pointer_type { .wrong_pointer_type }
.array_type { .array_type }
.fixed_array_type { .fixed_array_type }
.function_type { .function_type }
.generic_type { .generic_type }
.map_type { .map_type }
.channel_type { .channel_type }
.shared_type { .shared_type }
.thread_type { .thread_type }
.multi_return_type { .multi_return_type }
.option_type { .option_type }
.result_type { .result_type }
.type_parameters { .type_parameters }
//
.visibility_modifiers { .visibility_modifiers }
.import_list { .import_list }
.import_declaration { .import_declaration }
.import_spec { .import_spec }
.import_path { .import_path }
.import_name { .import_name }
.import_alias { .import_alias }
.selective_import_list { .selective_import_list }
.module_clause { .module_clause }
.reference_expression { .reference_expression }
.generic_parameters { .generic_parameters }
.generic_parameter { .generic_parameter }
.global_variable { .global_var_definition }
.embedded_definition { .embedded_definition }
}
}
pub fn (s StubBase) name() string {
return s.name
}
pub fn (s StubBase) text() string {
return s.text
}
pub fn (s StubBase) receiver() string {
return s.receiver
}
pub fn (s StubBase) text_range() TextRange {
return s.identifier_text_range
}
fn (s StubBase) get_psi() ?PsiElement {
return StubbedElementType{}.create_psi(s)
}
fn (s &StubBase) parent_of_type(typ StubType) ?StubElement {
mut res := &StubBase{
...s
}
for {
parent := res.parent_stub()?
if parent is StubBase {
res = unsafe { parent }
} else {
return none
}
if res.stub_type == typ {
return res
}
}
return none
}
fn (s &StubBase) sibling_of_type_backward(typ StubType) ?StubElement {
mut res := &StubBase{
...s
}
for {
prev := res.prev_sibling()?
if prev is StubBase {
res = unsafe { prev }
} else {
return none
}
if res.stub_type == typ {
return res
}
}
return none
}
fn (s &StubBase) parent_stub() ?&StubElement {
if s.parent_id == -1 {
return none
}
return s.stub_list.get_stub(s.parent_id) or { return none }
}
fn (s &StubBase) get_child_by_type(typ StubType) ?StubElement {
return s.stub_list.get_child_by_type(s.id, typ)
}
fn (s &StubBase) get_children_by_type(typ StubType) []StubElement {
return s.stub_list.get_children_stubs(s.id).filter(it.stub_type() == typ)
}
fn (s &StubBase) has_child_of_type(typ StubType) bool {
return s.stub_list.has_child_of_type(s.id, typ)
}
fn (s &StubBase) prev_sibling() ?&StubElement {
return s.stub_list.prev_sibling(s.id)
}
fn (s &StubBase) next_sibling() ?&StubElement {
return s.stub_list.next_sibling(s.id)
}
pub fn (s &StubBase) children_stubs() []StubElement {
return s.stub_list.get_children_stubs(s.id)
}
fn (s &StubBase) first_child() ?&StubElement {
return s.stub_list.first_child(s.id)
}
fn (s &StubBase) last_child() ?&StubElement {
return s.stub_list.last_child(s.id)
}
================================================
FILE: src/analyzer/psi/StubBasedPsiElement.v
================================================
module psi
// StubIndexKey describes the various types of indexes that are built on `index.StubTree`.
// These indexes allow us to quickly find the desired definitions by name in all indexed files,
// including the standard library and third-party libraries outside the project.
pub enum StubIndexKey as u8 {
functions
methods
static_methods
structs
interfaces
constants
type_aliases
enums
attributes
global_variables
methods_fingerprint
fields_fingerprint
interface_methods_fingerprint
interface_fields_fingerprint
modules_fingerprint
// See count_index_keys
}
// IndexSink describes the index creator interface.
// The `occurrence()` method is called for every stub in the file.
// See `StubbedElementType.index_stub()` for an example of calling this method.
//
// The `key` parameter is the index type for which the entry is to be created.
// The `value` parameter is the string that will be used as the value in the index.
// For example, for the `functions` index, this would be the name of the function.
pub interface IndexSink {
mut:
occurrence(key StubIndexKey, value string)
}
// StubBasedPsiElement describes a marker interface for PSI elements,
// from which `index.StubTree` will be built, on which stub indexes will be built.
//
// PSI elements that implement this interface can be created from
// both ASTs and stubs (`psi.StubBase`).
// This allows them to be processed uniformly when resolving names and other
// processing, since there is no difference whether we are processing a real
// AST tree or a tree of stubs from memory.
pub interface StubBasedPsiElement {
stub() // marker method
}
================================================
FILE: src/analyzer/psi/StubElement.v
================================================
module psi
// StubElement describes the interface of any stub.
pub interface StubElement {
id() StubId
name() string
text() string
receiver() string
stub_type() StubType
text_range() TextRange
parent_stub() ?&StubElement
first_child() ?&StubElement
children_stubs() []StubElement
get_child_by_type(typ StubType) ?StubElement
has_child_of_type(typ StubType) bool
get_children_by_type(typ StubType) []StubElement
prev_sibling() ?&StubElement
parent_of_type(typ StubType) ?StubElement
get_psi() ?PsiElement
}
pub fn (elements []StubElement) get_psi() []PsiElement {
mut result := []PsiElement{cap: elements.len}
for element in elements {
result << element.get_psi() or { continue }
}
return result
}
pub fn is_valid_stub(s StubElement) bool {
if s is StubBase {
return !isnil(s) && !isnil(s.stub_list)
}
return !isnil(s)
}
================================================
FILE: src/analyzer/psi/StubIndex.v
================================================
@[translated]
module psi
import time
import os
import loglib
__global stubs_index = StubIndex{}
const count_index_keys = 15 // StubIndexKey
const count_stub_index_location_keys = 5 // StubIndexLocationKind
// StubIndexLocationKind describes the type of index.
// same as `IndexingRootKind`
pub enum StubIndexLocationKind {
standard_library
modules
stubs
workspace
}
pub struct StubIndex {
pub mut:
sinks []StubIndexSink
// module_to_files describes how to map the full name of a module to a list
// of files that this module contains.
module_to_files map[string][]StubIndexSink
// file_to_module describes the mapping of a file path to the full name
// of the module that this file belongs to.
file_to_module map[string]string
// data defines the index data that allows you to get the description of the element
// in 2 accesses to the array elements and one lookup by key.
data [count_stub_index_location_keys][count_index_keys]map[string]StubResult
// all_elements_by_modules contains all top-level elements in the module.
all_elements_by_modules [count_stub_index_location_keys]map[string][]PsiElement
// types_by_modules contains all top-level types in the module.
types_by_modules [count_stub_index_location_keys]map[string][]PsiElement
}
pub fn new_stubs_index(sinks []StubIndexSink) &StubIndex {
mut index := &StubIndex{
sinks: sinks
module_to_files: map[string][]StubIndexSink{}
all_elements_by_modules: unsafe { [count_stub_index_location_keys]map[string][]PsiElement{} }
types_by_modules: unsafe { [count_stub_index_location_keys]map[string][]PsiElement{} }
}
for i in 0 .. count_stub_index_location_keys {
for j in 0 .. count_index_keys {
index.data[i][j] = map[string]StubResult{}
}
}
watch := time.new_stopwatch(auto_start: true)
for sink in sinks {
index.update_index_from_sink(sink)
}
loglib.with_duration(watch.elapsed()).log_one(.info, 'Build stubs index')
return index
}
pub fn (mut s StubIndex) sub_indexes_from_sink(sink StubIndexSink) {
unsafe { s.module_to_files[sink.stub_list.module_fqn] << sink }
s.file_to_module[sink.stub_list.path] = sink.stub_list.module_fqn
}
pub fn (mut s StubIndex) update_index_from_sink(sink StubIndexSink) {
element_type := StubbedElementType{}
s.sub_indexes_from_sink(sink)
for index_id, datum in sink.data {
kind := sink.kind
mut mp := s.data[kind][index_id]
for name, ids in datum {
mut stubs_result := []&StubBase{cap: ids.len}
mut psi_result := []PsiElement{cap: ids.len}
mut top_level_elements_psi_result := []PsiElement{cap: ids.len}
mut top_level_types_elements_psi_result := []PsiElement{cap: ids.len}
for stub_id in ids {
stub := sink.stub_list.index_map[stub_id] or { continue }
stubs_result << stub
element := element_type.create_psi(stub) or { continue }
psi_result << element
if stub.stub_type in [
.function_declaration,
.constant_declaration,
.global_variable,
] {
top_level_elements_psi_result << element
}
if stub.stub_type in [
.struct_declaration,
.interface_declaration,
.enum_declaration,
.type_alias_declaration,
] {
top_level_types_elements_psi_result << element
}
}
mut data_by_name := unsafe { mp[name] }
data_by_name.stubs << stubs_result
data_by_name.psis << psi_result
mp[name] = data_by_name
module_fqn := sink.module_fqn()
// V treat different '' (different cap) as different keys
module_key := if module_fqn == '' { '' } else { module_fqn }
s.types_by_modules[kind][module_key] << top_level_types_elements_psi_result
s.all_elements_by_modules[kind][module_key] << top_level_elements_psi_result
s.all_elements_by_modules[kind][module_key] << top_level_types_elements_psi_result
}
s.data[kind][index_id] = mp.move()
}
}
pub fn (mut s StubIndex) update_stubs_index(changed_sinks []StubIndexSink, all_sinks []StubIndexSink) {
loglib.log_one(.info, 'Updating stubs index...')
loglib.log_one(.info, 'Changed files: ${changed_sinks.len}')
s.sinks = all_sinks
mut is_workspace_changes := false
for sink in changed_sinks {
if sink.kind == .workspace {
is_workspace_changes = true
break
}
}
if !is_workspace_changes {
return
}
s.module_to_files = map[string][]StubIndexSink{}
s.file_to_module = map[string]string{}
// clear all workspace index
s.data[StubIndexLocationKind.workspace] = [count_index_keys]map[string]StubResult{}
for i in 0 .. count_index_keys {
s.data[StubIndexLocationKind.workspace][i] = map[string]StubResult{}
}
s.all_elements_by_modules[StubIndexLocationKind.workspace] = map[string][]PsiElement{}
s.types_by_modules[StubIndexLocationKind.workspace] = map[string][]PsiElement{}
for sink in all_sinks {
if sink.kind != .workspace {
// for non workspace sinks we just update the module_to_files and file_to_module maps
s.sub_indexes_from_sink(sink)
continue
}
s.update_index_from_sink(sink)
}
}
// get_all_elements_from returns a list of all PSI elements defined in the given index.
//
// Example:
// ```
// // gets all the elements defined in the current project
// stubs_index.get_all_elements_from(.workspace)
// ```
pub fn (s &StubIndex) get_all_elements_from(kind StubIndexLocationKind) []PsiElement {
data := s.data[kind]
mut all_len := 0
$for field in StubIndexKey.values {
res := data[field.value]
for _, stubs in res {
all_len += stubs.psis.len
}
}
mut elements := []PsiElement{cap: all_len}
$for key in StubIndexKey.values {
res := data[key.value]
for _, stubs in res {
for psi in stubs.psis {
elements << psi
}
}
}
return elements
}
// get_all_elements_from_by_key returns a list of all PSI elements defined in the given index for the given key.
//
// Example:
// ```
// // gets all the functions defined in the current project
// stubs_index.get_all_elements_from_by_key(.workspace, .functions)
// ```
pub fn (s &StubIndex) get_all_elements_from_by_key(from StubIndexLocationKind, key StubIndexKey) []PsiElement {
data := s.data[from]
mp := data[key]
mut elements := []PsiElement{cap: mp.len}
for _, res in mp {
elements << res.psis
}
return elements
}
pub fn (s &StubIndex) get_all_elements_from_file(file string) []PsiElement {
mut elements := []PsiElement{cap: 20}
for sink in s.sinks {
if sink.stub_list.path != file {
continue
}
$for key in StubIndexKey.values {
if key.value !in [.attributes, .fields_fingerprint, .methods_fingerprint] {
elements << s.get_all_elements_from_sink_by_key(key.value, sink)
}
}
}
return elements
}
// get_all_declarations_from_module returns a list of all PSI elements defined in the given module.
pub fn (s &StubIndex) get_all_declarations_from_module(module_fqn string, only_types bool) []PsiElement {
if only_types {
if elements := s.types_by_modules[StubIndexLocationKind.workspace][module_fqn] {
return elements
}
$for value in StubIndexLocationKind.values {
if value.value != StubIndexLocationKind.workspace {
if elements := s.types_by_modules[value.value][module_fqn] {
return elements
}
}
}
}
// first try to get the elements from the workspace, if not found, try to get them from the other locations
if elements := s.all_elements_by_modules[StubIndexLocationKind.workspace][module_fqn] {
return elements
}
$for value in StubIndexLocationKind.values {
if value.value != StubIndexLocationKind.workspace {
if elements := s.all_elements_by_modules[value.value][module_fqn] {
return elements
}
}
}
return []
}
pub fn (s &StubIndex) get_all_sinks_from_module(module_fqn string) []StubIndexSink {
return s.module_to_files[module_fqn] or { return []StubIndexSink{} }
}
pub fn (s &StubIndex) get_all_sink_depends_on(module_fqn string) []StubIndexSink {
mut sinks := []StubIndexSink{cap: 10}
for sink in s.sinks {
if sink.kind != .workspace {
continue
}
for imported in sink.imported_modules {
if s.find_real_module_fqn(imported) == module_fqn {
sinks << sink
break
}
}
}
return sinks
}
pub fn (s &StubIndex) get_sink_for_file(file string) ?StubIndexSink {
for sink in s.sinks {
if sink.stub_list.path == file {
return sink
}
}
return none
}
// get_elements_by_name returns the definitions of the element with the given name from the given index.
pub fn (s &StubIndex) get_elements_by_name(key StubIndexKey, name string) []PsiElement {
mut elements := []PsiElement{cap: 5}
$for value in StubIndexLocationKind.values {
data := s.data[value.value]
res := data[key]
if found := res[name] {
elements << found.psis
}
}
return elements
}
pub fn (s &StubIndex) get_elements_from_by_name(from StubIndexLocationKind, key StubIndexKey, name string) []PsiElement {
mut elements := []PsiElement{cap: 5}
data := s.data[from]
res := data[key]
if found := res[name] {
elements << found.psis
}
return elements
}
// get_any_elements_by_name returns the definitions of the element with the given name.
pub fn (s &StubIndex) get_any_elements_by_name(name string) []PsiElement {
mut elements := []PsiElement{cap: 5}
$for value in StubIndexLocationKind.values {
data := s.data[value.value]
$for key in StubIndexKey.values {
if key.value !in [.methods, .attributes] {
res := data[key.value]
if found := res[name] {
elements << found.psis
}
}
}
}
return elements
}
// get_module_qualified_name returns the fully qualified name of the module in which the file is defined.
pub fn (s &StubIndex) get_module_qualified_name(file string) string {
return s.file_to_module[file] or { '' }
}
// get_module_root returns the module's root directory.
pub fn (s &StubIndex) get_module_root(module_fqn string) string {
files := s.module_to_files[module_fqn] or { return '' }
first := files[0] or { return '' }
return os.dir(first.stub_list.path)
}
pub fn (s &StubIndex) get_modules_by_name(name string) []PsiElement {
mut elements := []PsiElement{cap: 5}
$for value in StubIndexLocationKind.values {
data := s.data[value.value]
res := data[int(StubIndexKey.modules_fingerprint)]
if found := res[name] {
elements << found.psis
}
}
return elements
}
// get_all_modules returns all known modules.
pub fn get_all_modules() []string {
return stubs_index.module_to_files.keys()
}
fn (_ &StubIndex) get_all_elements_from_sink_by_key(key StubIndexKey, sink StubIndexSink) []PsiElement {
data := sink.data[int(key)] or { return [] }
element_type := StubbedElementType{}
mut elements := []PsiElement{cap: data.len}
for _, stub_ids in data {
for stub_id in stub_ids {
stub := sink.stub_list.index_map[stub_id] or { continue }
elements << element_type.create_psi(stub) or { continue }
}
}
return elements
}
struct StubResult {
mut:
stubs []&StubBase
psis []PsiElement
}
pub fn (s &StubIndex) find_real_module_fqn(name string) string {
workspace_idx := int(StubIndexLocationKind.workspace)
workspace_modules := s.all_elements_by_modules[workspace_idx]
if name in workspace_modules {
return name
}
suffix := '.' + name
for mod_fqn, _ in workspace_modules {
if mod_fqn.ends_with(suffix) {
return mod_fqn
}
}
$for kind in StubIndexLocationKind.values {
if kind.value != StubIndexLocationKind.workspace {
modules := s.all_elements_by_modules[kind.value]
if name in modules {
return name
}
}
}
return name
}
================================================
FILE: src/analyzer/psi/StubIndexSink.v
================================================
module psi
@[heap]
pub struct StubIndexSink {
pub mut:
stub_id StubId
stub_list &StubList = unsafe { nil } // List of stubs in the current file for which the index is being built.
imported_modules []string
kind StubIndexLocationKind
data map[int]map[string][]StubId
}
const non_fqn_keys = [StubIndexKey.global_variables, .methods_fingerprint, .fields_fingerprint,
.interface_methods_fingerprint, .interface_fields_fingerprint, .methods, .static_methods,
.attributes, .modules_fingerprint]
fn (mut s StubIndexSink) occurrence(key StubIndexKey, value string) {
module_fqn := s.module_fqn()
resulting_value := if module_fqn != '' && key !in non_fqn_keys {
'${module_fqn}.${value}'
} else {
value
}
s.data[int(key)][resulting_value] << s.stub_id
}
@[inline]
pub fn (s StubIndexSink) module_fqn() string {
if s.stub_list == unsafe { nil } {
return ''
}
return s.stub_list.module_fqn
}
================================================
FILE: src/analyzer/psi/StubList.v
================================================
module psi
// StubList describes a way to store all stubs in a specific file.
// Storing stubs as a table is more efficient than storing them as a tree, and
// also makes it easier to serialize stubs to a file.
@[heap]
pub struct StubList {
pub mut:
// module_fqn is the fully qualified name of the module from the root, eg `foo.bar` or `foo.bar.baz`,
// if no module is defined then the empty string.
module_fqn string
path string // absolute path to the file
index_map map[StubId]&StubBase
child_map map[StubId][]int
}
fn (s StubList) root() &StubBase {
return s.index_map[0] or {
// should never happen
return new_root_stub('unknown file')
}
}
fn (s &StubList) get_stub(id StubId) ?&StubBase {
if id < 0 {
return none
}
return s.index_map[id] or { none }
}
fn (mut s StubList) add_stub(mut stub StubBase, parent &StubElement) {
stub_id := s.index_map.len
stub.id = stub_id
s.index_map[stub_id] = stub
// add stub to parent's children
parent_id := if parent != unsafe { nil } && parent is StubBase { parent.id } else { -1 }
mut parent_children := s.child_map[parent_id]
parent_children << stub_id
s.child_map[parent_id] = parent_children
}
fn (s &StubList) first_child(id StubId) ?&StubElement {
stub := s.get_stub(id)?
children_ids := s.child_map[stub.id()]
if children_ids.len == 0 {
return none
}
child_id := children_ids.first()
return s.index_map[child_id] or { return none }
}
fn (s &StubList) last_child(id StubId) ?&StubElement {
stub := s.get_stub(id)?
children_ids := s.child_map[stub.id()]
if children_ids.len == 0 {
return none
}
child_id := children_ids.last()
return s.index_map[child_id] or { return none }
}
fn (s &StubList) get_child_by_type(id StubId, typ StubType) ?StubElement {
stub_ids := s.child_map[id]
for stub_id in stub_ids {
stub := s.index_map[stub_id] or { continue }
if stub.stub_type == typ {
return stub
}
}
return none
}
fn (s &StubList) has_child_of_type(id StubId, typ StubType) bool {
stub_ids := s.child_map[id]
for stub_id in stub_ids {
stub := s.index_map[stub_id] or { continue }
if stub.stub_type == typ {
return true
}
}
return false
}
fn (s &StubList) get_children_stubs(id StubId) []StubElement {
stub_ids := s.child_map[id]
mut stubs := []StubElement{cap: stub_ids.len}
for stub_id in stub_ids {
stubs << s.index_map[stub_id] or { continue }
}
return stubs
}
fn (s &StubList) prev_sibling(id StubId) ?&StubElement {
stub := s.get_stub(id)?
parent := stub.parent_stub()?
children_ids := s.child_map[parent.id()]
index := children_ids.index(id)
if index == 0 || index == -1 {
return none
}
prev_id := children_ids[index - 1]
return s.index_map[prev_id] or { return none }
}
fn (s &StubList) next_sibling(id StubId) ?&StubElement {
stub := s.get_stub(id)?
parent := stub.parent_stub()?
children_ids := s.child_map[parent.id()]
index := children_ids.index(id)
if index == 0 || index == -1 {
return none
}
prev_id := children_ids[index + 1] or { return none }
return s.index_map[prev_id] or { return none }
}
================================================
FILE: src/analyzer/psi/StubbedElementTypeImpl.v
================================================
module psi
import utils
import tree_sitter_v.bindings
pub enum StubType as u8 {
root
function_declaration
method_declaration
static_method_declaration
static_receiver
receiver
signature
parameter_list
parameter_declaration
struct_declaration
interface_declaration
interface_method_declaration
enum_declaration
field_declaration
struct_field_scope
enum_field_definition
constant_declaration
type_alias_declaration
attributes
attribute
attribute_expression
value_attribute
// types
plain_type
type_reference_expression
qualified_type
pointer_type
wrong_pointer_type
array_type
fixed_array_type
function_type
generic_type
map_type
channel_type
shared_type
thread_type
multi_return_type
option_type
result_type
type_parameters
//
visibility_modifiers
import_list
import_declaration
import_spec
import_path
import_name
import_alias
selective_import_list
module_clause
reference_expression
generic_parameters
generic_parameter
global_variable
embedded_definition
}
pub fn node_type_to_stub_type(typ bindings.NodeType) StubType {
return match typ {
.function_declaration { .function_declaration }
.receiver { .receiver }
.static_method_declaration { .static_method_declaration }
.static_receiver { .static_receiver }
.signature { .signature }
.parameter_list { .parameter_list }
.parameter_declaration { .parameter_declaration }
.struct_declaration { .struct_declaration }
.interface_declaration { .interface_declaration }
.interface_method_definition { .interface_method_declaration }
.struct_field_declaration { .field_declaration }
.const_definition { .constant_declaration }
.type_declaration { .type_alias_declaration }
.enum_declaration { .enum_declaration }
.enum_field_definition { .enum_field_definition }
.struct_field_scope { .struct_field_scope }
.attributes { .attributes }
.attribute { .attribute }
.attribute_expression { .attribute_expression }
.value_attribute { .value_attribute }
// types
.plain_type { .plain_type }
.type_reference_expression { .type_reference_expression }
.qualified_type { .qualified_type }
.pointer_type { .pointer_type }
.wrong_pointer_type { .wrong_pointer_type }
.array_type { .array_type }
.fixed_array_type { .fixed_array_type }
.function_type { .function_type }
.generic_type { .generic_type }
.map_type { .map_type }
.channel_type { .channel_type }
.shared_type { .shared_type }
.thread_type { .thread_type }
.multi_return_type { .multi_return_type }
.option_type { .option_type }
.result_type { .result_type }
.type_parameters { .type_parameters }
// types end
.visibility_modifiers { .visibility_modifiers }
.import_list { .import_list }
.import_declaration { .import_declaration }
.import_spec { .import_spec }
.import_path { .import_path }
.import_name { .import_name }
.import_alias { .import_alias }
.selective_import_list { .selective_import_list }
.module_clause { .module_clause }
.reference_expression { .reference_expression }
.generic_parameters { .generic_parameters }
.generic_parameter { .generic_parameter }
.global_var_definition { .global_variable }
.embedded_definition { .embedded_definition }
else { .root }
}
}
pub struct StubbedElementType {}
pub fn (_ &StubbedElementType) index_stub(stub &StubBase, mut sink IndexSink) {
if stub.stub_list.path.ends_with('_test.v') {
return
}
if stub.stub_type == .module_clause {
sink.occurrence(.modules_fingerprint, stub.name())
return
}
if stub.stub_type == .function_declaration {
name := stub.name()
if name.starts_with('test_') {
return
}
sink.occurrence(.functions, name)
}
if stub.stub_type == .method_declaration {
receiver := stub.receiver()
sink.occurrence(.methods, receiver)
sink.occurrence(.methods_fingerprint, stub.additional)
}
if stub.stub_type == .static_method_declaration {
receiver := stub.receiver()
sink.occurrence(.static_methods, receiver)
}
if stub.stub_type == .struct_declaration {
name := stub.name()
if name.ends_with('Attribute') {
// convert DeprecatedAfter to deprecated_after
clear_name := utils.pascal_case_to_snake_case(name.trim_string_right('Attribute'))
sink.occurrence(.attributes, clear_name)
return
}
sink.occurrence(.structs, name)
}
if stub.stub_type == .interface_declaration {
sink.occurrence(.interfaces, stub.name())
}
if stub.stub_type == .interface_method_declaration {
sink.occurrence(.interface_methods_fingerprint, stub.additional)
}
if stub.stub_type == .enum_declaration {
sink.occurrence(.enums, stub.name())
}
if stub.stub_type == .constant_declaration {
sink.occurrence(.constants, stub.name())
}
if stub.stub_type == .type_alias_declaration {
sink.occurrence(.type_aliases, stub.name())
}
if stub.stub_type == .global_variable {
sink.occurrence(.global_variables, stub.name())
}
if stub.stub_type == .field_declaration {
if parent := stub.parent_stub() {
if parent.stub_type() == .struct_declaration {
sink.occurrence(.fields_fingerprint, stub.name)
} else if parent.stub_type() == .interface_declaration {
sink.occurrence(.interface_fields_fingerprint, stub.name)
}
}
}
}
pub fn (_ &StubbedElementType) create_psi(stub &StubBase) ?PsiElement {
stub_type := stub.stub_type
base_psi := new_psi_node_from_stub(stub.id, stub.stub_list)
if stub_type == .function_declaration || stub_type == .method_declaration {
return FunctionOrMethodDeclaration{
PsiElementImpl: base_psi
}
}
if stub_type == .static_method_declaration {
return StaticMethodDeclaration{
PsiElementImpl: base_psi
}
}
if stub_type == .static_receiver {
return StaticReceiver{
PsiElementImpl: base_psi
}
}
if stub_type == .receiver {
return Receiver{
PsiElementImpl: base_psi
}
}
if stub_type == .signature {
return Signature{
PsiElementImpl: base_psi
}
}
if stub_type == .parameter_list {
return ParameterList{
PsiElementImpl: base_psi
}
}
if stub_type == .parameter_declaration {
return ParameterDeclaration{
PsiElementImpl: base_psi
}
}
if stub_type == .type_reference_expression {
return TypeReferenceExpression{
PsiElementImpl: base_psi
}
}
if stub_type == .struct_declaration {
return StructDeclaration{
PsiElementImpl: base_psi
}
}
if stub_type == .interface_declaration {
return InterfaceDeclaration{
PsiElementImpl: base_psi
}
}
if stub_type == .interface_method_declaration {
return InterfaceMethodDeclaration{
PsiElementImpl: base_psi
}
}
if stub_type == .enum_declaration {
return EnumDeclaration{
PsiElementImpl: base_psi
}
}
if stub_type == .enum_field_definition {
return EnumFieldDeclaration{
PsiElementImpl: base_psi
}
}
if stub_type == .field_declaration {
return FieldDeclaration{
PsiElementImpl: base_psi
}
}
if stub_type == .struct_field_scope {
return StructFieldScope{
PsiElementImpl: base_psi
}
}
if stub_type == .constant_declaration {
return ConstantDefinition{
PsiElementImpl: base_psi
}
}
if stub_type == .type_alias_declaration {
return TypeAliasDeclaration{
PsiElementImpl: base_psi
}
}
if stub_type == .attributes {
return Attributes{
PsiElementImpl: base_psi
}
}
if stub_type == .attribute {
return Attribute{
PsiElementImpl: base_psi
}
}
if stub_type == .attribute_expression {
return AttributeExpression{
PsiElementImpl: base_psi
}
}
if stub_type == .value_attribute {
return ValueAttribute{
PsiElementImpl: base_psi
}
}
if stub_type == .plain_type {
return PlainType{
PsiElementImpl: base_psi
}
}
if stub_type == .qualified_type {
return QualifiedType{
PsiElementImpl: base_psi
}
}
if stub_type == .visibility_modifiers {
return VisibilityModifiers{
PsiElementImpl: base_psi
}
}
if stub_type == .import_list {
return ImportList{
PsiElementImpl: base_psi
}
}
if stub_type == .import_declaration {
return ImportDeclaration{
PsiElementImpl: base_psi
}
}
if stub_type == .import_spec {
return ImportSpec{
PsiElementImpl: base_psi
}
}
if stub_type == .import_path {
return ImportPath{
PsiElementImpl: base_psi
}
}
if stub_type == .import_alias {
return ImportAlias{
PsiElementImpl: base_psi
}
}
if stub_type == .selective_import_list {
return SelectiveImportList{
PsiElementImpl: base_psi
}
}
if stub_type == .module_clause {
return ModuleClause{
PsiElementImpl: base_psi
}
}
if stub_type == .reference_expression {
return ReferenceExpression{
PsiElementImpl: base_psi
}
}
if stub_type == .generic_parameters {
return GenericParameters{
PsiElementImpl: base_psi
}
}
if stub_type == .generic_parameter {
return GenericParameter{
PsiElementImpl: base_psi
}
}
if stub_type == .global_variable {
return GlobalVarDefinition{
PsiElementImpl: base_psi
}
}
if stub_type == .embedded_definition {
return EmbeddedDefinition{
PsiElementImpl: base_psi
}
}
return base_psi
}
pub fn (_ &StubbedElementType) get_receiver_type(psi PsiNamedElement) string {
typ := if psi is FunctionOrMethodDeclaration {
receiver := psi.receiver() or { return '' }
receiver.type_element() or { return '' }
} else if psi is StaticMethodDeclaration {
receiver := psi.receiver() or { return '' }
PsiElement(receiver.PsiElementImpl)
} else {
return ''
}
text := typ.get_text().trim_string_left('&')
if text.contains('[') && !text.contains('map[') && !text.starts_with('[') {
// Foo[T] -> Foo
return text.all_before('[')
}
return text
}
pub fn (s &StubbedElementType) create_stub(psi PsiElement, parent_stub &StubBase, module_fqn string) ?&StubBase {
if psi is FunctionOrMethodDeclaration {
text_range := psi.text_range()
identifier_text_range := psi.identifier_text_range()
comment := psi.doc_comment()
mut receiver_type := s.get_receiver_type(psi)
if receiver_type != '' {
if module_fqn != '' {
receiver_type = module_fqn + '.' + receiver_type
}
}
is_method := receiver_type != ''
stub_type := if is_method {
StubType.method_declaration
} else {
StubType.function_declaration
}
fingerprint := if is_method {
psi.fingerprint()
} else {
''
}
return new_stub_base(parent_stub, stub_type, psi.name(), identifier_text_range, text_range,
comment: comment
receiver: receiver_type
additional: fingerprint
)
}
if psi is StaticMethodDeclaration {
text_range := psi.text_range()
identifier_text_range := psi.identifier_text_range()
comment := psi.doc_comment()
mut receiver_type := s.get_receiver_type(psi)
if receiver_type != '' {
if module_fqn != '' {
receiver_type = module_fqn + '.' + receiver_type
}
}
return new_stub_base(parent_stub, .static_method_declaration, psi.name(),
identifier_text_range, text_range,
comment: comment
receiver: receiver_type
)
}
if psi is StructDeclaration {
text_range := psi.text_range()
identifier_text_range := psi.identifier_text_range()
comment := psi.doc_comment()
name := if psi.is_attribute() {
psi.name() + 'Attribute'
} else {
psi.name()
}
return new_stub_base(parent_stub, .struct_declaration, name, identifier_text_range,
text_range,
comment: comment
)
}
if psi is InterfaceDeclaration {
return declaration_stub(*psi, parent_stub, .interface_declaration)
}
if psi is InterfaceMethodDeclaration {
return declaration_stub(*psi, parent_stub, .interface_method_declaration,
additional: psi.fingerprint()
)
}
if psi is StaticReceiver {
return declaration_stub(*psi, parent_stub, .static_receiver, include_text: true)
}
if psi is Receiver {
return declaration_stub(*psi, parent_stub, .receiver, include_text: true)
}
if psi is Signature {
return text_based_stub(*psi, parent_stub, .signature)
}
if psi is ParameterList {
return text_based_stub(*psi, parent_stub, .parameter_list)
}
if psi is ParameterDeclaration {
return declaration_stub(*psi, parent_stub, .parameter_declaration, include_text: true)
}
if psi is EnumDeclaration {
return declaration_stub(*psi, parent_stub, .enum_declaration)
}
if psi is EnumFieldDeclaration {
if expression := psi.last_child() {
text := expression.get_text()
return declaration_stub(*psi, parent_stub, .enum_field_definition, additional: text)
}
return declaration_stub(*psi, parent_stub, .enum_field_definition)
}
if psi is FieldDeclaration {
return declaration_stub(*psi, parent_stub, .field_declaration)
}
if psi is ConstantDefinition {
if expression := psi.last_child() {
text := expression.get_text()
return declaration_stub(*psi, parent_stub, .constant_declaration, additional: text)
}
return declaration_stub(*psi, parent_stub, .constant_declaration)
}
if psi is TypeAliasDeclaration {
return declaration_stub(*psi, parent_stub, .type_alias_declaration)
}
if psi is StructFieldScope {
return text_based_stub(*psi, parent_stub, .struct_field_scope)
}
if psi is Attributes {
text_range := psi.text_range()
return new_stub_base(parent_stub, .attributes, '', text_range, text_range)
}
if psi is Attribute {
return text_based_stub(*psi, parent_stub, .attribute)
}
if psi is AttributeExpression {
return text_based_stub(*psi, parent_stub, .attribute_expression)
}
if psi is ValueAttribute {
return text_based_stub(*psi, parent_stub, .value_attribute)
}
if psi is VisibilityModifiers {
return text_based_stub(*psi, parent_stub, .visibility_modifiers)
}
if psi is ModuleClause {
return declaration_stub(*psi, parent_stub, .module_clause)
}
node_type := psi.node().type_name
if node_is_type(node_type) {
stub_type := node_type_to_stub_type(node_type)
return text_based_stub(psi, parent_stub, stub_type)
}
if psi is ImportSpec {
return declaration_stub(*psi, parent_stub, .import_spec, include_text: true)
}
if node_type in [
.import_list,
.import_declaration,
.import_path,
.import_name,
.import_alias,
.selective_import_list,
] {
stub_type := node_type_to_stub_type(node_type)
return text_based_stub(psi, parent_stub, stub_type,
include_text: node_type !in [
.import_list,
.import_declaration,
.selective_import_list,
]
)
}
if psi is ReferenceExpression {
return text_based_stub(*psi, parent_stub, .reference_expression)
}
if psi is GenericParameters {
return text_based_stub(*psi, parent_stub, .generic_parameters)
}
if psi is GenericParameter {
return declaration_stub(*psi, parent_stub, .generic_parameter)
}
if psi is GlobalVarDefinition {
return declaration_stub(*psi, parent_stub, .global_variable)
}
if psi is EmbeddedDefinition {
return declaration_stub(*psi, parent_stub, .embedded_definition)
}
return none
}
@[params]
struct StubParams {
pub:
include_text bool
additional string
}
@[inline]
pub fn declaration_stub(psi PsiNamedElement, parent_stub &StubElement, stub_type StubType, params StubParams) ?&StubBase {
text_range := (psi as PsiElement).text_range()
identifier_text_range := psi.identifier_text_range()
return new_stub_base(parent_stub, stub_type, psi.name(), identifier_text_range, text_range,
comment: if psi is PsiDocCommentOwner { psi.doc_comment() } else { '' }
text: if params.include_text { (psi as PsiElement).get_text() } else { '' }
additional: params.additional
)
}
@[params]
struct TestStubParams {
pub:
include_text bool = true
}
@[inline]
pub fn text_based_stub(psi PsiElement, parent_stub &StubElement, stub_type StubType, params TestStubParams) ?&StubBase {
text_range := psi.text_range()
return new_stub_base(parent_stub, stub_type, '', text_range, text_range,
text: if params.include_text { psi.get_text() } else { '' }
)
}
@[inline]
pub fn node_is_type(type_name bindings.NodeType) bool {
return type_name in [
.plain_type,
.type_reference_expression,
.qualified_type,
.pointer_type,
.wrong_pointer_type,
.array_type,
.fixed_array_type,
.function_type,
.generic_type,
.map_type,
.channel_type,
.shared_type,
.thread_type,
.multi_return_type,
.option_type,
.result_type,
.type_parameters,
]
}
================================================
FILE: src/analyzer/psi/TextRange.v
================================================
module psi
// TextRange represents a range of text in a file.
pub struct TextRange {
pub:
line int
column int
end_line int
end_column int
}
pub fn (t TextRange) == (other TextRange) bool {
return t.line == other.line && t.column == other.column && t.end_line == other.end_line
&& t.end_column == other.end_column
}
================================================
FILE: src/analyzer/psi/TreeWalker.v
================================================
module psi
import tree_sitter_v.bindings
pub struct TreeWalker {
mut:
already_visited_children bool
cursor bindings.TreeCursor[bindings.NodeType] @[required]
}
pub fn (mut tw TreeWalker) next() ?AstNode {
if !tw.already_visited_children {
if tw.cursor.to_first_child() {
tw.already_visited_children = false
} else if tw.cursor.next() {
tw.already_visited_children = false
} else {
if !tw.cursor.to_parent() {
return none
}
tw.already_visited_children = true
return tw.next()
}
} else {
if tw.cursor.next() {
tw.already_visited_children = false
} else {
if !tw.cursor.to_parent() {
return none
}
return tw.next()
}
}
node := tw.cursor.current_node()?
return node
}
pub fn new_tree_walker(root_node AstNode) TreeWalker {
return TreeWalker{
cursor: root_node.tree_cursor()
}
}
@[inline]
pub fn (mut tw TreeWalker) to_first_child() bool {
return tw.cursor.to_first_child()
}
@[inline]
pub fn (mut tw TreeWalker) to_parent() bool {
return tw.cursor.to_parent()
}
@[inline]
pub fn (mut tw TreeWalker) next_sibling() bool {
return tw.cursor.next()
}
@[inline]
pub fn (tw &TreeWalker) current_node() ?AstNode {
return tw.cursor.current_node()
}
@[inline]
pub fn (mut tw TreeWalker) free() {
unsafe { tw.cursor.raw_cursor.delete() }
}
================================================
FILE: src/analyzer/psi/TypeAliasDeclaration.v
================================================
module psi
import analyzer.psi.types
pub struct TypeAliasDeclaration {
PsiElementImpl
}
pub fn (a &TypeAliasDeclaration) get_type() types.Type {
types_list := a.types()
inner_type := if types_list.len > 0 {
convert_type(types_list[0])
} else {
types.Type(types.unknown_type)
}
return types.new_alias_type(a.name(), a.module_name(), inner_type)
}
pub fn (a &TypeAliasDeclaration) generic_parameters() ?&GenericParameters {
generic_parameters := a.find_child_by_type_or_stub(.generic_parameters)?
if generic_parameters is GenericParameters {
return generic_parameters
}
return none
}
pub fn (a &TypeAliasDeclaration) is_public() bool {
modifiers := a.visibility_modifiers() or { return false }
return modifiers.is_public()
}
pub fn (a &TypeAliasDeclaration) module_name() string {
file := a.containing_file() or { return '' }
return stubs_index.get_module_qualified_name(file.path)
}
pub fn (a TypeAliasDeclaration) doc_comment() string {
if stub := a.get_stub() {
return stub.comment
}
return extract_doc_comment(a)
}
pub fn (a &TypeAliasDeclaration) types() []PlainType {
inner_types := a.find_children_by_type_or_stub(.plain_type)
mut result := []PlainType{cap: inner_types.len}
for type_ in inner_types {
if type_ is PlainType {
result << type_
}
}
return result
}
pub fn (a TypeAliasDeclaration) identifier() ?PsiElement {
return a.find_child_by_type(.identifier)
}
pub fn (a &TypeAliasDeclaration) identifier_text_range() TextRange {
if stub := a.get_stub() {
return stub.identifier_text_range
}
identifier := a.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (a TypeAliasDeclaration) name() string {
if stub := a.get_stub() {
return stub.name
}
identifier := a.identifier() or { return '' }
return identifier.get_text()
}
pub fn (a TypeAliasDeclaration) visibility_modifiers() ?&VisibilityModifiers {
modifiers := a.find_child_by_type_or_stub(.visibility_modifiers)?
if modifiers is VisibilityModifiers {
return modifiers
}
return none
}
fn (_ &TypeAliasDeclaration) stub() {}
================================================
FILE: src/analyzer/psi/TypeCache.v
================================================
@[translated]
module psi
import analyzer.psi.types
import sync
import loglib
__global type_cache = TypeCache{}
pub struct TypeCache {
mut:
mutex sync.RwMutex
data map[string]types.Type
}
pub fn (t &TypeCache) get(element PsiElement) ?types.Type {
t.mutex.@rlock()
defer {
t.mutex.runlock()
}
fingerprint := t.element_fingerprint(element)
return t.data[fingerprint] or { return none }
}
pub fn (mut t TypeCache) put(element PsiElement, typ types.Type) types.Type {
t.mutex.@lock()
defer {
t.mutex.unlock()
}
fingerprint := t.element_fingerprint(element)
t.data[fingerprint] = typ
return typ
}
pub fn (mut t TypeCache) clear() {
t.mutex.@lock()
defer {
t.mutex.unlock()
}
loglib.with_fields({
'cache_size': t.data.len.str()
}).log_one(.info, 'Clearing type cache')
t.data = map[string]types.Type{}
}
@[inline]
fn (_ &TypeCache) element_fingerprint(element PsiElement) string {
file := element.containing_file() or { return '' }
range := element.text_range()
return '${file.path}:${element.node().type_name}:${range.line}:${range.column}:${range.end_column}:${range.end_line}'
}
================================================
FILE: src/analyzer/psi/TypeInferer.v
================================================
module psi
import analyzer.psi.types
pub fn infer_type(elem ?PsiElement) types.Type {
return TypeInferer{}.infer_type(elem)
}
pub fn convert_type(plain_type ?PsiElement) types.Type {
mut visited := map[string]types.Type{}
return TypeInferer{}.convert_type(plain_type, mut visited)
}
pub struct TypeInferer {}
pub fn (t &TypeInferer) infer_type(elem ?PsiElement) types.Type {
element := elem or { return types.unknown_type }
if from_cache := type_cache.get(element) {
return from_cache
}
typ := t.infer_type_impl(elem)
type_cache.put(element, typ)
return typ
}
pub fn (t &TypeInferer) infer_type_impl(elem ?PsiElement) types.Type {
element := elem or { return types.unknown_type }
mut visited := map[string]types.Type{}
match element.node().type_name {
.in_expression, .is_expression, .select_expression {
return types.new_primitive_type('bool')
}
.inc_expression, .dec_expression {
return t.infer_type(element.first_child())
}
.as_type_cast_expression {
return t.infer_type(element.last_child())
}
.spawn_expression, .go_expression {
return types.new_thread_type(t.infer_type(element.last_child()))
}
.parenthesized_expression {
expr := element.find_child_by_name('expression') or { return types.unknown_type }
return t.infer_type(expr)
}
.receive_expression {
operand := element.find_child_by_name('operand') or { return types.unknown_type }
return types.unwrap_channel_type(t.infer_type(operand))
}
else {}
}
match element {
BinaryExpression {
return t.infer_binary_expression_type(element)
}
UnaryExpression {
return t.infer_unary_expression_type(element)
}
OrBlockExpression, ResultPropagationExpression, OptionPropagationExpression {
expr := element.expression() or { return types.unknown_type }
expr_type := t.infer_type(expr)
return types.unwrap_result_or_option_type(expr_type)
}
IndexExpression {
return t.infer_index_expression_type(element)
}
SliceExpression {
return t.infer_slice_expression_type(element)
}
Range {
return t.infer_range_type(element)
}
SelectorExpression {
return t.infer_selector_expression_type(element)
}
ReferenceExpression {
return t.infer_reference_expression_type(element)
}
TypeInitializer {
return t.infer_type_initializer_type(element, mut visited)
}
UnsafeExpression {
return t.infer_unsafe_expression_type(element)
}
IfExpression {
return t.infer_if_expression_type(element)
}
CompileTimeIfExpression {
return t.infer_compile_time_if_expression_type(element)
}
MatchExpression {
return t.infer_match_expression_type(element)
}
ArrayCreation {
return t.infer_array_creation_type(element)
}
MapInitExpression {
return t.infer_map_init_expression_type(element)
}
CallExpression {
return t.infer_call_expression_type(element, mut visited)
}
Literal {
return t.infer_literal_type(element)
}
Signature {
return t.process_signature(element)
}
VarDefinition {
return t.infer_var_definition_type(element)
}
FieldDeclaration {
return t.infer_from_plain_type(element)
}
Receiver {
return t.infer_from_plain_type(element)
}
ParameterDeclaration {
return t.infer_parameter_declaration_type(element)
}
Block {
return t.infer_block_type(element)
}
FunctionLiteral, FunctionOrMethodDeclaration, StaticMethodDeclaration,
InterfaceMethodDeclaration {
signature := element.signature() or { return types.unknown_type }
return t.process_signature(signature)
}
EnumDeclaration, EnumFieldDeclaration, ConstantDefinition {
return element.get_type()
}
TypeReferenceExpression {
return t.infer_type_reference_type(element, mut visited)
}
GlobalVarDefinition {
return t.infer_global_var_definition_type(element, mut visited)
}
EmbeddedDefinition {
return t.infer_embedded_definition_type(element, mut visited)
}
else {
return types.unknown_type
}
}
}
pub fn (t &TypeInferer) infer_binary_expression_type(element BinaryExpression) types.Type {
match element.operator() {
'&&', '||', '==', '!=', '<', '<=', '>', '>=' {
return types.new_primitive_type('bool')
}
'<<' {
return types.new_primitive_type('int')
}
'>>', '>>>' {
return types.new_primitive_type('int')
}
'+', '-', '|', '^', '&', '*', '/' {
left := element.left() or { return types.unknown_type }
if left.node().type_name != .literal {
return t.infer_type(left)
}
right := element.right() or { return types.unknown_type }
return t.infer_type(right)
}
else {
return types.unknown_type
}
}
}
pub fn (t &TypeInferer) infer_unary_expression_type(element UnaryExpression) types.Type {
operator := element.operator()
if operator == '!' {
return types.new_primitive_type('bool')
}
expression := element.expression() or { return types.unknown_type }
expr_type := t.infer_type(expression)
return match operator {
'&' { types.Type(types.new_pointer_type(expr_type)) }
'*' { types.unwrap_pointer_type(expr_type) }
'<-' { types.unwrap_channel_type(expr_type) }
else { expr_type }
}
}
pub fn (t &TypeInferer) infer_index_expression_type(element IndexExpression) types.Type {
expr := element.expression() or { return types.unknown_type }
expr_type := t.infer_type(expr)
return t.infer_index_type(expr_type)
}
pub fn (t &TypeInferer) infer_slice_expression_type(element SliceExpression) types.Type {
expr := element.expression() or { return types.unknown_type }
expr_type := t.infer_type(expr)
if expr_type is types.FixedArrayType {
// [3]int -> []int
return types.new_array_type(expr_type.inner)
}
return expr_type
}
pub fn (t &TypeInferer) infer_compile_time_if_expression_type(element CompileTimeIfExpression) types.Type {
block := element.block()
block_type := t.infer_type(block)
if block_type is types.UnknownType {
else_branch := element.else_branch() or { return types.unknown_type }
return t.infer_type(else_branch)
}
return block_type
}
pub fn (t &TypeInferer) infer_match_expression_type(element MatchExpression) types.Type {
arms := element.arms()
if arms.len == 0 {
return types.unknown_type
}
first := arms.first()
block := first.find_child_by_name('block') or { return types.unknown_type }
return t.infer_type(block)
}
pub fn (t &TypeInferer) infer_array_creation_type(element ArrayCreation) types.Type {
expressions := element.expressions()
if expressions.len == 0 {
return types.new_array_type(types.unknown_type)
}
first_expr := expressions.first()
if element.is_fixed {
return types.new_fixed_array_type(t.infer_type(first_expr), expressions.len)
}
return types.new_array_type(t.infer_type(first_expr))
}
pub fn (t &TypeInferer) infer_map_init_expression_type(element MapInitExpression) types.Type {
file := element.containing_file() or { return types.unknown_type }
module_fqn := file.module_fqn()
key_values := element.key_values()
if key_values.len == 0 {
return types.new_map_type(module_fqn, types.unknown_type, types.unknown_type)
}
first_key_value := key_values.first()
if first_key_value is MapKeyedElement {
key := first_key_value.key() or { return types.unknown_type }
value := first_key_value.value() or { return types.unknown_type }
key_type := t.infer_type(key)
value_type := t.infer_type(value)
return types.new_map_type(module_fqn, key_type, value_type)
}
return types.new_map_type(module_fqn, types.unknown_type, types.unknown_type)
}
pub fn (t &TypeInferer) infer_call_expression_type(element CallExpression, mut visited map[string]types.Type) types.Type {
if grand := element.expression() {
if grand is FunctionLiteral {
signature := grand.signature() or { return types.unknown_type }
return t.convert_type(signature.result(), mut visited)
}
}
return t.infer_call_expr_type(element)
}
pub fn (t &TypeInferer) infer_var_definition_type(element VarDefinition) types.Type {
grand := element.parent_nth(2) or { return types.unknown_type }
if grand.node().type_name == .range_clause {
return t.process_range_clause(element, grand)
}
decl := element.declaration() or { return types.unknown_type }
if init := decl.initializer_of(element) {
typ := t.infer_type(init)
if decl_parent := decl.parent() {
if decl_parent is IfExpression {
return types.unwrap_result_or_option_type(typ)
}
}
if typ is types.MultiReturnType {
parent := element.parent() or { return types.unknown_type }
mut define_index := 0
for index, def in parent.find_children_by_type(.reference_expression) {
if def.is_equal(element) {
define_index = index
break
}
}
inner_types := typ.types
return inner_types[define_index] or { return types.unknown_type }
}
return typ
}
return types.unknown_type
}
pub fn (t &TypeInferer) infer_parameter_declaration_type(element ParameterDeclaration) types.Type {
type_ := t.infer_from_plain_type(element)
if _ := element.find_child_by_name('variadic') {
return types.new_array_type(type_)
}
return type_
}
pub fn (t &TypeInferer) infer_block_type(element Block) types.Type {
last_expression := element.last_expression() or { return types.unknown_type }
return t.infer_type(last_expression)
}
pub fn (t &TypeInferer) infer_global_var_definition_type(element GlobalVarDefinition, mut visited map[string]types.Type) types.Type {
type_element := element.find_child_by_type_or_stub(.plain_type) or { return types.unknown_type }
return t.convert_type(type_element, mut visited)
}
pub fn (t &TypeInferer) infer_embedded_definition_type(element EmbeddedDefinition, mut visited map[string]types.Type) types.Type {
if qualified_type := element.find_child_by_type_or_stub(.qualified_type) {
return t.convert_type_inner(qualified_type, mut visited)
}
if generic_type := element.find_child_by_type_or_stub(.generic_type) {
return t.convert_type_inner(generic_type, mut visited)
}
if ref_expression := element.find_child_by_type_or_stub(.type_reference_expression) {
if ref_expression is TypeReferenceExpression {
return t.infer_type_reference_type(ref_expression, mut visited)
}
}
return types.unknown_type
}
pub fn (t &TypeInferer) infer_reference_expression_type(element ReferenceExpression) types.Type {
if resolved := element.resolve() {
return t.infer_type(resolved)
}
if element.text_matches('it') {
call := get_it_call(element) or { return types.unknown_type }
caller_type := call.caller_type()
if caller_type is types.ArrayType {
return caller_type.inner
}
return types.unknown_type
}
return types.unknown_type
}
pub fn (t &TypeInferer) infer_if_expression_type(element IfExpression) types.Type {
block := element.block()
block_type := t.infer_type(block)
if block_type is types.UnknownType {
else_branch := element.else_branch() or { return types.unknown_type }
return t.infer_type(else_branch)
}
return block_type
}
pub fn (t &TypeInferer) infer_unsafe_expression_type(element UnsafeExpression) types.Type {
block := element.block()
return t.infer_type(block)
}
pub fn (t &TypeInferer) infer_type_initializer_type(element TypeInitializer, mut visited map[string]types.Type) types.Type {
type_element := element.find_child_by_type(.plain_type) or { return types.unknown_type }
return t.convert_type(type_element, mut visited)
}
pub fn (t &TypeInferer) infer_selector_expression_type(element SelectorExpression) types.Type {
resolved := element.resolve() or { return types.unknown_type }
typ := t.infer_type(resolved)
if types.is_generic(typ) {
return GenericTypeInferer{}.infer_generic_fetch(resolved, element, typ)
}
return typ
}
pub fn (t &TypeInferer) infer_range_type(element Range) types.Type {
if element.inclusive() {
left := element.left() or { return types.unknown_type }
return t.infer_type(left)
}
return types.new_array_type(types.new_primitive_type('int'))
}
pub fn (t &TypeInferer) process_signature(signature Signature) types.Type {
params := signature.parameters()
param_types := params.map(fn (it PsiElement) types.Type {
// TODO: support fn (int, string) without names
if it is PsiTypedElement {
return it.get_type()
}
return types.unknown_type
})
result := signature.result()
mut visited := map[string]types.Type{}
result_type := t.convert_type(result, mut visited)
module_fqn := if file := signature.containing_file() {
file.module_fqn()
} else {
''
}
return types.new_function_type(module_fqn, param_types, result_type, result == none)
}
pub fn (t &TypeInferer) process_range_clause(element PsiElement, range PsiElement) types.Type {
right := range.find_child_by_name('right') or { return types.unknown_type }
right_type := types.unwrap_alias_type(t.infer_type(right))
var_definition_list := range.find_child_by_name('left') or { return types.unknown_type }
var_definitions := var_definition_list.find_children_by_type(.var_definition)
if var_definitions.len == 1 {
if right_type is types.ArrayType {
return right_type.inner
}
if right_type is types.FixedArrayType {
return right_type.inner
}
if right_type is types.MapType {
return right_type.value
}
if right_type is types.StructType {
if right_type.name() == 'string' {
return types.new_primitive_type('u8')
}
return t.infer_iterator_struct(right_type)
}
}
mut define_index := 0
for index, def in var_definitions {
if def.is_equal(element) {
define_index = index
break
}
}
if define_index == 0 {
if right_type is types.MapType {
return right_type.key
}
return types.new_primitive_type('int')
}
if define_index == 1 {
if right_type is types.ArrayType {
return right_type.inner
}
if right_type is types.FixedArrayType {
return right_type.inner
}
if right_type is types.MapType {
return right_type.value
}
if right_type is types.StructType {
if right_type.name() == 'string' {
return types.new_primitive_type('u8')
}
return t.infer_iterator_struct(right_type)
}
return types.unknown_type
}
return types.unknown_type
}
pub fn (_ &TypeInferer) infer_iterator_struct(typ types.Type) types.Type {
method := find_method(typ, 'next') or { return types.unknown_type }
if method is FunctionOrMethodDeclaration {
signature := method.signature() or { return types.unknown_type }
func_type := signature.get_type()
if func_type is types.FunctionType {
return types.unwrap_result_or_option_type(func_type.result)
}
}
return types.unknown_type
}
pub fn (t &TypeInferer) infer_call_expr_type(element CallExpression) types.Type {
if element.is_json_decode() {
return types.new_result_type(element.get_json_decode_type(), false)
}
if resolved := element.resolve() {
expr_type := t.infer_type(resolved)
if expr_type is types.FunctionType {
result_type := expr_type.result
if types.is_generic(result_type) {
if resolved is GenericParametersOwner {
return GenericTypeInferer{}.infer_generic_call(element, resolved, result_type)
}
}
if resolved is FunctionOrMethodDeclaration {
if !resolved.is_method() {
return result_type
}
if typ := t.process_map_array_method_call(resolved, expr_type, element) {
return typ
}
}
return result_type
}
}
// most probably type cast expression: PsiElement(node)
// try to resolve as type
expr := element.ref_expression() or { return types.unknown_type }
ref := new_reference(element.containing_file, expr, true)
if resolved := ref.resolve() {
if resolved is PsiTypedElement {
return resolved.get_type()
}
}
return types.unknown_type
}
pub fn (t &TypeInferer) process_map_array_method_call(element FunctionOrMethodDeclaration, element_type types.FunctionType,
expr CallExpression) ?types.Type {
receiver_type := types.unwrap_pointer_type(element.receiver_type())
if types.is_builtin_array_type(receiver_type) {
if typ := t.process_array_method_call(element, element_type, expr) {
return typ
}
}
if types.is_builtin_map_type(receiver_type) {
if typ := t.process_map_method_call(element, expr) {
return typ
}
}
return none
}
pub fn (_ &TypeInferer) process_array_method_call(element FunctionOrMethodDeclaration, element_type types.FunctionType,
expr CallExpression) ?types.Type {
return_type := element_type.result
if return_type is types.VoidPtrType {
caller_type := expr.caller_type()
if caller_type is types.ArrayType {
return caller_type.inner
}
}
if types.is_builtin_array_type(return_type) {
if element.name() == 'map' {
arguments := expr.arguments()
first_arg := arguments[0] or { return none }
first_arg_type := infer_type(first_arg)
// map(fn (int) { ... }) -> array
if first_arg_type is types.FunctionType {
return *types.new_array_type(first_arg_type.result)
}
// map(it > 10) -> array
return *types.new_array_type(first_arg_type)
}
return expr.caller_type()
}
return none
}
pub fn (_ &TypeInferer) process_map_method_call(element FunctionOrMethodDeclaration, expr CallExpression) ?types.Type {
caller_type := types.unwrap_alias_type(expr.caller_type())
if caller_type is types.MapType {
match element.name() {
'keys' { return *types.new_array_type(caller_type.key) }
'values' { return *types.new_array_type(caller_type.value) }
'clone', 'move' { return caller_type }
else { return none }
}
}
return none
}
pub fn (_ &TypeInferer) infer_literal_type(element Literal) types.Type {
child := element.first_child() or { return types.unknown_type }
if child.node().type_name == .interpreted_string_literal
|| child.node().type_name == .raw_string_literal {
return types.string_type
}
if child.node().type_name == .c_string_literal {
return types.new_pointer_type(types.new_primitive_type('u8'))
}
if child.node().type_name == .int_literal {
return types.new_primitive_type('int')
}
if child.node().type_name == .float_literal {
return types.new_primitive_type('f64')
}
if child.node().type_name == .rune_literal {
return types.new_primitive_type('rune')
}
if child.node().type_name == .true_ || child.node().type_name == .false_ {
return types.new_primitive_type('bool')
}
if child.node().type_name == .nil_ {
return types.new_primitive_type('voidptr')
}
if child.node().type_name == .none_ {
return types.new_primitive_type('none')
}
return types.unknown_type
}
pub fn (t &TypeInferer) infer_index_type(typ types.Type) types.Type {
if typ is types.ArrayType {
return typ.inner
}
if typ is types.FixedArrayType {
return typ.inner
}
if typ is types.MapType {
return typ.value
}
if typ is types.StructType {
if typ.name == 'string' {
return types.new_primitive_type('u8')
}
return types.unknown_type
}
if typ is types.PointerType {
return typ.inner
}
return types.unknown_type
}
pub fn (t &TypeInferer) convert_type(plain_type ?PsiElement, mut visited map[string]types.Type) types.Type {
typ := plain_type or { return types.unknown_type }
if typ !is PlainType {
return types.unknown_type
}
type_text := typ.get_text()
if type_text in visited {
return visited[type_text]
}
mut child := typ.first_child_or_stub() or { return types.unknown_type }
for child.element_type() == .unknown {
child = child.next_sibling_or_stub() or { return types.unknown_type }
}
type_inner := t.convert_type_inner(child, mut visited)
visited[type_text] = type_inner
return type_inner
}
pub fn (t &TypeInferer) convert_type_inner(element PsiElement, mut visited map[string]types.Type) types.Type {
if element.element_type() == .pointer_type {
inner := element.last_child_or_stub()
return types.new_pointer_type(t.convert_type(inner, mut visited))
}
if element.element_type() == .array_type {
inner := element.last_child_or_stub()
return types.new_array_type(t.convert_type(inner, mut visited))
}
if element.element_type() == .fixed_array_type {
// TODO: parse size
inner := element.last_child_or_stub()
return types.new_array_type(t.convert_type(inner, mut visited))
}
if element.element_type() == .thread_type {
inner := element.last_child_or_stub()
return types.new_thread_type(t.convert_type(inner, mut visited))
}
if element.element_type() == .channel_type {
inner := element.last_child_or_stub()
return types.new_channel_type(t.convert_type(inner, mut visited))
}
if element.element_type() == .option_type {
inner := element.last_child_or_stub()
return types.new_option_type(t.convert_type(inner, mut visited), inner == none)
}
if element.element_type() == .result_type {
inner := element.last_child_or_stub()
return types.new_result_type(t.convert_type(inner, mut visited), inner == none)
}
if element.element_type() == .multi_return_type {
inner_type_elements := element.find_children_by_type_or_stub(.plain_type)
inner_types := inner_type_elements.map(t.convert_type(it, mut visited))
return types.new_multi_return_type(inner_types)
}
if element.element_type() == .map_type {
file := element.containing_file() or { return types.unknown_type }
module_fqn := file.module_fqn()
types_inner := element.find_children_by_type_or_stub(.plain_type)
if types_inner.len != 2 {
return types.new_map_type(module_fqn, types.unknown_type, types.unknown_type)
}
key := types_inner[0]
value := types_inner[1]
return types.new_map_type(module_fqn, t.convert_type(key, mut visited), t.convert_type(value, mut
visited))
}
if element.element_type() == .function_type {
signature := element.find_child_by_type_or_stub(.signature) or { return types.unknown_type }
if signature is Signature {
return t.process_signature(signature)
}
return types.unknown_type
}
if element.element_type() == .generic_type {
inner_type := if inner := element.find_child_by_type_or_stub(.type_reference_expression) {
if inner is TypeReferenceExpression {
t.infer_type_reference_type(inner, mut visited)
} else {
return types.unknown_type
}
} else if inner_qualified := element.find_child_by_type_or_stub(.qualified_type) {
t.convert_type_inner(inner_qualified, mut visited)
} else {
return types.unknown_type
}
type_parameters := element.find_child_by_type_or_stub(.type_parameters) or {
return inner_type
}
type_parameters_list := type_parameters.find_children_by_type_or_stub(.plain_type)
return types.new_generic_instantiation_type(inner_type, type_parameters_list.map(t.convert_type(it, mut
visited)))
}
if element is QualifiedType {
if ref := element.right() {
if ref is TypeReferenceExpression {
return t.infer_type_reference_type(ref, mut visited)
}
}
}
if element is TypeReferenceExpression {
return t.infer_type_reference_type(element, mut visited)
}
return types.unknown_type
}
fn (t &TypeInferer) infer_type_reference_type(element TypeReferenceExpression, mut visited map[string]types.Type) types.Type {
text := element.get_text()
if types.is_primitive_type(text) {
// fast path
return types.new_primitive_type(text)
}
if text == 'string' {
return types.string_type
}
if text == 'voidptr' {
return types.voidptr_type
}
if text == 'array' {
return types.builtin_array_type
}
if text == 'map' {
return types.builtin_map_type
}
resolved := element.resolve() or { return types.unknown_type }
if resolved is StructDeclaration {
return resolved.get_type()
}
if resolved is InterfaceDeclaration {
return resolved.get_type()
}
if resolved is EnumDeclaration {
return resolved.get_type()
}
if resolved is TypeAliasDeclaration {
name := resolved.name()
visited[name] = types.unknown_type
types_list := resolved.types()
if types_list.len == 0 {
return types.unknown_type
}
first := types_list.first()
alias_type := types.new_alias_type(name, resolved.module_name(), t.convert_type(first, mut
visited))
visited[name] = alias_type
return alias_type
}
if resolved is GenericParameter {
return types.new_generic_type(element.name())
}
return types.unknown_type
}
fn (t &TypeInferer) infer_from_plain_type(element PsiElement) types.Type {
plain_typ := element.find_child_by_type_or_stub(.plain_type) or { return types.unknown_type }
mut visited := map[string]types.Type{}
return t.convert_type(plain_typ, mut visited)
}
pub fn (t &TypeInferer) infer_context_type(elem ?PsiElement) types.Type {
element := elem or { return types.unknown_type }
parent := element.parent() or { return types.unknown_type }
if parent.element_type() == .binary_expression {
right := parent.last_child_or_stub() or { return types.unknown_type }
if right.is_equal(element) {
left := parent.first_child_or_stub() or { return types.unknown_type }
return t.infer_type(left)
}
}
if parent.element_type() == .expression_list {
grand := parent.parent() or { return types.unknown_type }
if grand.element_type() == .assignment_statement {
// TODO: support multiple assignments
right_list := grand.last_child_or_stub() or { return types.unknown_type }
right := right_list.first_child_or_stub() or { return types.unknown_type }
if right.is_equal(element) {
left_list := grand.first_child_or_stub() or { return types.unknown_type }
left := left_list.first_child_or_stub() or { return types.unknown_type }
return t.infer_type(left)
}
}
}
if parent.element_type() == .match_expression_list {
match_expr := parent.parent_of_type(.match_expression) or { return types.unknown_type }
if match_expr is MatchExpression {
return t.infer_type(match_expr.expression())
}
}
if parent is KeyedElement {
field := parent.field() or { return types.unknown_type }
ref := field.reference_expression() or { return types.unknown_type }
resolved := ref.resolve() or { return types.unknown_type }
return t.infer_from_plain_type(resolved)
}
if parent.element_type() == .argument {
call_expression := parent.parent_nth(2) or { return types.unknown_type }
if call_expression is CallExpression {
called := call_expression.resolve() or { return types.unknown_type }
if called is FunctionOrMethodDeclaration {
if called.is_method()
&& called.receiver_type().qualified_name() == types.flag_enum_type.qualified_name() {
// when color.has(.red)
return call_expression.caller_type()
}
}
typ := t.infer_type(called)
if typ is types.FunctionType {
index := call_expression.parameter_index_on_offset(parent.node().start_byte())
param_type := typ.params[index] or { return types.unknown_type }
return param_type
}
}
}
if parent.element_type() == .expression_list {
grand := parent.parent() or { return types.unknown_type }
if grand.element_type() == .return_statement {
return t.enclosing_function_return_type(grand)
}
}
if parent.element_type() == .simple_statement {
if_expr := parent.parent_nth(2) or { return types.unknown_type }
if if_expr is IfExpression {
return_stmt := if_expr.parent_nth(2) or { return types.unknown_type }
if return_stmt.element_type() == .return_statement {
return t.enclosing_function_return_type(return_stmt)
}
}
match_expr := if_expr.parent_nth(2) or { return types.unknown_type }
if match_expr is MatchExpression {
return_stmt := match_expr.parent_nth(2) or { return types.unknown_type }
if return_stmt.element_type() == .return_statement {
return t.enclosing_function_return_type(return_stmt)
}
}
}
if parent is FieldDeclaration {
return parent.get_type()
}
if parent is ArrayCreation {
expressions := parent.expressions()
first := expressions[0] or { return types.unknown_type }
if first.element_type() != .enum_fetch {
return t.infer_type(first)
}
bin_expr := parent.parent() or { return types.unknown_type }
if bin_expr.element_type() in [.binary_expression, .in_expression] {
left := bin_expr.first_child_or_stub() or { return types.unknown_type }
if left.is_parent_of(parent) {
return types.unknown_type
}
return t.infer_type(left)
}
return types.unknown_type
}
return types.unknown_type
}
fn (_ &TypeInferer) enclosing_function_return_type(elem PsiElement) types.Type {
function := elem.parent_of_any_type(.function_declaration, .function_literal) or {
return types.unknown_type
}
if function is SignatureOwner {
signature := function.signature() or { return types.unknown_type }
typ := signature.get_type()
if typ is types.FunctionType {
return typ.result
}
}
return types.unknown_type
}
================================================
FILE: src/analyzer/psi/TypeInitializer.v
================================================
module psi
import analyzer.psi.types
pub struct TypeInitializer {
PsiElementImpl
}
pub fn (n &TypeInitializer) get_type() types.Type {
return infer_type(n)
}
pub fn (n &TypeInitializer) element_list() []PsiElement {
body := n.find_child_by_name('body') or { return [] }
element_list := body.find_child_by_type(.element_list) or { return [] }
return element_list.named_children()
}
================================================
FILE: src/analyzer/psi/TypeReferenceExpression.v
================================================
module psi
import analyzer.psi.types
pub struct TypeReferenceExpression {
PsiElementImpl
}
fn (_ &TypeReferenceExpression) stub() {}
pub fn (_ TypeReferenceExpression) is_public() bool {
return true
}
pub fn (r TypeReferenceExpression) identifier() ?PsiElement {
return r.first_child()
}
pub fn (r &TypeReferenceExpression) identifier_text_range() TextRange {
if stub := r.get_stub() {
return stub.identifier_text_range
}
identifier := r.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (r TypeReferenceExpression) name() string {
if stub := r.get_stub() {
return stub.text
}
identifier := r.identifier() or { return '' }
return identifier.get_text()
}
pub fn (r TypeReferenceExpression) qualifier() ?PsiElement {
parent := r.parent()?
if parent is QualifiedType {
left := parent.left()?
if left.is_equal(r) {
return none
}
return left
}
return none
}
pub fn (r TypeReferenceExpression) reference() PsiReference {
return new_reference(r.containing_file, r, true)
}
pub fn (r TypeReferenceExpression) resolve() ?PsiElement {
return r.reference().resolve()
}
pub fn (r TypeReferenceExpression) get_type() types.Type {
element := r.resolve() or { return types.unknown_type }
if element is PsiTypedElement {
return element.get_type()
}
return types.unknown_type
}
================================================
FILE: src/analyzer/psi/UnaryExpression.v
================================================
module psi
pub struct UnaryExpression {
PsiElementImpl
}
pub fn (n UnaryExpression) operator() string {
operator_element := n.find_child_by_name('operator') or { return '' }
return operator_element.get_text()
}
pub fn (n UnaryExpression) expression() ?PsiElement {
return n.find_child_by_name('operand')
}
================================================
FILE: src/analyzer/psi/UnsafeExpression.v
================================================
module psi
pub struct UnsafeExpression {
PsiElementImpl
}
pub fn (n UnsafeExpression) block() ?&Block {
block := n.find_child_by_type(.block)?
if block is Block {
return block
}
return none
}
================================================
FILE: src/analyzer/psi/ValueAttribute.v
================================================
module psi
pub struct ValueAttribute {
PsiElementImpl
}
pub fn (n ValueAttribute) value() string {
return n.get_text()
}
fn (_ &ValueAttribute) stub() {}
================================================
FILE: src/analyzer/psi/VarDeclaration.v
================================================
module psi
pub struct VarDeclaration {
PsiElementImpl
}
fn (v VarDeclaration) index_of(def VarDefinition) int {
first_child := v.first_child() or { return -1 }
children := first_child.children()
.filter(it is VarDefinition || it is MutExpression)
.map(fn (it PsiElement) PsiElement {
if it is MutExpression {
return it.last_child() or { return it }
}
return it
})
for i, definition in children {
if definition.is_equal(def) {
return i
}
}
return -1
}
fn (v VarDeclaration) initializer_of(def VarDefinition) ?PsiElement {
index := v.index_of(def)
if index == -1 {
return none
}
expressions := v.expressions()
if expressions.len == 1 && expressions.first() is CallExpression {
return expressions.first()
}
if index >= expressions.len {
return none
}
return expressions[index]
}
pub fn (v VarDeclaration) vars() []src.analyzer.psi.PsiElement {
first_child := v.first_child() or { return [] }
return first_child
.children()
.filter(it is VarDefinition || it is MutExpression)
.map(fn (it PsiElement) PsiElement {
if it is MutExpression {
return it.last_child() or { return it }
}
return it
})
}
fn (v VarDeclaration) expressions() []src.analyzer.psi.PsiElement {
last_child := v.last_child() or { return [] }
return last_child.children()
}
================================================
FILE: src/analyzer/psi/VarDefinition.v
================================================
module psi
import analyzer.psi.types
pub struct VarDefinition {
PsiElementImpl
}
pub fn (_ &VarDefinition) is_public() bool {
return true
}
pub fn (n &VarDefinition) identifier() ?PsiElement {
return n.find_child_by_type(.identifier)
}
pub fn (n &VarDefinition) identifier_text_range() TextRange {
identifier := n.identifier() or { return TextRange{} }
return identifier.text_range()
}
pub fn (n &VarDefinition) name() string {
identifier := n.identifier() or { return '' }
return identifier.get_text()
}
pub fn (n &VarDefinition) declaration() ?&VarDeclaration {
if parent := n.parent_nth(2) {
if parent is VarDeclaration {
return parent
}
}
if parent := n.parent_nth(3) {
if parent is VarDeclaration {
return parent
}
}
return none
}
pub fn (n &VarDefinition) get_type() types.Type {
return infer_type(n)
}
pub fn (n &VarDefinition) mutability_modifiers() ?&MutabilityModifiers {
if mut_expr := n.parent() {
if mut_expr.node().type_name == .mutable_expression {
modifiers := mut_expr.find_child_by_type(.mutability_modifiers)?
if modifiers is MutabilityModifiers {
return modifiers
}
}
}
return none
}
pub fn (n &VarDefinition) is_mutable() bool {
mods := n.mutability_modifiers() or {
if first_child := n.first_child() {
if first_child.text_matches('mut') {
return true
}
}
if grand := n.parent_nth(4) {
if grand.element_type() == .for_clause {
// variable inside for loop initializer is mutable by default
return true
}
}
return false
}
return mods.is_mutable()
}
================================================
FILE: src/analyzer/psi/VisibilityModifiers.v
================================================
module psi
pub struct VisibilityModifiers {
PsiElementImpl
}
pub fn (n VisibilityModifiers) is_public() bool {
return n.get_text() == 'pub'
}
fn (n &VisibilityModifiers) stub() {}
================================================
FILE: src/analyzer/psi/doc_comment_extractor.v
================================================
module psi
import strings
pub fn extract_doc_comment(el PsiElement) string {
el_start_line := el.node().start_point().row
mut comment := el.prev_sibling() or { return '' }
if comment !is LineComment {
comment = comment.prev_sibling() or { return '' }
}
mut comments := []PsiElement{}
for comment is LineComment {
comment_start_line := comment.node().start_point().row
if comment_start_line + 1 + u32(comments.len) != el_start_line {
break
}
line := comment.prev_sibling() or { break }
if line.node().start_point().row == comment_start_line {
break
}
comments << comment
comment = line
}
mut field_eol_comment := ''
if el is FieldDeclaration {
if next := el.next_sibling() {
if next is LineComment {
comment_start_line := next.node.start_point().row
if comment_start_line == el_start_line {
field_eol_comment = next.get_text().trim_string_left('//').trim_space()
}
}
}
}
if comments.len == 0 {
return if field_eol_comment != '' { '... ' + field_eol_comment } else { '' }
}
comments.reverse_in_place()
lines := comments.map(it.get_text()
.trim_string_left('//')
.trim_string_left(' ')
.trim_right(' \t'))
mut res := strings.new_builder(lines.len * 40)
mut inside_code_block := false
for raw_line in lines {
line := raw_line.trim_right(' ')
// when `--------` line
if line.replace('-', '').len == 0 && line.len != 0 {
res.write_string('\n\n')
continue
}
is_end_of_sentence := line.ends_with('.') || line.ends_with('!') || line.ends_with('?')
|| line.ends_with(':')
is_list := line.starts_with('-')
is_header := line.starts_with('#')
is_table := line.starts_with('|') || line.starts_with('|')
is_example := line.starts_with('Example:')
is_code_block := line.starts_with('```')
if is_example || (is_code_block && !inside_code_block) {
res.write_string('\n')
}
without_example_label := line.trim_string_left('Example:').trim_space()
if is_example && without_example_label.len != 0 {
res.write_string('\nExample:\n')
res.write_string('```\n')
res.write_string(without_example_label)
res.write_string('\n')
res.write_string('```\n')
} else {
res.write_string(line)
}
if inside_code_block || is_code_block || is_table {
res.write_string('\n')
}
if (is_end_of_sentence || is_list || is_header || is_example) && !inside_code_block {
res.write_string('\n')
} else if !inside_code_block && !is_code_block {
res.write_string(' ')
}
if is_code_block {
inside_code_block = !inside_code_block
}
}
res_str := res.str() + if field_eol_comment != '' { '\n\n... ' + field_eol_comment } else { '' }
return res_str
}
================================================
FILE: src/analyzer/psi/element_factory.v
================================================
module psi
pub fn create_element(node AstNode, containing_file ?&PsiFile) PsiElement {
base_node := new_psi_node(containing_file, node)
if node.type_name == .module_clause {
return &ModuleClause{
PsiElementImpl: base_node
}
}
if node.type_name == .identifier {
return &Identifier{
PsiElementImpl: base_node
}
}
if node.type_name == .plain_type {
return &PlainType{
PsiElementImpl: base_node
}
}
if node.type_name == .selector_expression {
return &SelectorExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .for_statement {
return &ForStatement{
PsiElementImpl: base_node
}
}
if node.type_name == .call_expression {
return &CallExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .argument {
return &Argument{
PsiElementImpl: base_node
}
}
if node.type_name == .index_expression {
return &IndexExpression{
PsiElementImpl: base_node
}
}
var := node_to_var_definition(node, containing_file, base_node)
if !isnil(var) {
return var
}
if node.type_name == .reference_expression {
return &ReferenceExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .type_reference_expression {
return &TypeReferenceExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .type_declaration {
return &TypeAliasDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .type_initializer {
return &TypeInitializer{
PsiElementImpl: base_node
}
}
if node.type_name == .field_name {
return &FieldName{
PsiElementImpl: base_node
}
}
if node.type_name == .function_declaration {
return &FunctionOrMethodDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .receiver {
return &Receiver{
PsiElementImpl: base_node
}
}
if node.type_name == .struct_declaration {
return &StructDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .interface_declaration {
return &InterfaceDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .interface_method_definition {
return &InterfaceMethodDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .enum_declaration {
return &EnumDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .struct_field_declaration {
return &FieldDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .struct_field_scope {
return &StructFieldScope{
PsiElementImpl: base_node
}
}
if node.type_name == .enum_field_definition {
return &EnumFieldDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .const_declaration {
return &ConstantDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .const_definition {
return &ConstantDefinition{
PsiElementImpl: base_node
}
}
if node.type_name == .var_declaration {
return &VarDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .block {
return &Block{
PsiElementImpl: base_node
}
}
if node.type_name == .mutable_expression {
return &MutExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .signature {
return &Signature{
PsiElementImpl: base_node
}
}
if node.type_name == .parameter_list {
return &ParameterList{
PsiElementImpl: base_node
}
}
if node.type_name == .parameter_declaration {
return &ParameterDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .literal {
return &Literal{
PsiElementImpl: base_node
}
}
if node.type_name == .line_comment {
return &LineComment{
PsiElementImpl: base_node
}
}
if node.type_name == .block_comment {
return &BlockComment{
PsiElementImpl: base_node
}
}
if node.type_name == .mutability_modifiers {
return &MutabilityModifiers{
PsiElementImpl: base_node
}
}
if node.type_name == .visibility_modifiers {
return &VisibilityModifiers{
PsiElementImpl: base_node
}
}
if node.type_name == .attributes {
return &Attributes{
PsiElementImpl: base_node
}
}
if node.type_name == .attribute {
return &Attribute{
PsiElementImpl: base_node
}
}
if node.type_name == .attribute_expression {
return &AttributeExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .value_attribute {
return &ValueAttribute{
PsiElementImpl: base_node
}
}
if node.type_name == .range {
return &Range{
PsiElementImpl: base_node
}
}
if node.type_name == .interpreted_string_literal {
return &StringLiteral{
PsiElementImpl: base_node
}
}
if node.type_name == .unsafe_expression {
return &UnsafeExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .array_creation {
return &ArrayCreation{
PsiElementImpl: base_node
}
}
if node.type_name == .fixed_array_creation {
return &ArrayCreation{
PsiElementImpl: base_node
is_fixed: true
}
}
if node.type_name == .map_init_expression {
return &MapInitExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .map_keyed_element {
return &MapKeyedElement{
PsiElementImpl: base_node
}
}
if node.type_name == .function_literal {
return &FunctionLiteral{
PsiElementImpl: base_node
}
}
if node.type_name == .if_expression {
return &IfExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .compile_time_if_expression {
return &CompileTimeIfExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .match_expression {
return &MatchExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .import_spec {
return &ImportSpec{
PsiElementImpl: base_node
}
}
if node.type_name == .qualified_type {
return &QualifiedType{
PsiElementImpl: base_node
}
}
if node.type_name == .import_list {
return &ImportList{
PsiElementImpl: base_node
}
}
if node.type_name == .import_declaration {
return &ImportDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .import_path {
return &ImportPath{
PsiElementImpl: base_node
}
}
if node.type_name == .import_name {
return &ImportName{
PsiElementImpl: base_node
}
}
if node.type_name == .import_alias {
return &ImportAlias{
PsiElementImpl: base_node
}
}
if node.type_name == .selective_import_list {
return &SelectiveImportList{
PsiElementImpl: base_node
}
}
if node.type_name == .global_var_definition {
return &GlobalVarDefinition{
PsiElementImpl: base_node
}
}
if node.type_name == .keyed_element {
return &KeyedElement{
PsiElementImpl: base_node
}
}
if node.type_name == .generic_parameters {
return &GenericParameters{
PsiElementImpl: base_node
}
}
if node.type_name == .generic_parameter {
return &GenericParameter{
PsiElementImpl: base_node
}
}
if node.type_name == .slice_expression {
return &SliceExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .embedded_definition {
return &EmbeddedDefinition{
PsiElementImpl: base_node
}
}
if node.type_name == .or_block_expression {
return &OrBlockExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .option_propagation_expression {
return &OptionPropagationExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .result_propagation_expression {
return &ResultPropagationExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .type_parameters {
return &GenericTypeArguments{
PsiElementImpl: base_node
}
}
if node.type_name == .unary_expression {
return &UnaryExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .binary_expression {
return &BinaryExpression{
PsiElementImpl: base_node
}
}
if node.type_name == .source_file {
return &SourceFile{
PsiElementImpl: base_node
}
}
if node.type_name == .static_method_declaration {
return &StaticMethodDeclaration{
PsiElementImpl: base_node
}
}
if node.type_name == .static_receiver {
return &StaticReceiver{
PsiElementImpl: base_node
}
}
return &PsiElementImpl{
node: node
containing_file: containing_file
}
}
@[inline]
pub fn node_to_var_definition(node AstNode, containing_file ?&PsiFile, base_node ?PsiElementImpl) &VarDefinition {
if node.type_name == .var_definition {
return &VarDefinition{
PsiElementImpl: base_node or { new_psi_node(containing_file, node) }
}
}
if node.type_name == .reference_expression {
parent := node.parent() or { return unsafe { nil } }
if parent.type_name != .expression_list && parent.type_name != .mutable_expression {
return unsafe { nil }
}
grand := parent.parent() or { return unsafe { nil } }
if grand.type_name == .var_declaration {
var_list := grand.child_by_field_name('var_list') or { return unsafe { nil } }
if var_list.is_parent_of(node) {
return &VarDefinition{
PsiElementImpl: base_node or { new_psi_node(containing_file, node) }
}
}
}
if grand_grand := grand.parent() {
if grand_grand.type_name == .var_declaration && parent.type_name == .mutable_expression {
return &VarDefinition{
PsiElementImpl: base_node or { new_psi_node(containing_file, node) }
}
}
}
}
return unsafe { nil }
}
================================================
FILE: src/analyzer/psi/search/ReferencesSearch.v
================================================
module search
import analyzer.psi
import analyzer.parser
import runtime
import math
import time
import loglib
@[params]
pub struct SearchParams {
pub:
// include_declaration indicates whether to include the declaration
// of the symbol in the search results
// This is useful when we want to find all usages of a symbol for
// refactoring purposes, for example, rename a symbol.
//
// When include_declaration is true, results will include the declaration as `PsiNamedElement`,
// not `Identifier`, so caller should take care of this.
// For example, use `identifier_text_range()` instead of `text_range()` to get the range of the
// identifier.
include_declaration bool
// only_in_current_file indicates whether to search only in the current file
// This is set to true when we find references of a symbol for `documentHighlight`
// request.
only_in_current_file bool
}
pub fn references(element psi.PsiElement, params SearchParams) []psi.PsiElement {
containing_file := element.containing_file() or { return [] }
return ReferencesSearch{
params: params
containing_file: containing_file
}.search(element)
}
struct ReferencesSearch {
params SearchParams
containing_file &psi.PsiFile
}
pub fn (r &ReferencesSearch) search(element psi.PsiElement) []psi.PsiElement {
resolved := resolve_identifier(element) or { return [] }
if resolved is psi.VarDefinition {
// variables cannot be used outside the scope where they are defined
scope := element.parent_of_any_type(.block, .source_file) or { return [] }
return r.search_in_scope(resolved, scope)
}
if resolved is psi.ParameterDeclaration {
parent := resolved.parent_of_any_type(.function_literal, .function_declaration) or {
return []
}
return r.search_in_scope(resolved, parent)
}
if resolved is psi.Receiver {
parent := resolved.parent_of_type(.function_declaration) or { return [] }
return r.search_in_scope(resolved, parent)
}
if resolved is psi.ImportName {
import_spec := resolved.parent_of_type(.import_spec) or { return [] }
if import_spec is psi.ImportSpec {
file := element.containing_file() or { return [] }
return r.search_in_scope(import_spec, file.root())
}
return []
}
if resolved is psi.ModuleClause {
return r.search_module_import(resolved)
}
if resolved is psi.GenericParameter {
return r.search_generic_parameter(resolved)
}
if resolved is psi.FunctionOrMethodDeclaration {
if resolved.is_method() {
return r.search_method(resolved)
}
}
if resolved is psi.PsiNamedElement {
return r.search_named_element(resolved)
}
return []
}
// search_method searches references of a method.
//
// If the struct of the method implements some interface, we must also look for the use of the
// interface method, since the method of the struct for which we are looking for references is
// also implicitly called through it.
//
// This is important for renaming, because if we rename a struct method, we must also rename the
// interface method so that the struct continues to implement it.
pub fn (r &ReferencesSearch) search_method(element psi.FunctionOrMethodDeclaration) []psi.PsiElement {
iface_super_methods := super_methods(element)
if iface_super_methods.len == 0 {
return r.search_named_element(element)
}
mut result := r.search_named_element(element)
for super_method in iface_super_methods {
if super_method is psi.PsiNamedElement {
result << r.search_named_element(super_method)
}
}
return result
}
pub fn (r &ReferencesSearch) search_generic_parameter(element psi.GenericParameter) []psi.PsiElement {
return r.search_private_named_element(element)
}
pub fn (r &ReferencesSearch) search_module_import(element psi.PsiNamedElement) []psi.PsiElement {
if r.params.only_in_current_file {
// module cannot be imported in the same file where it is defined
return []
}
mut result := []psi.PsiElement{cap: 10}
psi_element := element as psi.PsiElement
file := psi_element.containing_file() or { return [] }
file_sink := file.index_sink() or { return [] }
module_name := file_sink.module_fqn()
depends_sinks := stubs_index.get_all_sink_depends_on(module_name)
for sink in depends_sinks {
root := sink.stub_list.index_map[0] or { continue }
children := root.children_stubs()
for child in children {
if child.stub_type() == .import_list {
declarations := child.children_stubs()
for declaration in declarations {
import_spec := declaration.first_child() or { continue }
import_path := import_spec.first_child() or { continue }
if import_path.stub_type() == .import_path {
if import_path.text() == module_name {
result << import_path.get_psi() or { continue }
}
}
}
}
}
}
return result
}
pub fn (r &ReferencesSearch) search_named_element(element psi.PsiNamedElement) []psi.PsiElement {
is_public := element.is_public()
is_field := element is psi.FieldDeclaration
if is_public || is_field {
return r.search_public_named_element(element)
} else {
return r.search_private_named_element(element)
}
}
pub fn (r &ReferencesSearch) search_private_named_element(element psi.PsiNamedElement) []psi.PsiElement {
module_name := r.containing_file.module_fqn()
return r.search_named_element_in_module(module_name, element)
}
pub fn (r &ReferencesSearch) search_named_element_in_module(module_name string, element psi.PsiNamedElement) []psi.PsiElement {
mut result := []psi.PsiElement{cap: 10}
if r.params.include_declaration {
result << element as psi.PsiElement
}
if r.params.only_in_current_file {
result << r.search_in(element, r.containing_file.root())
return result
}
sinks_to_search := stubs_index.get_all_sinks_from_module(module_name)
if sinks_to_search.len == 0 {
return []
}
mut path_to_search := []string{cap: sinks_to_search.len}
for sink in sinks_to_search {
path_to_search << sink.stub_list.path
}
cpus := runtime.nr_cpus()
workers := math.max(cpus - 2, 1)
parsed_files := parser.parse_batch_files(path_to_search, workers)
for parsed_file in parsed_files {
mut psi_file := psi.new_psi_file(parsed_file.path, parsed_file.tree,
parsed_file.source_text)
result << r.search_in(element, psi_file.root)
psi_file.free()
}
return result
}
pub fn (r &ReferencesSearch) search_public_named_element(element psi.PsiNamedElement) []psi.PsiElement {
if r.params.only_in_current_file {
mut result := []psi.PsiElement{cap: 10}
if r.params.include_declaration {
result << element as psi.PsiElement
}
result << r.search_in(element, r.containing_file.root())
return result
}
file_sink := r.containing_file.index_sink() or { return [] }
module_name := file_sink.module_fqn()
// we don't want to search symbol usages in the same module where it is defined
// if this is not a workspace module
usages_in_own_module := if file_sink.kind == .workspace {
r.search_named_element_in_module(module_name, element)
} else {
[]psi.PsiElement{}
}
mut files := []string{cap: 10}
depends_sinks := stubs_index.get_all_sink_depends_on(module_name)
for sink in depends_sinks {
if sink.kind != .workspace {
continue
}
files << sink.stub_list.path
}
mut usages_in_depends_modules := []psi.PsiElement{cap: 10}
cpus := runtime.nr_cpus()
workers := math.max(cpus - 2, 1)
watch := time.new_stopwatch(auto_start: true)
parsed_files := parser.parse_batch_files(files, workers)
for parsed_result in parsed_files {
mut psi_file := psi.new_psi_file(parsed_result.path, parsed_result.tree,
parsed_result.source_text)
usages_in_depends_modules << r.search_in(element, psi_file.root)
psi_file.free()
}
loglib.with_duration(watch.elapsed()).info('Finish searching in depends modules')
mut all_usages := []psi.PsiElement{cap: usages_in_own_module.len + usages_in_depends_modules.len}
all_usages << usages_in_own_module
all_usages << usages_in_depends_modules
return all_usages
}
pub fn (r &ReferencesSearch) search_in_scope(element psi.PsiNamedElement, scope psi.PsiElement) []psi.PsiElement {
mut result := []psi.PsiElement{cap: 10}
if r.params.include_declaration {
result << element as psi.PsiElement
}
// looking for all references to a variable inside the scope
result << r.search_in(element, scope)
return result
}
pub fn (r &ReferencesSearch) search_in(element psi.PsiNamedElement, search_root psi.PsiElement) []psi.PsiElement {
name := element.name()
mut result := []psi.PsiElement{cap: 10}
mut walker := psi.new_psi_tree_walker(search_root)
defer { walker.free() }
for {
node := walker.next() or { break }
if node is psi.ReferenceExpression || node is psi.TypeReferenceExpression {
ref := node as psi.ReferenceExpressionBase
if node.text_matches(name) {
resolved := ref.resolve() or { continue }
if resolved is psi.PsiNamedElement {
if resolved.identifier_text_range() == element.identifier_text_range() {
result << node
}
}
if element is psi.ImportSpec && resolved is psi.ImportSpec {
if element.import_name() == resolved.import_name() {
if element_file := element.containing_file() {
if resolved_file := resolved.containing_file() {
if element_file.path == resolved_file.path {
result << node
}
}
}
}
}
}
}
}
return result
}
fn resolve_identifier(element psi.PsiElement) ?psi.PsiElement {
parent := element.parent()?
resolved := if parent is psi.ReferenceExpression {
parent.resolve()?
} else if parent is psi.TypeReferenceExpression {
parent.resolve()?
} else {
parent
}
return resolved
}
================================================
FILE: src/analyzer/psi/search/common.v
================================================
module search
import analyzer.psi
import analyzer.psi.types
// is_implemented checks if the given symbol (methods and fields) implements the given interface (methods and fields).
fn is_implemented(iface_methods []psi.PsiElement, iface_fields []psi.PsiElement, symbol_methods []psi.PsiElement,
symbol_fields []psi.PsiElement) bool {
mut symbol_methods_set := map[string]psi.FunctionOrMethodDeclaration{}
for symbol_method in symbol_methods {
if symbol_method is psi.FunctionOrMethodDeclaration {
symbol_methods_set[symbol_method.fingerprint()] = *symbol_method
}
}
for iface_method in iface_methods {
if iface_method is psi.InterfaceMethodDeclaration {
if iface_method.fingerprint() !in symbol_methods_set {
// if at least one method is not implemented, then the whole interface is not implemented
return false
}
}
}
mut symbol_fields_set := map[string]psi.FieldDeclaration{}
for symbol_field in symbol_fields {
if symbol_field is psi.FieldDeclaration {
symbol_fields_set[symbol_field.name()] = *symbol_field
}
}
for iface_field in iface_fields {
if iface_field is psi.FieldDeclaration {
if iface_field.is_embedded_definition() {
continue
}
if iface_field.name() !in symbol_fields_set {
// if at least one field is not implemented, then the whole interface is not implemented
return false
}
}
}
for iface_method in iface_methods {
if iface_method is psi.InterfaceMethodDeclaration {
symbol_method := symbol_methods_set[iface_method.fingerprint()] or { return false }
if !is_method_compatible(*iface_method, symbol_method) {
return false
}
}
}
for iface_field in iface_fields {
if iface_field is psi.FieldDeclaration {
symbol_field := symbol_fields_set[iface_field.name()] or { return false }
if !is_field_compatible(*iface_field, symbol_field) {
return false
}
}
}
return true
}
fn is_method_compatible(iface_method psi.InterfaceMethodDeclaration, symbol_method psi.FunctionOrMethodDeclaration) bool {
iface_signature := iface_method.signature() or { return false }
symbol_signature := symbol_method.signature() or { return false }
iface_type := iface_signature.get_type()
symbol_type := symbol_signature.get_type()
if iface_type is types.FunctionType {
if symbol_type is types.FunctionType {
iface_params := iface_type.params
symbol_params := symbol_type.params
if iface_params.len != symbol_params.len {
return false
}
for i in 0 .. iface_params.len {
if iface_params[i].qualified_name() != symbol_params[i].qualified_name() {
return false
}
}
if iface_type.no_result != symbol_type.no_result {
return false
}
if iface_type.result.qualified_name() != symbol_type.result.qualified_name() {
return false
}
return true
}
}
return false
}
fn is_field_compatible(iface_field psi.FieldDeclaration, symbol_field psi.FieldDeclaration) bool {
iface_type := iface_field.get_type()
symbol_type := symbol_field.get_type()
return iface_type.qualified_name() == symbol_type.qualified_name()
}
================================================
FILE: src/analyzer/psi/search/implementations.v
================================================
module search
import analyzer.psi
// implementations returns all implementations of the given interface
//
// Search algorithm:
// 1. Having interface methods and fields, we look for all methods and fields in structures with the same fingerprint.
// method fingerprint is the name + the number of parameters + the presence of a return value.
// field fingerprint is the name.
//
// During indexing, we already collect all methods and fields into `.methods_fingerprint`
// and `.fields_fingerprint` indices, so searching for such methods and fields has a complexity of O(1).
//
// 2. For each received method and field, find the parent structure and add it to the list of candidates.
//
// 3. For each candidate, check that it implements all methods and fields of the interface.
pub fn implementations(iface psi.InterfaceDeclaration) []psi.PsiElement {
methods := iface.methods()
fields := iface.fields()
if methods.len == 0 && fields.len == 0 {
return []
}
candidates := candidates_by_methods_and_fields(methods, fields)
if candidates.len == 0 {
return []
}
mut result := map[string]psi.PsiElement{}
for candidate in candidates {
name := candidate.name()
if name in result {
// don't check one candidate several times
continue
}
if is_implemented_by_type(methods, fields, candidate as psi.PsiElement) {
result[name] = candidate as psi.PsiElement
}
}
return result.values()
}
fn is_implemented_by_type(iface_methods []psi.PsiElement, iface_fields []psi.PsiElement, symbol psi.PsiElement) bool {
symbol_type := if symbol is psi.PsiTypedElement {
symbol.get_type()
} else {
return false
}
symbol_methods := psi.methods_list(symbol_type)
if symbol_methods.len == 0 && iface_methods.len != 0 {
return false
}
symbol_fields := psi.fields_list(symbol_type)
if symbol_fields.len == 0 && iface_fields.len != 0 {
return false
}
return is_implemented(iface_methods, iface_fields, symbol_methods, symbol_fields)
}
fn candidates_by_methods_and_fields(methods []psi.PsiElement, fields []psi.PsiElement) []psi.PsiNamedElement {
by_methods := candidates_by_methods(methods)
by_fields := candidates_by_fields(fields)
mut result := []psi.PsiNamedElement{cap: by_methods.len + by_fields.len}
result << by_methods
result << by_fields
return result
}
fn candidates_by_methods(methods []psi.PsiElement) []psi.PsiNamedElement {
mut candidates := []psi.PsiNamedElement{cap: 5}
for method in methods {
if method is psi.InterfaceMethodDeclaration {
fingerprint := method.fingerprint()
// all methods with the same fingerprint can probably be part of struct that implements the interface
struct_methods := stubs_index.get_elements_from_by_name(.workspace,
.methods_fingerprint, fingerprint)
for struct_method in struct_methods {
if struct_method is psi.FunctionOrMethodDeclaration {
owner := struct_method.owner() or { continue }
if owner is psi.PsiNamedElement {
candidates << owner
}
}
}
}
}
return candidates
}
fn candidates_by_fields(fields []psi.PsiElement) []psi.PsiNamedElement {
mut candidates := []psi.PsiNamedElement{cap: 5}
for field in fields {
if field is psi.FieldDeclaration {
fingerprint := field.name()
// all fields with the same fingerprint can probably be part of struct that implements the interface
struct_fields := stubs_index.get_elements_from_by_name(.workspace, .fields_fingerprint,
fingerprint)
for struct_field in struct_fields {
if struct_field is psi.FieldDeclaration {
owner := struct_field.owner() or { continue }
if owner is psi.PsiNamedElement {
candidates << owner
}
}
}
}
}
return candidates
}
================================================
FILE: src/analyzer/psi/search/implmenttion_methods.v
================================================
module search
import analyzer.psi
// implementation_methods returns all methods that implement the given interface method.
pub fn implementation_methods(method psi.InterfaceMethodDeclaration) []psi.PsiElement {
mut result := []psi.PsiElement{}
owner := method.owner() or { return [] }
structs := implementations(owner)
for struct_ in structs {
if struct_ is psi.StructDeclaration {
struct_method := psi.find_method(struct_.get_type(), method.name()) or { continue }
result << struct_method
}
}
return result
}
================================================
FILE: src/analyzer/psi/search/super_methods.v
================================================
module search
import analyzer.psi
// super_methods returns interface methods that are implemented by the struct of given method.
pub fn super_methods(method psi.FunctionOrMethodDeclaration) []psi.PsiElement {
mut result := []psi.PsiElement{}
method_name := method.name()
owner := method.owner() or { return [] }
if owner is psi.StructDeclaration {
super_interfaces := supers(*owner)
for super_interface in super_interfaces {
if super_interface is psi.InterfaceDeclaration {
if iface_method := super_interface.find_method(method_name) {
result << iface_method
}
}
}
}
return result
}
================================================
FILE: src/analyzer/psi/search/supers.v
================================================
module search
import analyzer.psi
// supers returns all interfaces that are implemented by the given struct
//
// Search algorithm:
// 1. Find all methods and fields of the struct
// 2. Search for all interface methods and fields that have the same fingerprint
// (see description in `search.implementations()`)
// 3. For each candidate, check that struct implements all methods and fields of the interface.
pub fn supers(strukt psi.StructDeclaration) []psi.PsiElement {
struct_type := strukt.get_type()
methods := psi.methods_list(struct_type)
fields := strukt.fields()
if methods.len == 0 && fields.len == 0 {
return []
}
candidates := super_candidates_by_methods_and_fields(methods, fields)
if candidates.len == 0 {
return []
}
mut result := map[string]psi.PsiElement{}
for candidate in candidates {
name := candidate.name()
if name in result {
// don't check one candidate several times
continue
}
if candidate is psi.InterfaceDeclaration {
if is_implemented_interface(methods, fields, *candidate) {
result[name] = candidate
}
}
}
return result.values()
}
fn is_implemented_interface(symbol_methods []psi.PsiElement, symbol_fields []psi.PsiElement, iface psi.InterfaceDeclaration) bool {
iface_methods := iface.methods()
iface_fields := iface.fields()
return is_implemented(iface_methods, iface_fields, symbol_methods, symbol_fields)
}
fn super_candidates_by_methods_and_fields(methods []psi.PsiElement, fields []psi.PsiElement) []psi.PsiNamedElement {
by_methods := super_candidates_by_methods(methods)
by_fields := super_candidates_by_fields(fields)
mut result := []psi.PsiNamedElement{cap: by_methods.len + by_fields.len}
result << by_methods
result << by_fields
return result
}
fn super_candidates_by_methods(methods []psi.PsiElement) []psi.PsiNamedElement {
mut candidates := []psi.PsiNamedElement{cap: 5}
for method in methods {
if method is psi.FunctionOrMethodDeclaration {
fingerprint := method.fingerprint()
// all methods with the same fingerprint can probably be part of the same interface
interface_methods := stubs_index.get_elements_from_by_name(.workspace,
.interface_methods_fingerprint, fingerprint)
for interface_method in interface_methods {
if interface_method is psi.InterfaceMethodDeclaration {
candidates << interface_method.owner() or { continue }
}
}
}
}
return candidates
}
fn super_candidates_by_fields(fields []psi.PsiElement) []psi.PsiNamedElement {
mut candidates := []psi.PsiNamedElement{cap: 5}
for field in fields {
if field is psi.FieldDeclaration {
fingerprint := field.name()
// all fields with the same fingerprint can probably be part of interface that can be implemented by the struct
interface_fields := stubs_index.get_elements_from_by_name(.workspace,
.interface_fields_fingerprint, fingerprint)
for interface_field in interface_fields {
if interface_field is psi.FieldDeclaration {
owner := interface_field.owner() or { continue }
if owner is psi.PsiNamedElement {
candidates << owner
}
}
}
}
}
return candidates
}
================================================
FILE: src/analyzer/psi/types/AliasType.v
================================================
module types
pub struct AliasType {
BaseNamedType
pub:
inner Type
}
pub fn new_alias_type(name string, module_name string, inner Type) &AliasType {
return &AliasType{
name: name
module_name: module_name
inner: inner
}
}
pub fn (s &AliasType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
s.inner.accept(mut visitor)
}
pub fn (s &AliasType) substitute_generics(name_map map[string]Type) Type {
return new_alias_type(s.name, s.module_name, s.inner.substitute_generics(name_map))
}
================================================
FILE: src/analyzer/psi/types/ArrayType.v
================================================
module types
pub struct ArrayType {
pub:
inner Type
}
pub fn new_array_type(inner Type) &ArrayType {
return &ArrayType{
inner: inner
}
}
pub fn (s &ArrayType) name() string {
return '[]${s.inner.name()}'
}
pub fn (s &ArrayType) qualified_name() string {
return '[]${s.inner.qualified_name()}'
}
pub fn (s &ArrayType) readable_name() string {
return '[]${s.inner.readable_name()}'
}
pub fn (s &ArrayType) module_name() string {
return s.inner.module_name()
}
pub fn (s &ArrayType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
s.inner.accept(mut visitor)
}
pub fn (s &ArrayType) substitute_generics(name_map map[string]Type) Type {
return new_array_type(s.inner.substitute_generics(name_map))
}
================================================
FILE: src/analyzer/psi/types/BaseNamedType.v
================================================
module types
struct BaseNamedType {
pub:
module_name string
name string
}
pub fn (s &BaseNamedType) name() string {
return s.name
}
pub fn (s &BaseNamedType) qualified_name() string {
if s.module_name == '' {
return s.name
}
return s.module_name + '.' + s.name
}
pub fn (s &BaseNamedType) readable_name() string {
if s.module_name == '' {
return s.name
}
last_module := s.module_name.split('.').last()
if last_module == 'builtin' || last_module == 'stubs' || last_module == 'main' {
return s.name
}
return last_module + '.' + s.name
}
pub fn (s &BaseNamedType) module_name() string {
return s.module_name
}
================================================
FILE: src/analyzer/psi/types/BaseType.v
================================================
module types
pub struct BaseType {
pub:
module_name string
}
@[markused]
pub fn (s &BaseType) module_name() string {
return s.module_name
}
================================================
FILE: src/analyzer/psi/types/ChannelType.v
================================================
module types
pub struct ChannelType {
pub:
inner Type
}
pub fn new_channel_type(inner Type) &ChannelType {
return &ChannelType{
inner: inner
}
}
pub fn (s &ChannelType) name() string {
return 'chan ${s.inner.name()}'
}
pub fn (s &ChannelType) qualified_name() string {
return 'chan ${s.inner.qualified_name()}'
}
pub fn (s &ChannelType) readable_name() string {
return 'chan ${s.inner.readable_name()}'
}
pub fn (s &ChannelType) module_name() string {
return s.inner.module_name()
}
pub fn (s &ChannelType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
s.inner.accept(mut visitor)
}
pub fn (s &ChannelType) substitute_generics(name_map map[string]Type) Type {
return new_channel_type(s.inner.substitute_generics(name_map))
}
================================================
FILE: src/analyzer/psi/types/EnumType.v
================================================
module types
pub struct EnumType {
BaseNamedType
}
pub fn new_enum_type(name string, module_name string) &EnumType {
return &EnumType{
name: name
module_name: module_name
}
}
pub fn (s &EnumType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
}
pub fn (s &EnumType) substitute_generics(_ map[string]Type) Type {
return s
}
================================================
FILE: src/analyzer/psi/types/FixedArrayType.v
================================================
module types
pub struct FixedArrayType {
pub:
inner Type
size int
}
pub fn new_fixed_array_type(inner Type, size int) &FixedArrayType {
return &FixedArrayType{
inner: inner
size: size
}
}
pub fn (s &FixedArrayType) name() string {
return '[${s.size}]${s.inner.name()}'
}
pub fn (s &FixedArrayType) qualified_name() string {
return '[${s.size}]${s.inner.qualified_name()}'
}
pub fn (s &FixedArrayType) readable_name() string {
return '[${s.size}]${s.inner.readable_name()}'
}
pub fn (s &FixedArrayType) module_name() string {
return s.inner.module_name()
}
pub fn (s &FixedArrayType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
s.inner.accept(mut visitor)
}
pub fn (s &FixedArrayType) substitute_generics(name_map map[string]Type) Type {
return new_fixed_array_type(s.inner.substitute_generics(name_map), s.size)
}
================================================
FILE: src/analyzer/psi/types/FunctionType.v
================================================
module types
import strings
pub struct FunctionType {
BaseType
pub:
params []Type
result Type
no_result bool
}
pub fn new_function_type(module_name string, params []Type, result Type, no_result bool) &FunctionType {
return &FunctionType{
params: params
result: result
no_result: no_result
module_name: module_name
}
}
pub fn (s &FunctionType) name() string {
mut sb := strings.new_builder(20)
sb.write_string('fn (')
for index, param in s.params {
sb.write_string(param.name())
if index < s.params.len - 1 {
sb.write_string(', ')
}
}
sb.write_string(')')
if !s.no_result {
sb.write_string(' ')
sb.write_string(s.result.name())
}
return sb.str()
}
pub fn (s &FunctionType) qualified_name() string {
mut sb := strings.new_builder(20)
sb.write_string('fn (')
for index, param in s.params {
sb.write_string(param.qualified_name())
if index < s.params.len - 1 {
sb.write_string(', ')
}
}
sb.write_string(')')
if !s.no_result {
sb.write_string(' ')
sb.write_string(s.result.qualified_name())
}
return sb.str()
}
pub fn (s &FunctionType) readable_name() string {
mut sb := strings.new_builder(20)
sb.write_string('fn (')
for index, param in s.params {
sb.write_string(param.readable_name())
if index < s.params.len - 1 {
sb.write_string(', ')
}
}
sb.write_string(')')
if !s.no_result {
sb.write_string(' ')
sb.write_string(s.result.readable_name())
}
return sb.str()
}
pub fn (s &FunctionType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
for param in s.params {
param.accept(mut visitor)
}
s.result.accept(mut visitor)
}
pub fn (s &FunctionType) substitute_generics(name_map map[string]Type) Type {
params := s.params.map(it.substitute_generics(name_map))
result := s.result.substitute_generics(name_map)
return new_function_type(s.module_name, params, result, s.no_result)
}
================================================
FILE: src/analyzer/psi/types/GenericInstantiationType.v
================================================
module types
pub struct GenericInstantiationType {
pub:
inner Type
specialization []Type
}
pub fn new_generic_instantiation_type(inner Type, specialization []Type) &GenericInstantiationType {
return &GenericInstantiationType{
inner: inner
specialization: specialization
}
}
pub fn (s &GenericInstantiationType) name() string {
return '${s.inner.name()}[${s.specialization.map(it.name()).join(', ')}]'
}
pub fn (s &GenericInstantiationType) qualified_name() string {
return '${s.inner.qualified_name()}[${s.specialization.map(it.qualified_name()).join(', ')}]'
}
pub fn (s &GenericInstantiationType) readable_name() string {
return '${s.inner.readable_name()}[${s.specialization.map(it.readable_name()).join(', ')}]'
}
pub fn (s &GenericInstantiationType) module_name() string {
return s.inner.module_name()
}
pub fn (s &GenericInstantiationType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
s.inner.accept(mut visitor)
for specialization in s.specialization {
specialization.accept(mut visitor)
}
}
pub fn (s &GenericInstantiationType) substitute_generics(name_map map[string]Type) Type {
inner := s.inner.substitute_generics(name_map)
specialization := s.specialization.map(it.substitute_generics(name_map))
return new_generic_instantiation_type(inner, specialization)
}
================================================
FILE: src/analyzer/psi/types/GenericType.v
================================================
module types
pub struct GenericType {
BaseNamedType
}
pub fn new_generic_type(name string) &GenericType {
return &GenericType{
name: name
}
}
pub fn (s &GenericType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
}
pub fn (s &GenericType) substitute_generics(name_map map[string]Type) Type {
return name_map[s.name] or { return s }
}
================================================
FILE: src/analyzer/psi/types/InterfaceType.v
================================================
module types
pub struct InterfaceType {
BaseNamedType
}
pub fn new_interface_type(name string, module_name string) &InterfaceType {
return &InterfaceType{
name: name
module_name: module_name
}
}
pub fn (s &InterfaceType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
}
pub fn (s &InterfaceType) substitute_generics(name_map map[string]Type) Type {
return s
}
================================================
FILE: src/analyzer/psi/types/MapType.v
================================================
module types
pub struct MapType {
BaseType
pub:
key Type
value Type
}
pub fn new_map_type(module_name string, key Type, value Type) &MapType {
return &MapType{
key: key
value: value
module_name: module_name
}
}
pub fn (s &MapType) name() string {
return 'map[${s.key.name()}]${s.value.name()}'
}
pub fn (s &MapType) qualified_name() string {
return 'map[${s.key.name()}]${s.value.qualified_name()}'
}
pub fn (s &MapType) readable_name() string {
return 'map[${s.key.name()}]${s.value.readable_name()}'
}
pub fn (s &MapType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
s.key.accept(mut visitor)
s.value.accept(mut visitor)
}
pub fn (s &MapType) substitute_generics(name_map map[string]Type) Type {
return new_map_type(s.module_name, s.key.substitute_generics(name_map),
s.value.substitute_generics(name_map))
}
================================================
FILE: src/analyzer/psi/types/MultiReturnType.v
================================================
module types
pub struct MultiReturnType {
pub:
types []Type
}
pub fn new_multi_return_type(types []Type) &MultiReturnType {
return &MultiReturnType{
types: types
}
}
pub fn (s &MultiReturnType) name() string {
return '(${s.types.map(it.name()).join(', ')})'
}
pub fn (s &MultiReturnType) qualified_name() string {
return '(${s.types.map(it.qualified_name()).join(', ')})'
}
pub fn (s &MultiReturnType) readable_name() string {
return '(${s.types.map(it.readable_name()).join(', ')})'
}
pub fn (s &MultiReturnType) module_name() string {
return ''
}
pub fn (s &MultiReturnType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
for type_ in s.types {
type_.accept(mut visitor)
}
}
pub fn (s &MultiReturnType) substitute_generics(name_map map[string]Type) Type {
return new_multi_return_type(s.types.map(it.substitute_generics(name_map)))
}
================================================
FILE: src/analyzer/psi/types/OptionType.v
================================================
module types
pub struct OptionType {
pub:
inner Type
no_inner bool
}
pub fn new_option_type(inner Type, no_inner bool) &OptionType {
return &OptionType{
inner: inner
no_inner: no_inner
}
}
pub fn (s &OptionType) name() string {
if s.no_inner {
return '?'
}
return '?${s.inner.name()}'
}
pub fn (s &OptionType) qualified_name() string {
if s.no_inner {
return '?'
}
return '?${s.inner.qualified_name()}'
}
pub fn (s &OptionType) readable_name() string {
if s.no_inner {
return '?'
}
return '?${s.inner.readable_name()}'
}
pub fn (s &OptionType) module_name() string {
return s.inner.module_name()
}
pub fn (s &OptionType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
s.inner.accept(mut visitor)
}
pub fn (s &OptionType) substitute_generics(name_map map[string]Type) Type {
return new_option_type(s.inner.substitute_generics(name_map), s.no_inner)
}
================================================
FILE: src/analyzer/psi/types/PointerType.v
================================================
module types
pub struct PointerType {
pub:
inner Type
}
pub fn new_pointer_type(inner Type) &PointerType {
return &PointerType{
inner: inner
}
}
pub fn (s &PointerType) name() string {
return '&${s.inner.name()}'
}
pub fn (s &PointerType) qualified_name() string {
return '&${s.inner.qualified_name()}'
}
pub fn (s &PointerType) readable_name() string {
return '&${s.inner.readable_name()}'
}
pub fn (s &PointerType) module_name() string {
return s.inner.module_name()
}
pub fn (s &PointerType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
s.inner.accept(mut visitor)
}
pub fn (s &PointerType) substitute_generics(name_map map[string]Type) Type {
return new_pointer_type(s.inner.substitute_generics(name_map))
}
================================================
FILE: src/analyzer/psi/types/PrimitiveType.v
================================================
module types
pub struct PrimitiveType {
pub:
name string
}
pub fn new_primitive_type(name string) &PrimitiveType {
return &PrimitiveType{
name: name
}
}
fn (s &PrimitiveType) name() string {
return s.name
}
fn (s &PrimitiveType) qualified_name() string {
return s.name
}
fn (s &PrimitiveType) readable_name() string {
return s.name
}
pub fn (s &PrimitiveType) module_name() string {
return 'builtin'
}
pub fn is_primitive_type(typ string) bool {
return typ in ['i8', 'i16', 'i32', 'int', 'i64', 'byte', 'u8', 'u16', 'u32', 'u64', 'f32',
'f64', 'char', 'bool', 'rune', 'usize', 'isize']
}
pub fn (s &PrimitiveType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
}
pub fn (s &PrimitiveType) substitute_generics(name_map map[string]Type) Type {
return s
}
================================================
FILE: src/analyzer/psi/types/ResultType.v
================================================
module types
pub struct ResultType {
pub:
inner Type
no_inner bool
}
pub fn new_result_type(inner Type, no_inner bool) &ResultType {
return &ResultType{
inner: inner
no_inner: no_inner
}
}
pub fn (s &ResultType) name() string {
if s.no_inner {
return '!'
}
return '!${s.inner.name()}'
}
pub fn (s &ResultType) qualified_name() string {
if s.no_inner {
return '!'
}
return '!${s.inner.qualified_name()}'
}
pub fn (s &ResultType) readable_name() string {
if s.no_inner {
return '!'
}
return '!${s.inner.readable_name()}'
}
pub fn (s &ResultType) module_name() string {
return s.inner.module_name()
}
pub fn (s &ResultType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
s.inner.accept(mut visitor)
}
pub fn (s &ResultType) substitute_generics(name_map map[string]Type) Type {
return new_result_type(s.inner.substitute_generics(name_map), s.no_inner)
}
================================================
FILE: src/analyzer/psi/types/StructType.v
================================================
module types
pub const string_type = new_struct_type('string', 'builtin')
pub const builtin_array_type = new_struct_type('array', 'builtin')
pub const builtin_map_type = new_struct_type('map', 'builtin')
pub const array_init_type = new_struct_type('ArrayInit', 'stubs')
pub const chan_init_type = new_struct_type('ChanInit', 'stubs')
pub const flag_enum_type = new_enum_type('FlagEnum', 'stubs')
pub const any_type = new_alias_type('Any', 'stubs', unknown_type)
pub struct StructType {
BaseNamedType
}
pub fn new_struct_type(name string, module_name string) &StructType {
return &StructType{
name: name
module_name: module_name
}
}
pub fn (s &StructType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
}
pub fn (s &StructType) substitute_generics(name_map map[string]Type) Type {
return s
}
================================================
FILE: src/analyzer/psi/types/ThreadType.v
================================================
module types
pub struct ThreadType {
inner Type
}
pub fn new_thread_type(inner Type) &ThreadType {
return &ThreadType{
inner: inner
}
}
pub fn (s &ThreadType) name() string {
return 'thread ${s.inner.name()}'
}
pub fn (s &ThreadType) qualified_name() string {
return 'thread ${s.inner.qualified_name()}'
}
pub fn (s &ThreadType) readable_name() string {
return 'thread ${s.inner.readable_name()}'
}
pub fn (s &ThreadType) module_name() string {
return s.inner.module_name()
}
pub fn (s &ThreadType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
s.inner.accept(mut visitor)
}
pub fn (s &ThreadType) substitute_generics(name_map map[string]Type) Type {
return new_thread_type(s.inner.substitute_generics(name_map))
}
================================================
FILE: src/analyzer/psi/types/Type.v
================================================
module types
pub interface Type {
name() string
qualified_name() string
readable_name() string
module_name() string
substitute_generics(name_map map[string]Type) Type
accept(mut visitor TypeVisitor)
}
================================================
FILE: src/analyzer/psi/types/TypeVisitor.v
================================================
module types
pub interface TypeVisitor {
mut:
enter(typ Type) bool
}
================================================
FILE: src/analyzer/psi/types/UnknownType.v
================================================
module types
pub const unknown_type = new_unknown_type()
pub struct UnknownType {}
// new_unknown_type creates a new unknown type.
// Use `unknown_type` constant instead.
fn new_unknown_type() &UnknownType {
return &UnknownType{}
}
fn (_ &UnknownType) name() string {
return 'unknown'
}
fn (_ &UnknownType) qualified_name() string {
return 'unknown'
}
fn (_ &UnknownType) readable_name() string {
return 'unknown'
}
fn (_ &UnknownType) module_name() string {
return ''
}
pub fn (s &UnknownType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
}
pub fn (s &UnknownType) substitute_generics(name_map map[string]Type) Type {
return s
}
================================================
FILE: src/analyzer/psi/types/VoidPtrType.v
================================================
module types
pub const voidptr_type = new_voidptr_type()
pub struct VoidPtrType {}
fn new_voidptr_type() &VoidPtrType {
return &VoidPtrType{}
}
fn (_ &VoidPtrType) name() string {
return 'voidptr'
}
fn (_ &VoidPtrType) qualified_name() string {
return 'voidptr'
}
fn (_ &VoidPtrType) readable_name() string {
return 'voidptr'
}
fn (_ &VoidPtrType) module_name() string {
return ''
}
pub fn (s &VoidPtrType) accept(mut visitor TypeVisitor) {
if !visitor.enter(s) {
return
}
}
pub fn (s &VoidPtrType) substitute_generics(name_map map[string]Type) Type {
return s
}
================================================
FILE: src/analyzer/psi/types/helpers.v
================================================
module types
pub fn unwrap_pointer_type(typ Type) Type {
if typ is PointerType {
return typ.inner
}
return typ
}
pub fn unwrap_alias_type(typ Type) Type {
if typ is AliasType {
return typ.inner
}
return typ
}
pub fn unwrap_channel_type(typ Type) Type {
if typ is ChannelType {
return typ.inner
}
return typ
}
pub fn unwrap_result_or_option_type(typ Type) Type {
if typ is ResultType {
return typ.inner
}
if typ is OptionType {
return typ.inner
}
return typ
}
pub fn unwrap_result_or_option_type_if(typ Type, condition bool) Type {
if condition {
return unwrap_result_or_option_type(typ)
}
return typ
}
pub fn unwrap_generic_instantiation_type(typ Type) Type {
if typ is GenericInstantiationType {
return typ.inner
}
return typ
}
pub fn is_builtin_array_type(typ Type) bool {
if typ is StructType {
return typ.qualified_name() == builtin_array_type.qualified_name()
}
return false
}
pub fn is_builtin_map_type(typ Type) bool {
if typ is StructType {
return typ.qualified_name() == builtin_map_type.qualified_name()
}
return false
}
struct IsGenericVisitor {
mut:
is_generic bool
}
fn (mut v IsGenericVisitor) enter(typ Type) bool {
if typ is GenericType {
v.is_generic = true
return false
}
return true
}
pub fn is_generic(typ Type) bool {
if typ is GenericType {
return true
}
mut v := IsGenericVisitor{}
typ.accept(mut v)
return v.is_generic
}
================================================
FILE: src/analyzer/psi/types_util.v
================================================
module psi
import analyzer.psi.types
pub fn own_methods_list(typ types.Type) []PsiElement {
module_name := typ.module_name()
name := typ.name()
if module_name == '' || name == '' {
return []
}
key := '${module_name}.${name}'
methods := stubs_index.get_elements_by_name(.methods, key)
return methods
}
pub fn fields_list(typ types.Type) []PsiElement {
name := typ.qualified_name()
structs := stubs_index.get_elements_by_name(.structs, name)
if structs.len == 0 {
return []
}
struct_ := structs.first()
if struct_ is StructDeclaration {
return struct_.fields()
}
return []
}
pub fn methods_list(typ types.Type) []PsiElement {
mut result := own_methods_list(typ)
unwrapped := types.unwrap_alias_type(types.unwrap_pointer_type(typ))
if unwrapped.qualified_name() != typ.qualified_name() {
// if after unwrapping alias type we get another type, we need
// to collect their methods as well.
result << own_methods_list(unwrapped)
}
if typ is types.InterfaceType {
if interface_ := find_interface(typ.qualified_name()) {
embedded_types := interface_.embedded_definitions().map(it.get_type())
for embedded_type in embedded_types {
result << methods_list(embedded_type)
}
}
}
if typ is types.StructType {
if struct_ := find_struct(typ.qualified_name()) {
embedded_types := struct_.embedded_definitions().map(it.get_type())
for embedded_type in embedded_types {
result << methods_list(embedded_type)
}
}
}
return result
}
pub fn find_method(typ types.Type, name string) ?PsiElement {
methods := methods_list(typ)
for method in methods {
if method is PsiNamedElement {
if method.name() == name {
return method as PsiElement
}
}
}
return none
}
pub fn static_methods_list(typ types.Type) []PsiElement {
module_name := typ.module_name()
name := typ.name()
if module_name == '' || name == '' {
return []
}
key := '${module_name}.${name}'
methods := stubs_index.get_elements_by_name(.static_methods, key)
return methods
}
================================================
FILE: src/analyzer/psi/utils.v
================================================
module psi
pub fn get_it_call(element PsiElement) ?&CallExpression {
mut parent_call := element.parent_of_type(.call_expression)?
if mut parent_call is CallExpression {
for {
expression := parent_call.expression() or { break }
if expression.is_parent_of(element) {
// when it used as expression of call
// it.foo()
parent_call = parent_call.parent_of_type(.call_expression) or { break }
continue
}
break
}
}
methods_names := ['filter', 'map', 'any', 'all']
if mut parent_call is CallExpression && is_array_method_call(parent_call, ...methods_names) {
return parent_call
}
return none
}
pub fn is_array_method_call(element CallExpression, names ...string) bool {
ref_expression := element.ref_expression() or { return false }
last_child := (ref_expression as PsiElement).last_child() or { return false }
called_name := last_child.get_text()
return called_name in names
}
================================================
FILE: src/analyzer/psi/walk.v
================================================
module psi
// inspect traverses an AST in depth-first order: It starts by calling
// `cb(node)`; node must not be `nil`. If call returns `true`, `inspect` invokes `cb`
// recursively for each of the non-nil children of node. Otherwise, `inspect` skips
// the children of node.
//
// Example:
// ```
// inspect(root, fn (node PsiElement) bool {
// match node {
// psi.FunctionDeclaration {
// println('function ${node.name()}')
// }
// else {}
// }
// return true
// })
pub fn inspect(node PsiElement, cb fn (PsiElement) bool) {
inspector := Inspector{
cb: cb
}
node.accept(inspector)
}
struct Inspector {
cb fn (src.analyzer.psi.PsiElement) bool = unsafe { nil }
}
fn (r &Inspector) visit_element(element PsiElement) {
if !r.visit_element_impl(element) {
return
}
mut child := element.first_child() or { return }
for {
child.accept(r)
child = child.next_sibling() or { break }
}
}
fn (i &Inspector) visit_element_impl(element PsiElement) bool {
return i.cb(create_element(element.node(), element.containing_file()))
}
================================================
FILE: src/bytes/README.md
================================================
# Description
`bytes` module implements serialization and deserialization data to bytes.
================================================
FILE: src/bytes/deserialize.v
================================================
module bytes
pub struct Deserializer {
mut:
index int
data []u8
}
pub fn new_deserializer(data []u8) Deserializer {
return Deserializer{
data: data
}
}
pub fn (mut s Deserializer) read_string() string {
len := s.read_int()
if len == 0 {
return ''
}
data := s.data[s.index..s.index + len]
s.index += len
return data.bytestr()
}
pub fn (mut s Deserializer) read_int() int {
first := s.read_u8()
if first == 1 {
return s.read_u8()
}
data := s.data[s.index..s.index + 4]
s.index += 4
mut res := 0
for index, datum in data {
res |= datum << (8 * (3 - index))
}
return res
}
pub fn (mut s Deserializer) read_i64() i64 {
data := s.data[s.index..s.index + 8]
s.index += 8
mut res := 0
for index, datum in data {
res |= datum << (8 * (7 - index))
}
return res
}
@[inline]
pub fn (mut s Deserializer) read_u8() u8 {
index := s.index
s.index++
return s.data[index]
}
================================================
FILE: src/bytes/serialization_test.v
================================================
module bytes
fn test_serialize_deserialize() {
mut s := Serializer{}
s.write_string('hello')
s.write_int(560000)
mut d := Deserializer{
data: s.data
}
assert d.read_string() == 'hello'
assert d.read_int() == 560000
}
fn test_serialize_deserialize_several_strings() {
mut s := Serializer{}
s.write_string('hello')
s.write_string('world')
mut d := Deserializer{
data: s.data
}
assert d.read_string() == 'hello'
assert d.read_string() == 'world'
}
fn test_serialize_deserialize_stub_element() {
mut s := Serializer{}
data := StubBase{
name: 'test'
text_range: TextRange{
line: 1
column: 2
end_line: 3
end_column: 4
}
stub_type: .function_declaration
id: 123456
text: 'some text with spaces'
comment: '// comment data'
receiver: 'Foo'
}
serialize_stub_element(mut s, data)
println(s.data.bytestr())
mut d := Deserializer{
data: s.data
}
data2 := deserialize_stub_element(mut d)
assert data2.name == data.name
assert data2.text_range.line == data.text_range.line
assert data2.text_range.column == data.text_range.column
assert data2.text_range.end_line == data.text_range.end_line
assert data2.text_range.end_column == data.text_range.end_column
assert data2.stub_type == data.stub_type
assert data2.id == data.id
assert data2.text == data.text
assert data2.comment == data.comment
assert data2.receiver == data.receiver
}
fn serialize_stub_element(mut s Serializer, stub StubBase) {
s.write_string(stub.name)
s.write_int(stub.text_range.line)
s.write_int(stub.text_range.column)
s.write_int(stub.text_range.end_line)
s.write_int(stub.text_range.end_column)
s.write_u8(u8(stub.stub_type))
s.write_int(stub.id)
s.write_string(stub.text)
s.write_string(stub.comment)
s.write_string(stub.receiver)
}
fn deserialize_stub_element(mut s Deserializer) StubBase {
name := s.read_string()
text_range := TextRange{
line: s.read_int()
column: s.read_int()
end_line: s.read_int()
end_column: s.read_int()
}
stub_type := unsafe { StubType(s.read_u8()) }
id := s.read_int()
text := s.read_string()
comment := s.read_string()
receiver := s.read_string()
return StubBase{
name: name
text_range: text_range
stub_type: stub_type
id: id
text: text
comment: comment
receiver: receiver
}
}
pub type StubId = int
pub struct StubData {
pub:
text string
comment string
receiver string
}
pub struct StubBase {
StubData
pub:
name string
text_range TextRange
// stub_list &StubList
// parent &StubElement
stub_type StubType
pub mut:
id StubId
}
pub struct TextRange {
pub:
line int
column int
end_line int
end_column int
}
pub enum StubType as u8 {
root
function_declaration
method_declaration
receiver
signature
struct_declaration
enum_declaration
field_declaration
struct_field_scope
enum_field_definition
constant_declaration
type_alias_declaration
attributes
attribute
attribute_expression
value_attribute
plain_type
}
================================================
FILE: src/bytes/serialize.v
================================================
module bytes
pub struct Serializer {
pub mut:
data []u8
}
@[inline]
pub fn (mut s Serializer) write_u8(data u8) {
s.data << data
}
@[inline]
pub fn (mut s Serializer) write_string(str string) {
s.write_int(str.len)
unsafe { s.data.push_many(str.str, str.len) }
}
pub fn (mut s Serializer) write_int(data int) {
if data > 0 && data < 0xff {
s.data << 1
s.data << u8(data)
return
}
s.data << 0
for i in 0 .. 4 {
s.data << u8(data >> (8 * (3 - i))) & 0xff
}
}
pub fn (mut s Serializer) write_i64(data i64) {
for i in 0 .. 8 {
s.data << u8(data >> (8 * (7 - i))) & 0xff
}
}
================================================
FILE: src/check-updates.v
================================================
module main
import cli
fn check_updates_cmd(_ cli.Command) ! {
download_install_vsh()!
call_install_vsh('check-updates')!
}
================================================
FILE: src/clear-cache.v
================================================
module main
import cli
import os
import config
fn clear_cache_cmd(_ cli.Command) ! {
global_config_path := config.analyzer_caches_path
if !os.exists(global_config_path) {
warnln('No global cache found at: ${global_config_path}')
return
}
if !os.is_dir(global_config_path) {
warnln('Global cache directory is not a directory: ${global_config_path}')
return
}
println('Clearing cache...')
println('Found global cache at: ${global_config_path}')
os.rmdir_all(global_config_path) or { errorln('Failed to clear cache: ${err}') }
successln('Cache cleared')
}
================================================
FILE: src/config/EditorConfig.v
================================================
module config
import toml
pub enum SemanticTokensMode {
full
syntax
none_
}
pub struct InlayHintsConfig {
pub mut:
enable bool = true
enable_range_hints bool = true
enable_type_hints bool = true
enable_implicit_err_hints bool = true
enable_parameter_name_hints bool = true
enable_constant_type_hints bool = true
enable_enum_field_value_hints bool = true
}
pub struct CodeLensConfig {
pub mut:
enable bool = true
enable_run_lens bool = true
enable_inheritors_lens bool = true
enable_super_interfaces_lens bool = true
enable_run_tests_lens bool = true
}
pub struct EditorConfig {
pub:
root string
path string
pub mut:
custom_vroot string
custom_cache_dir string
inlay_hints InlayHintsConfig
enable_semantic_tokens SemanticTokensMode = SemanticTokensMode.full
code_lens CodeLensConfig
}
pub fn from_toml(root string, path string, content string) !EditorConfig {
mut econfig := EditorConfig{
root: root
path: path
}
res := toml.parse_text(content)!
custom_vroot_value := res.value('custom_vroot')
if custom_vroot_value is string {
econfig.custom_vroot = custom_vroot_value
}
custom_cache_dir := res.value('custom_cache_dir')
if custom_cache_dir is string {
econfig.custom_cache_dir = custom_cache_dir
}
enable_semantic_tokens := res.value('enable_semantic_tokens')
if enable_semantic_tokens is string {
econfig.enable_semantic_tokens = match enable_semantic_tokens {
'full' { SemanticTokensMode.full }
'syntax' { SemanticTokensMode.syntax }
'none' { SemanticTokensMode.none_ }
else { SemanticTokensMode.full }
}
}
inlay_hints_table := res.value('inlay_hints')
enable_value := inlay_hints_table.value('enable')
econfig.inlay_hints.enable = if enable_value is toml.Null {
true // default to true
} else {
enable_value.bool()
}
enable_range_hints_value := inlay_hints_table.value('enable_range_hints')
econfig.inlay_hints.enable_range_hints = if enable_range_hints_value is toml.Null {
true // default to true
} else {
enable_range_hints_value.bool()
}
enable_type_hints_value := inlay_hints_table.value('enable_type_hints')
econfig.inlay_hints.enable_type_hints = if enable_type_hints_value is toml.Null {
true // default to true
} else {
enable_type_hints_value.bool()
}
enable_implicit_err_hints := inlay_hints_table.value('enable_implicit_err_hints')
econfig.inlay_hints.enable_implicit_err_hints = if enable_implicit_err_hints is toml.Null {
true // default to true
} else {
enable_implicit_err_hints.bool()
}
enable_parameter_name_hints := inlay_hints_table.value('enable_parameter_name_hints')
econfig.inlay_hints.enable_parameter_name_hints = if enable_parameter_name_hints is toml.Null {
true // default to true
} else {
enable_parameter_name_hints.bool()
}
enable_constant_type_hints := inlay_hints_table.value('enable_constant_type_hints')
econfig.inlay_hints.enable_constant_type_hints = if enable_constant_type_hints is toml.Null {
true // default to true
} else {
enable_constant_type_hints.bool()
}
enable_enum_field_value_hints := inlay_hints_table.value('enable_enum_field_value_hints')
econfig.inlay_hints.enable_enum_field_value_hints = if enable_enum_field_value_hints is toml.Null {
true // default to true
} else {
enable_enum_field_value_hints.bool()
}
code_lens_table := res.value('code_lens')
enable_lens_value := code_lens_table.value('enable')
econfig.code_lens.enable = if enable_lens_value is toml.Null {
true // default to true
} else {
enable_lens_value.bool()
}
enable_run_lens := code_lens_table.value('enable_run_lens')
econfig.code_lens.enable_run_lens = if enable_run_lens is toml.Null {
true // default to true
} else {
enable_run_lens.bool()
}
enable_inheritors_lens := code_lens_table.value('enable_inheritors_lens')
econfig.code_lens.enable_inheritors_lens = if enable_inheritors_lens is toml.Null {
true // default to true
} else {
enable_inheritors_lens.bool()
}
enable_super_interfaces_lens := code_lens_table.value('enable_super_interfaces_lens')
econfig.code_lens.enable_super_interfaces_lens = if enable_super_interfaces_lens is toml.Null {
true // default to true
} else {
enable_super_interfaces_lens.bool()
}
enable_run_tests_lens := code_lens_table.value('enable_run_tests_lens')
econfig.code_lens.enable_run_tests_lens = if enable_run_tests_lens is toml.Null {
true // default to true
} else {
enable_run_tests_lens.bool()
}
return econfig
}
pub fn (e &EditorConfig) path() string {
if e.path.starts_with(e.root) {
return e.path[e.root.len + 1..]
}
return e.path
}
pub fn (e &EditorConfig) is_local() bool {
return e.path.starts_with(e.root)
}
================================================
FILE: src/config/constants.v
================================================
module config
import os
import metadata
// analyzer_name is the name of the analyzer.
pub const analyzer_name = 'v-analyzer'
// analyzer_config_name is the name of the analyzer's configuration
pub const analyzer_config_name = 'config.toml'
// analyzer_configs_path is the path to the directory containing the
// root configuration files for the analyzer.
pub const analyzer_configs_path = os.join_path(os.home_dir(), '.config', 'v-analyzer')
// analyzer_local_configs_folder_name is the name of the directory
// containing the local configuration files for the analyzer.
pub const analyzer_local_configs_folder_name = '.v-analyzer'
// analyzer_log_file_name is the name of the log file for the analyzer.
pub const analyzer_log_file_name = 'v-analyzer.log'
// analyzer_logs_path is the path to the directory containing the
// logs for the analyzer.
pub const analyzer_logs_path = os.join_path(analyzer_configs_path, 'logs')
// analyzer_global_config_path is the path to the global configuration
// file for the analyzer.
pub const analyzer_global_config_path = os.join_path(analyzer_configs_path, analyzer_config_name)
// analyzer_caches_path is the path to the directory containing the
// cache files for the analyzer.
pub const analyzer_caches_path = os.join_path(os.cache_dir(), 'v-analyzer',
'${metadata.build_commit}_${os.file_last_mod_unix(os.executable())}')
// analyzer_stubs_path is the path to the directory containing the
// unpacked stub files for the analyzer.
pub const analyzer_stubs_path = os.join_path(analyzer_configs_path, 'metadata')
// analyzer_stubs_version_path is the path to the file containing the version of the stubs.
pub const analyzer_stubs_version_path = os.join_path(analyzer_stubs_path, 'version.txt')
pub const default = '# Specifies the path to the V installation directory with `v` executable.
# If not set, the plugin will try to find it on its own.
# Set it if you get errors like "Cannot find V standard library!".
#custom_vroot = "~/v"
# Specifies the path where to store the cache.
# By default, it is stored in the system\'s cache directory.
# You can set it to `./` to store the cache in the project\'s directory, this is useful
# if you want to debug the analyzer.
# Basically, you don\'t need to set it.
#custom_cache_dir = "./"
# Specifies whenever to enable semantic tokens or not.
# - `full` — enables all semantic tokens. In this mode analyzer resolves all symbols
# in the file to provide the most accurate highlighting.
# - `syntax` — enables only syntax tokens, such tokens highlight structural elements
# such as field names or import names.
# The fastest option, which is always enabled when the file contains more than 1000 lines.
# - `none` — disables semantic tokens.
# By default, `full` for files with less than 1000 lines, `syntax` for files with more.
enable_semantic_tokens = "full"
# Specifies inlay hints to show.
[inlay_hints]
# Specifies whenever to enable inlay hints or not.
# By default, they are enabled.
enable = true
# Specifies whenever to show type hints for ranges or not.
# Example:
# ```
# 0 ≤ .. < 10
# ^ ^
# ```
# or:
# ```
# a[0 ≤ .. < 10]
# ^ ^
# ```
enable_range_hints = true
# Specifies whenever to show type hints for variables or not.
# Example:
# ```
# name : Foo := foo()
# ^^^^^
# ```
enable_type_hints = true
# Specifies whenever to show hints for implicit err variables or not.
# Example:
# ```
# foo() or { err ->
# ^^^^^^
# }
# ```
enable_implicit_err_hints = true
# Specifies whenever to show hints for function parameters in call or not.
# Example:
# ```
# fn foo(a int, b int) int {}
#
# foo(a: 1, b: 2)
# ^^ ^^
enable_parameter_name_hints = true
# Specifies whenever to show type hints for constants or not.
# Example:
# ```
# const foo : int = 1
# ^^^^^
# ```
enable_constant_type_hints = true
# Specifies whenever to show hints for enum field values or not.
# Example:
# ```
# enum Foo {
# bar = 0
# ^^^
# baz = 1
# ^^^
# }
# ```
enable_enum_field_value_hints = true
# Specifies code lenses to show.
[code_lens]
# Specifies whenever to enable code lenses or not.
# By default, they are enabled.
enable = true
# Specifies whenever to show code lenses for main function to run current directory or not.
# Example:
# ```
# ▶ Run
# fn main() {}
# ```
enable_run_lens = true
# Specifies whenever to show code lenses for interface inheritors or not.
# Example:
# ```
# 2 implementations
# interface Foo {}
# ```
enable_inheritors_lens = true
# Specifies whenever to show code lenses for structs implementing interfaces or not.
# Example:
# ```
# implemented 2 interfaces
# struct Boo {}
# ```
enable_super_interfaces_lens = true
# Specifies whenever to show code lenses for test functions to run test or whole file or not.
# Example:
# ```
# ▶ Run test | all file tests
# fn test_foo() {}
# ```
# Note: "all file tests" is shown only for the first test function in the file.
enable_run_tests_lens = true
'
================================================
FILE: src/init.v
================================================
module main
import cli
import os
import config
import readline
import term
fn init_cmd(cmd cli.Command) ! {
pwd := os.getwd()
if pwd == '' {
return error('Cannot get current working directory')
}
directory_for_config := os.join_path(pwd, config.analyzer_local_configs_folder_name)
if !os.exists(directory_for_config) {
os.mkdir_all(directory_for_config) or {
return error("Cannot create '${directory_for_config}' directory for config: ${err}")
}
println("${term.green('✓')} Created '${config.analyzer_local_configs_folder_name}' directory for config")
}
config_file := os.join_path(directory_for_config, config.analyzer_config_name)
if os.exists(config_file) {
warnln("Config file '${config_file}' already exists")
read_line := readline.read_line('Want to overwrite it? [y/N] ') or {
errorln('Cannot read line: ${err}')
'N'
}
overwrite := read_line.trim_space() == 'y'
if !overwrite {
return
}
}
os.write_file(config_file, config.default) or {
return error("Cannot write config file '${config_file}': ${err}")
}
println("${term.green('✓')} Successfully created config file '${config_file}'")
}
================================================
FILE: src/jsonrpc/README.md
================================================
# Description
`jsonrpc` module describes the JSON-RPC 2.0 implementation.
================================================
FILE: src/jsonrpc/jsonrpc.v
================================================
// Copyright (c) 2022 Ned Palacios. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module jsonrpc
import json
import strings
import io
pub const version = '2.0'
// see
// - https://www.jsonrpc.org/specification#error_object
// - http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
// Invalid JSON was received by the server.
// An error occurred on the server while parsing the JSON text.
pub const parse_error = error_with_code('Invalid JSON.', -32700)
// The JSON sent is not a valid Request object.
pub const invalid_request = error_with_code('Invalid request.', -32600)
// The method does not exist / is not available.
pub const method_not_found = error_with_code('Method not found.', -32601)
// Invalid method parameter(s).
pub const invalid_params = error_with_code('Invalid params', -32602)
// Internal JSON-RPC error.
pub const internal_error = error_with_code('Internal error.', -32693)
// Server errors.
pub const server_error_start = error_with_code('Error occurred when starting server.', -32099)
pub const server_not_initialized = error_with_code('Server not initialized.', -32002)
pub const unknown_error = error_with_code('Unknown error.', -32001)
pub const server_error_end = error_with_code('Error occurred when stopping the server.', -32000)
pub const error_codes = [
parse_error.code(),
invalid_request.code(),
method_not_found.code(),
invalid_params.code(),
internal_error.code(),
server_error_start.code(),
server_not_initialized.code(),
server_error_end.code(),
unknown_error.code(),
]
// Null represents the null value in JSON.
pub struct Null {}
pub const null = Null{}
// Request is a representation of a rpc call to the server.
// https://www.jsonrpc.org/specification#request_object
pub struct Request {
pub mut:
jsonrpc string = version
id string @[raw]
method string
params string @[raw]
}
// json returns the JSON string form of the Request.
pub fn (req Request) json() string {
// NOTE: make request act as a notification for server_test_utils
id_payload := if req.id.len != 0 { ',"id":${req.id},' } else { ',' }
return '{"jsonrpc":"${version}"${id_payload}"method":"${req.method}","params":${req.params}}'
}
// decode_params decodes the parameters of a Request.
pub fn (req Request) decode_params[T]() !T {
return json.decode(T, req.params) or { return err }
}
// Response is a representation of server reply after an rpc call was made.
// https://www.jsonrpc.org/specification#response_object
pub struct Response[T] {
pub:
jsonrpc string = version
id string
// error ResponseError
result T
error ResponseError
}
// json returns the JSON string form of the Response
pub fn (resp Response[T]) json() string {
mut resp_wr := strings.new_builder(100)
defer {
unsafe { resp_wr.free() }
}
encode_response[T](resp, mut resp_wr)
return resp_wr.str()
}
const null_in_u8 = 'null'.bytes()
const error_field_in_u8 = ',"error":'.bytes()
const result_field_in_u8 = ',"result":'.bytes()
fn encode_response[T](resp Response[T], mut writer io.Writer) {
writer.write('{"jsonrpc":"${version}","id":'.bytes()) or {}
if resp.id.len == 0 {
writer.write(null_in_u8) or {}
} else {
writer.write(resp.id.bytes()) or {}
}
if resp.error.code != 0 {
err := json.encode(resp.error)
writer.write(error_field_in_u8) or {}
writer.write(err.bytes()) or {}
} else {
writer.write(result_field_in_u8) or {}
$if T is Null {
writer.write(null_in_u8) or {}
} $else {
res := json.encode(resp.result)
writer.write(res.bytes()) or {}
}
}
writer.write([u8(`}`)]) or {}
}
// NotificationMessage is a Request object without the ID. A Request object that is a
// Notification signifies the Client's lack of interest in the corresponding Response object,
// and as such no Response object needs to be returned to the client. The Server MUST NOT reply
// to a Notification, including those that are within a batch request.
//
// Notifications are not confirmable by definition, since they do not have a Response object to be
// returned. As such, the Client would not be aware of any errors (like e.g. "Invalid params","Internal error").
// https://www.jsonrpc.org/specification#notification
pub struct NotificationMessage[T] {
pub:
jsonrpc string = version
method string
params T
}
// json returns the JSON string form of the NotificationMessage.
pub fn (notif NotificationMessage[T]) json() string {
mut notif_wr := strings.new_builder(100)
defer {
unsafe { notif_wr.free() }
}
encode_notification[T](notif, mut notif_wr)
return notif_wr.str()
}
fn encode_notification[T](notif NotificationMessage[T], mut writer io.Writer) {
writer.write('{"jsonrpc":"${version}","method":"${notif.method}","params":'.bytes()) or {}
$if T is Null {
writer.write(null_in_u8) or {}
} $else {
res := json.encode(notif.params)
writer.write(res.bytes()) or {}
}
writer.write([u8(`}`)]) or {}
}
fn encode_request[T](notif NotificationMessage[T], mut writer io.Writer) {
writer.write('{"jsonrpc":"${version}","id": 1, "method":"${notif.method}","params":'.bytes()) or {}
$if T is Null {
writer.write(null_in_u8) or {}
} $else {
res := json.encode(notif.params)
writer.write(res.bytes()) or {}
}
writer.write([u8(`}`)]) or {}
}
// ResponseError is a representation of an error when a rpc call encounters an error.
// When a rpc call encounters an error, the Response Object MUST contain the error member
// with a value that is a Object with the following members:
// https://www.jsonrpc.org/specification#error_object
pub struct ResponseError {
pub mut:
code int
message string
data string
}
pub fn (err ResponseError) code() int {
return err.code
}
pub fn (err ResponseError) msg() string {
return err.message
}
// err returns the ResponseError as an implementation of IError.
pub fn (e ResponseError) err() IError {
return IError(e)
}
@[params]
pub struct ResponseErrorGeneratorParams {
pub:
error IError @[required]
data string
}
// response_error creates a ResponseError from the given IError.
@[inline]
pub fn response_error(params ResponseErrorGeneratorParams) ResponseError {
return ResponseError{
code: params.error.code()
message: params.error.msg()
data: params.data
}
}
================================================
FILE: src/jsonrpc/jsonrpc_test.v
================================================
import jsonrpc
fn test_response_json_null() {
resp := jsonrpc.Response[jsonrpc.Null]{
id: '1'
}
assert resp.json() == '{"jsonrpc":"2.0","id":1,"result":null}'
}
fn test_notification_json_null() {
resp := jsonrpc.NotificationMessage[jsonrpc.Null]{
method: 'test'
}
assert resp.json() == '{"jsonrpc":"2.0","method":"test","params":null}'
}
================================================
FILE: src/jsonrpc/server.v
================================================
// Copyright (c) 2022 Ned Palacios. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module jsonrpc
import io
import json
import sync
import strings
// Server represents a JSONRPC server that sends/receives data
// from a stream (an io.ReaderWriter), inspects data with interceptors
// and hands over the decoded request to a Handler.
@[heap]
pub struct Server {
mut:
req_buf strings.Builder = strings.new_builder(4096)
conlen_buf strings.Builder = strings.new_builder(4096)
res_buf strings.Builder = strings.new_builder(4096)
pub mut:
stream io.ReaderWriter
interceptors []Interceptor
handler Handler
}
// intercept_raw_request intercepts the incoming raw request buffer
// to the interceptors.
pub fn (mut s Server) intercept_raw_request(req []u8) ! {
for mut interceptor in s.interceptors {
interceptor.on_raw_request(req)!
}
}
// intercept_request intercepts the incoming decoded JSONRPC Request
// to the interceptors.
pub fn (mut s Server) intercept_request(req &Request) ! {
for mut interceptor in s.interceptors {
interceptor.on_request(req)!
}
}
// intercept_encoded_response intercepts the outgoing raw response buffer
// to the interceptors.
pub fn (mut s Server) intercept_encoded_response(resp []u8) {
for mut interceptor in s.interceptors {
interceptor.on_encoded_response(resp)
}
}
pub interface InterceptorData {}
// dispatch_event sends a custom event to the interceptors.
pub fn (mut s Server) dispatch_event(event_name string, data InterceptorData) ! {
for mut i in s.interceptors {
i.on_event(event_name, data)!
}
}
// process_raw_request decodes the raw request string into JSONRPC request.
fn (_ Server) process_raw_request(raw_request string) !Request {
json_payload := raw_request.all_after('\r\n\r\n')
return json.decode(Request, json_payload) or { return err }
}
// respond executes the incoming request into a response.
// for testing purposes only.
pub fn (mut s Server) respond() ! {
mut base_rw := s.writer()
return s.internal_respond(mut base_rw)
}
fn (mut s Server) internal_respond(mut base_rw ResponseWriter) ! {
defer {
s.req_buf.go_back_to(0)
}
s.stream.read(mut s.req_buf) or {
if err is io.Eof {
return err
}
return err
}
req := s.process_raw_request(s.req_buf.after(0)) or {
base_rw.write_error(response_error(error: parse_error))
return err
}
s.intercept_request(&req) or {
base_rw.write_error(response_error(error: err))
return err
}
mut rw := ResponseWriter{
server: s
writer: base_rw.writer
sb: base_rw.sb
req_id: req.id
}
s.handler.handle_jsonrpc(&req, mut rw) or {
// do not send response error if request is a notification
if req.id.len != 0 {
if err is none {
rw.write(null)
} else if err is ResponseError {
rw.write_error(err)
} else if err.code() !in error_codes {
rw.write_error(response_error(error: unknown_error))
} else {
rw.write_error(response_error(error: err))
}
}
return err
}
}
@[params]
pub struct NewWriterConfig {
pub:
own_buffer bool
}
// writer returns the Server's current ResponseWriter
pub fn (s &Server) writer(cfg NewWriterConfig) &ResponseWriter {
return &ResponseWriter{
server: s
writer: io.MultiWriter{
writers: [
InterceptorWriter{
interceptors: s.interceptors
},
// NOTE: writing content lengths should be an interceptor
// since there are some situations that a payload is only
// passthrough between processes and does not need a
// "repackaging" of the outgoing data
Writer{
clen_sb: if cfg.own_buffer { s.conlen_buf.clone() } else { s.conlen_buf }
read_writer: s.stream
},
]
}
sb: if cfg.own_buffer { s.res_buf.clone() } else { s.res_buf }
}
}
// start executes a loop and observes the incoming request from the stream.
pub fn (mut s Server) start() {
mut rw := s.writer()
for {
s.internal_respond(mut rw) or {
if err is io.Eof {
return
}
continue
}
}
}
// Interceptor is an interface that observes and inspects the data
// before handing over to the Handler.
pub interface Interceptor {
mut:
on_event(name string, data InterceptorData) !
on_raw_request(req []u8) !
on_request(req &Request) !
on_encoded_response(resp []u8) // we cant use generic methods without marking the interface as generic
}
// Handler is an interface that handles the JSONRPC request and
// returns a response data via a ResponseWriter.
pub interface Handler {
mut:
handle_jsonrpc(req &Request, mut wr ResponseWriter) !
}
// ResponseWriter constructs and sends a JSONRPC response to the stream.
pub struct ResponseWriter {
mut:
mutex sync.Mutex
sb strings.Builder
pub mut:
req_id string = 'null' // raw JSON
server &Server = unsafe { nil }
writer io.Writer
}
fn (mut rw ResponseWriter) flush() {
rw.writer.write(rw.sb) or {}
rw.sb.go_back_to(0)
}
// write sends the given payload to the stream.
pub fn (mut rw ResponseWriter) write[T](payload T) {
rw.mutex.@lock()
defer {
rw.mutex.unlock()
}
final_resp := Response[T]{
id: rw.req_id
result: payload
}
encode_response[T](final_resp, mut rw.sb)
rw.flush()
}
pub fn (mut rw ResponseWriter) write_empty() {
rw.write[Null](null)
}
// write_notify sends the given method and params as
// a server notification to the stream.
pub fn (mut rw ResponseWriter) write_notify[T](method string, params T) {
rw.mutex.@lock()
defer {
rw.mutex.unlock()
}
notif := NotificationMessage[T]{
method: method
params: params
}
encode_notification[T](notif, mut rw.sb)
rw.flush()
}
pub fn (mut rw ResponseWriter) write_request[T](method string, params T) {
rw.mutex.@lock()
defer {
rw.mutex.unlock()
}
notif := NotificationMessage[T]{
method: method
params: params
}
encode_request[T](notif, mut rw.sb)
rw.flush()
}
// write_error sends a ResponseError to the stream.
pub fn (mut rw ResponseWriter) write_error(err &ResponseError) {
rw.mutex.@lock()
defer {
rw.mutex.unlock()
}
final_resp := Response[string]{
id: rw.req_id
error: err
}
encode_response[string](final_resp, mut rw.sb)
rw.flush()
}
// Writer is an internal representation of a raw response writer.
// It adds a Content-Length header to the response before handing
// over to the io.ReaderWriter.
struct Writer {
mut:
clen_sb strings.Builder
read_writer io.ReaderWriter
}
fn (mut w Writer) write(byt []u8) !int {
defer {
w.clen_sb.go_back_to(0)
}
w.clen_sb.write_string('Content-Length: ${byt.len}\r\n\r\n')
w.clen_sb.write(byt) or {}
return w.read_writer.write(w.clen_sb)
}
struct InterceptorWriter {
mut:
interceptors []Interceptor
}
fn (mut wr InterceptorWriter) write(buf []u8) !int {
for mut interceptor in wr.interceptors {
interceptor.on_encoded_response(buf)
}
return buf.len
}
// PassiveHandler is an implementation of a Handler
// used as a default value for Server.handler
pub struct PassiveHandler {}
fn (mut _ PassiveHandler) handle_jsonrpc(req &Request, mut rw ResponseWriter) ! {}
// is_interceptor_enabled checks if the given T is enabled in a Server.
pub fn is_interceptor_enabled[T](server &Server) bool {
get_interceptor[T](server) or { return false }
return true
}
pub fn get_interceptor[T](server &Server) ?&T {
for inter in server.interceptors {
if inter is T {
return inter
}
}
return none
}
================================================
FILE: src/jsonrpc/server_test.v
================================================
import io
import jsonrpc
import jsonrpc.server_test_utils { RpcResult, TestClient, TestStream }
struct TestHandler {}
struct SumParams {
mut:
nums []int
}
fn (mut _ TestHandler) handle_jsonrpc(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) ! {
match req.method {
'sum' {
params := req.decode_params[SumParams]()!
mut res := 0
for n in params.nums {
res += n
}
wr.write(RpcResult[int]{ result: res })
}
'mirror' {
texts := req.decode_params[[]string]()!
if texts.len == 0 || texts[0] == '0' {
wr.write(jsonrpc.null)
return
}
wr.write(RpcResult[string]{texts[0]})
}
'hello' {
wr.write(RpcResult[string]{'Hello world!'})
}
'trigger' {
wr.server.dispatch_event('record', 'dispatched!')!
wr.write(RpcResult[string]{'triggered'})
}
else {
return jsonrpc.response_error(error: jsonrpc.method_not_found).err()
}
}
}
fn test_server() {
mut stream := &TestStream{}
mut server := &jsonrpc.Server{
handler: &TestHandler{}
stream: stream
}
mut client := TestClient{
server: server
stream: stream
}
sum_result := client.send[SumParams, RpcResult[int]]('sum', SumParams{ nums: [1, 2, 4] })!
assert sum_result.result == 7
hello_result := client.send[string, RpcResult[string]]('hello', '')!
assert hello_result.result == 'Hello world!'
client.send[string, RpcResult[int]]('multiply', 'test') or {
if err !is io.Eof {
assert err.msg() == 'Method not found.'
}
}
client.send[[]string, RpcResult[string]]('mirror', ['0']) or { assert err is io.Eof }
}
struct TestInterceptor {
mut:
methods_recv []string
messages []string
}
fn (mut t TestInterceptor) on_event(name string, data jsonrpc.InterceptorData) ! {
if name == 'record' && data is string {
t.messages << data
}
}
fn (mut _ TestInterceptor) on_raw_request(req []u8) ! {}
fn (mut t TestInterceptor) on_request(req &jsonrpc.Request) ! {
t.methods_recv << req.method
}
fn (mut t TestInterceptor) on_encoded_response(resp []u8) {
t.messages << 'test!'
}
fn test_interceptor() {
mut test_inter := &TestInterceptor{}
mut stream := &TestStream{}
mut server := &jsonrpc.Server{
handler: &TestHandler{}
interceptors: [test_inter]
stream: stream
}
mut client := TestClient{
server: server
stream: stream
}
client.send[SumParams, RpcResult[int]]('sum', SumParams{ nums: [1, 2, 4] })!
assert test_inter.methods_recv.len == 1
assert test_inter.methods_recv[0] == 'sum'
assert test_inter.messages.len == 1
client.send[string, RpcResult[string]]('trigger', '')!
assert test_inter.methods_recv.len == 2
assert test_inter.methods_recv[1] == 'trigger'
assert test_inter.messages.len == 3
assert test_inter.messages[1] == 'dispatched!'
}
================================================
FILE: src/jsonrpc/server_test_utils/server_test_utils.v
================================================
// Copyright (c) 2022 Ned Palacios. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module server_test_utils
import io
import jsonrpc
import json
import datatypes
// new_test_client creates a test client to be used for observing responses
// and notifications from the given handler and interceptors
pub fn new_test_client(handler jsonrpc.Handler, interceptors ...jsonrpc.Interceptor) &TestClient {
mut stream := &TestStream{}
mut server := &jsonrpc.Server{
handler: handler
interceptors: interceptors
stream: stream
}
return &TestClient{
server: server
stream: stream
}
}
// TestResponse is a version of jsonrpc.Response that decodes
// incoming JSON as raw JSON string.
struct TestResponse {
raw_id string @[json: id; raw]
raw_result string @[json: result; raw]
}
// TestClient is a JSONRPC Client used for simulating communication between client and
// JSONRPC server. This exposes the JSONRPC server and a test stream for sending data
// as a server or as a client
pub struct TestClient {
mut:
id int
pub mut:
server &jsonrpc.Server
stream &TestStream
}
// send sends a request and receives a decoded response result.
pub fn (mut tc TestClient) send[T, U](method string, params T) !U {
params_json := json.encode(params)
req := jsonrpc.Request{
id: '${tc.id}'
method: method
params: params_json
}
tc.stream.send(req)
tc.server.respond() or { return err }
raw_json_content := tc.stream.response_text(req.id)
if raw_json_content.len == 0 || raw_json_content == 'null' {
return IError(io.Eof{})
}
println(raw_json_content)
return json.decode(U, raw_json_content)!
}
// notify is a version of send but instead of returning a response,
// it only notifies the server. Effectively sending a request as a
// notification.
pub fn (mut tc TestClient) notify[T](method string, params T) ! {
params_json := json.encode(params)
req := jsonrpc.Request{
id: ''
method: method
params: params_json
}
tc.stream.send(req)
tc.server.respond()!
}
// TestStream is a io.ReadWriter-compliant stream for sending
// and receiving responses from between the client and the server.
// Aside from being a ReaderWriter, it exposes additional methods
// for decoding JSONRPC response and notifications.
pub struct TestStream {
mut:
notif_idx int
notif_buf [][]u8 = [][]u8{len: 20, cap: 20}
resp_buf map[string]TestResponse
req_buf datatypes.Queue[[]u8]
}
// read receives the incoming request buffer.
pub fn (mut rw TestStream) read(mut buf []u8) !int {
req := rw.req_buf.pop() or { return IError(io.Eof{}) }
buf << req
return req.len
}
// write receives the outgoing response/notification buffer.
pub fn (mut rw TestStream) write(buf []u8) !int {
raw_json_content := buf.bytestr().all_after('\r\n\r\n')
if raw_json_content.contains('"result":') {
resp := json.decode(TestResponse, raw_json_content) or { return err }
rw.resp_buf[resp.raw_id] = resp
} else if raw_json_content.contains('"params":') {
idx := rw.notif_idx % 20
for i := idx + 1; i < rw.notif_buf.len; i++ {
if rw.notif_buf[i].len != 0 {
rw.notif_buf[idx].clear()
}
}
rw.notif_buf[idx] << buf
rw.notif_idx++
} else {
return error('none')
}
return buf.len
}
// send stringifies and dispatches the jsonrpc.Request into the request queue.
pub fn (mut rw TestStream) send(req jsonrpc.Request) {
req_json := req.json()
rw.req_buf.push('Content-Length: ${req_json.len}\r\n\r\n${req_json}'.bytes())
}
// response_text returns the raw response result of the given request id.
pub fn (rw &TestStream) response_text(raw_id string) string {
return rw.resp_buf[raw_id].raw_result
}
// notification_at returns the jsonrpc.Notification in a given index.
pub fn (rw &TestStream) notification_at[T](idx int) !jsonrpc.NotificationMessage[T] {
raw_json_content := rw.notif_buf[idx].bytestr().all_after('\r\n\r\n')
return json.decode(jsonrpc.NotificationMessage[T], raw_json_content)!
}
// last_notification_at_method returns the last jsonrpc.Notification from the given method name.
pub fn (rw &TestStream) last_notification_at_method[T](method_name string) !jsonrpc.NotificationMessage[T] {
for i := rw.notif_buf.len - 1; i >= 0; i-- {
raw_notif_content := rw.notif_buf[i]
if raw_notif_content.len == 0 {
continue
}
if raw_notif_content.bytestr().contains('"method":"${method_name}"') {
return rw.notification_at[T](i) or { return err }
}
}
return error('')
}
// RpcResult is a result form used for primitive types.
pub struct RpcResult[T] {
pub mut:
result T
}
================================================
FILE: src/loglib/ColorMode.v
================================================
module loglib
pub enum ColorMode {
auto
always
never
}
fn get_color_mode_by_name(name string) ?ColorMode {
return match name {
'auto' { ColorMode.auto }
'always' { ColorMode.always }
'never' { ColorMode.never }
else { none }
}
}
================================================
FILE: src/loglib/Entry.v
================================================
module loglib
import time
import os
pub type Fields = map[string]string
pub struct Entry {
pub mut:
logger &Logger
fields Fields
time time.Time
level LogLevel
message string
}
pub fn new_entry(logger &Logger) &Entry {
return &Entry{
logger: logger
}
}
pub fn (entry &Entry) clone() &Entry {
return &Entry{
logger: entry.logger
fields: entry.fields.clone()
time: entry.time
level: entry.level
message: entry.message
}
}
pub fn (entry &Entry) with_fields(fields Fields) &Entry {
mut own_fields := entry.fields.clone()
for k, v in fields {
own_fields[k] = v
}
return &Entry{
logger: entry.logger
fields: own_fields
time: entry.time
level: entry.level
message: entry.message
}
}
pub fn (entry &Entry) with_duration(dur time.Duration) &Entry {
return entry.with_fields({
'duration': dur.str()
})
}
pub fn (entry &Entry) with_gc_heap_usage(usage GCHeapUsage) &Entry {
return entry.with_fields({
'heap_size': usage.heap_size.str()
'free_bytes': usage.free_bytes.str()
'total_bytes': usage.total_bytes.str()
'unmapped_bytes': usage.unmapped_bytes.str()
'bytes_since_gc': usage.bytes_since_gc.str()
})
}
pub fn (entry &Entry) error(msg ...string) {
entry.log(.error, ...msg)
}
pub fn (entry &Entry) warn(msg ...string) {
entry.log(.warn, ...msg)
}
pub fn (entry &Entry) info(msg ...string) {
entry.log(.info, ...msg)
}
pub fn (entry &Entry) trace(msg ...string) {
entry.log(.trace, ...msg)
}
pub fn (entry &Entry) log(level LogLevel, msg ...string) {
if !entry.logger.is_level_enabled(level) {
return
}
entry.log_impl(level, ...msg)
}
pub fn (entry &Entry) log_one(level LogLevel, msg string) {
entry.log(level, msg)
}
pub fn (entry &Entry) log_impl(level LogLevel, msg ...string) {
mut new_entry := entry.clone()
new_entry.time = time.now()
new_entry.level = level
new_entry.message = msg.join(' ')
new_entry.write()
if u64(level) <= u64(LogLevel.panic) {
panic(new_entry)
}
}
fn (mut entry Entry) write() {
formatted := entry.logger.formatter.format(entry) or {
eprintln('failed to format log message: ${err}')
return
}
entry.logger.out.write(formatted) or {
eprintln('failed to write log message: ${err}')
return
}
if time.since(entry.logger.last_flush) > entry.logger.flush_rate {
mut out := entry.logger.out
if mut out is os.File {
out.flush()
}
entry.logger.last_flush = time.now()
}
}
================================================
FILE: src/loglib/Formatter.v
================================================
module loglib
pub interface Formatter {
mut:
format(entry &Entry) ![]u8
}
================================================
FILE: src/loglib/LogLevel.v
================================================
module loglib
pub enum LogLevel {
panic
fatal
error
warn
info
debug
trace
}
fn (l LogLevel) label() string {
return match l {
.panic { 'PANIC' }
.fatal { 'FATAL' }
.error { 'ERROR' }
.warn { 'WARN' }
.info { 'INFO' }
.debug { 'DEBUG' }
.trace { 'TRACE' }
}
}
================================================
FILE: src/loglib/Logger.v
================================================
@[translated]
module loglib
import term
import io
import os
import time
__global logger = Logger{
formatter: TextFormatter{}
out: os.stderr()
}
pub const support_colors = term.can_show_color_on_stderr() && term.can_show_color_on_stdout()
@[heap]
pub struct Logger {
pub mut:
disabled bool
color_mode ColorMode
formatter Formatter
out io.Writer
last_flush time.Time
flush_rate time.Duration = 5 * time.second
level u64 = u64(LogLevel.info)
}
@[inline]
pub fn (mut l Logger) disable() {
l.disabled = true
}
@[inline]
pub fn (mut l Logger) enable() {
l.disabled = false
}
@[inline]
pub fn (mut l Logger) use_color_mode(mode ColorMode) {
l.color_mode = mode
}
@[inline]
pub fn (mut l Logger) use_color_mode_string(mode string) {
enum_value := get_color_mode_by_name(mode) or { .auto }
l.color_mode = enum_value
}
@[inline]
pub fn (l &Logger) level() u64 {
return l.level
}
@[inline]
pub fn (mut l Logger) set_level(level LogLevel) {
l.level = u64(level)
}
@[inline]
pub fn (mut l Logger) set_flush_rate(dur time.Duration) {
l.flush_rate = dur
}
@[inline]
pub fn (mut l Logger) set_output(out io.Writer) {
l.out = out
}
@[inline]
pub fn (mut l Logger) get_output() io.Writer {
return l.out
}
@[inline]
pub fn (l &Logger) is_level_enabled(level LogLevel) bool {
return l.level() >= u64(level)
}
pub fn (l &Logger) log(level LogLevel, msg ...string) {
if !l.is_level_enabled(level) {
return
}
entry := new_entry(l)
entry.log(level, ...msg)
}
@[inline]
pub fn (mut l Logger) with_fields(fields Fields) &Entry {
entry := new_entry(l)
return entry.with_fields(fields)
}
@[inline]
pub fn (mut l Logger) with_duration(dur time.Duration) &Entry {
entry := new_entry(l)
return entry.with_duration(dur)
}
@[inline]
pub fn (mut l Logger) with_gc_heap_usage(usage GCHeapUsage) &Entry {
entry := new_entry(l)
return entry.with_gc_heap_usage(usage)
}
================================================
FILE: src/loglib/TextFormatter.v
================================================
module loglib
import strings
import term
pub struct TextFormatter {
mut:
is_terminal bool
initialized bool
}
fn (mut t TextFormatter) init(entry &Entry) {
t.is_terminal = check_if_terminal(entry.logger.out)
}
fn (mut t TextFormatter) format(entry &Entry) ![]u8 {
if !t.initialized {
t.init(entry)
t.initialized = true
}
mut sb := strings.new_builder(10)
sb.write_string(t.colorize(entry, entry.time.format_ss(), term.gray))
sb.write_string(' ')
t.format_level(entry, mut sb)
sb.write_string(' ')
t.format_message(entry, mut sb)
t.format_fields(entry, mut sb)
sb.write_string('\n')
return sb
}
fn (t &TextFormatter) format_level(entry &Entry, mut sb strings.Builder) {
level := entry.level
level_label := level.label()
colored := t.colorize(entry, '[${level_label}]', t.level_color(level))
sb.write_string(colored)
if level in [.info, .warn] {
sb.write_string(' ')
}
}
fn (t &TextFormatter) format_message(entry &Entry, mut sb strings.Builder) {
mut message := entry.message
if message.len < 35 && entry.fields.len != 0 {
message = message + ' '.repeat(35 - message.len)
}
sb.write_string(message)
}
fn (t &TextFormatter) format_fields(entry &Entry, mut sb strings.Builder) {
fields := entry.fields
if fields.len == 0 {
return
}
level_color := t.level_color(entry.level)
sb.write_string(' ')
mut index := 0
for key, field in fields {
sb.write_string(t.colorize(entry, key, level_color))
sb.write_string('=')
sb.write_string(field)
if index <= fields.len - 1 {
sb.write_string(' ')
}
index++
}
}
fn (t &TextFormatter) colorize(entry &Entry, msg string, fun fn (msg string) string) string {
if !support_colors || entry.logger.color_mode == .never || !t.is_terminal {
return msg
}
return fun(msg)
}
@[inline]
fn (_ &TextFormatter) level_color(level LogLevel) fn (msg string) string {
return match level {
.panic { term.red }
.fatal { term.red }
.error { term.red }
.warn { term.yellow }
.info { term.gray }
.debug { term.gray }
.trace { term.gray }
}
}
================================================
FILE: src/loglib/log.v
================================================
module loglib
import io
import time
@[inline]
pub fn log(level LogLevel, msg ...string) {
logger.log(level, ...msg)
}
@[inline]
pub fn log_one(level LogLevel, msg string) {
logger.log(level, msg)
}
@[inline]
pub fn warn(msg ...string) {
logger.log(.warn, ...msg)
}
@[inline]
pub fn info(msg ...string) {
logger.log(.info, ...msg)
}
@[inline]
pub fn trace(msg ...string) {
logger.log(.trace, ...msg)
}
@[inline]
pub fn error(msg ...string) {
logger.log(.error, ...msg)
}
@[inline]
pub fn set_level(level LogLevel) {
logger.set_level(level)
}
@[inline]
pub fn set_flush_rate(dur time.Duration) {
logger.flush_rate = dur
}
@[inline]
pub fn set_output(out io.Writer) {
logger.set_output(out)
}
@[inline]
pub fn get_output() io.Writer {
return logger.get_output()
}
@[inline]
pub fn with_fields(fields Fields) &Entry {
return logger.with_fields(fields)
}
@[inline]
pub fn with_duration(dur time.Duration) &Entry {
return logger.with_duration(dur)
}
@[inline]
pub fn with_gc_heap_usage(usage GCHeapUsage) &Entry {
return logger.with_gc_heap_usage(usage)
}
================================================
FILE: src/loglib/utils.v
================================================
module loglib
import os
import io
pub fn check_if_terminal(w io.Writer) bool {
if w is os.File {
return is_terminal(w.fd)
}
return false
}
pub fn is_terminal(fd int) bool {
$if windows {
env_conemu := os.getenv('ConEmuANSI')
if env_conemu == 'ON' {
return true
}
// 4 is enable_virtual_terminal_processing
return (os.is_atty(fd) & 0x0004) > 0
}
return os.is_atty(fd) > 0
}
================================================
FILE: src/lsp/README.md
================================================
# Description
`lsp` module describes the types for the Language Server Protocol 3.17 spec.
## Author
2020-2021 Ned Palacios
2023 VOSCA
================================================
FILE: src/lsp/capabilities.v
================================================
module lsp
pub struct WorkspaceClientCapabilities {
pub mut:
apply_edit bool @[json: applyEdit]
workspace_edit WorkspaceEdit @[json: workspaceEdit]
did_change_configuration DidChange @[json: didChangeConfiguration]
did_change_watched_files DidChange @[json: didChangeWatchedFiles]
symbol WorkspaceSymbolCapabilities
execute_command DidChange @[json: executeCommand]
workspace_folders bool @[json: workspaceFolders]
configuration bool
}
pub struct WorkspaceSymbolCapabilities {
pub mut:
dynamic_registration bool @[json: dynamicRegistration]
symbol_kind ValueSet @[json: symbolKind]
}
pub struct ValueSet {
pub mut:
value_set []int @[json: valueSet] // SymbolKind
}
pub struct DidChange {
pub mut:
dynamic_registration bool @[json: dynamicRegistration]
}
pub struct TextDocumentClientCapabilities {
pub mut:
code_lens Capability @[json: codeLens]
color_provider Capability @[json: colorProvider]
completion CompletionCapability
declaration LinkCapability
definition LinkCapability
document_highlight Capability @[json: documentHighlight]
document_link Capability @[json: documentLink]
document_symbol DocumentSymbolCapability @[json: documentSymbol]
folding_range FoldingRangeCapabilities @[json: foldingRange]
formatting Capability
hover HoverCapability
implementation LinkCapability
on_type_formatting Capability @[json: on_type_formatting]
publish_diagnostics PublishDiagnosticCapability @[json: publishDiagnostics]
range_formatting Capability @[json: rangeFormatting]
references Capability
rename RenameCapability
selection_range Capability @[json: selectionRange]
signature_help SignatureHelpCapability
synchronization TextDocumentSyncCapability
type_definition LinkCapability @[json: typeDefinition]
}
pub struct DocumentLinkSupport {
dynamic_registration bool @[json: dynamicRegistration]
tooltip_support bool @[json: tooltip_support]
}
pub struct TextDocumentSyncCapability {
pub mut:
dynamic_registration bool @[json: dynamicRegistration]
will_save bool @[json: willSave]
will_save_wait_until bool @[json: willSaveWaitUntil]
did_save bool @[json: didSave]
}
pub struct CompletionCapability {
pub mut:
dynamic_registration bool @[json: dynamicRegistration]
completion_item CompletionItemSettings @[json: completionItem]
completion_item_kind ValueSet @[json: completionItemKind]
context_support bool @[json: contextSupport]
}
pub struct HoverCapability {
pub mut:
dynamic_registration bool @[json: dynamicRegistration]
content_format []string @[json: contentFormat] // MarkupKind
}
pub struct SignatureHelpCapability {
pub mut:
dynamic_registration bool @[json: dynamicRegistration]
signature_information SignatureInformationCapability @[json: signatureInformation]
}
pub struct SignatureInformationCapability {
pub mut:
document_format []string @[json: documentFormat]
// MarkupKind
parameter_information ParamsInfo @[json: parameterInformation]
}
pub struct ParamsInfo {
pub mut:
label_offset_support bool @[json: labelOffsetSupport]
}
pub struct Capability {
pub mut:
dynamic_registration bool @[json: dynamicRegistration]
}
pub struct DocumentSymbolCapability {
pub mut:
dynamic_registration bool @[json: dynamicRegistration]
symbol_kind ValueSet @[json: symbolKind]
hierarchical_document_symbol_support bool @[json: hierarchicalDocumentSymbolSupport]
}
pub struct LinkCapability {
pub mut:
dynamic_registration bool @[json: dynamicRegistration]
link_support bool @[json: linkSupport]
}
pub struct CodeActionCapability {
pub mut:
dynamic_registration bool @[json: dynamicRegistration]
is_preferred_support bool @[json: isPreferredSupport]
code_action_literal_support CodeActionLiteralSupport @[json: codeActionLiteralSupport]
}
pub struct CodeActionLiteralSupport {
pub mut:
code_action_kind CodeActionKindF @[json: codeActionKind]
}
pub struct CodeActionKindF {
pub mut:
value_set []string @[json: valueSet]
}
pub struct RenameCapability {
pub mut:
dynamic_registration bool @[json: dynamicRegistration]
prepare_support bool @[json: prepareSupport]
}
pub struct PublishDiagnosticCapability {
pub mut:
related_information bool @[json: relatedInformation]
version_support bool @[json: versionSupport]
tag_support ValueSet @[json: tagSupport]
}
pub struct ClientCapabilities {
pub mut:
workspace WorkspaceClientCapabilities @[skip]
text_document TextDocumentClientCapabilities @[json: 'textDocument']
window WindowCapability
experimental string @[raw]
}
pub struct WindowCapability {
pub mut:
work_done_progress bool @[json: 'workDoneProgress']
}
@[json_as_number]
pub enum TextDocumentSyncKind {
none_ = 0
full = 1
incremental = 2
}
pub struct ServerCapabilities {
pub mut:
text_document_sync TextDocumentSyncOptions @[json: textDocumentSync]
hover_provider bool @[json: hoverProvider]
inlay_hint_provider InlayHintOptions @[json: inlayHintProvider]
completion_provider CompletionOptions @[json: completionProvider]
signature_help_provider SignatureHelpOptions @[json: signatureHelpProvider]
definition_provider bool @[json: definitionProvider]
type_definition_provider bool @[json: typeDefinitionProvider]
implementation_provider bool @[json: implementationProvider]
references_provider ReferencesOptions @[json: referencesProvider]
document_highlight_provider bool @[json: documentHighlightProvider]
document_symbol_provider bool @[json: documentSymbolProvider]
workspace_symbol_provider bool @[json: workspaceSymbolProvider]
code_action_provider CodeActionOptions @[json: codeActionProvider]
code_lens_provider CodeLensOptions @[json: codeLensProvider]
document_formatting_provider bool @[json: documentFormattingProvider]
document_on_type_formatting_provider DocumentOnTypeFormattingOptions @[json: documentOnTypeFormattingProvider]
rename_provider RenameOptions @[json: renameProvider]
document_link_provider DocumentLinkOptions @[json: documentLinkProvider]
color_provider bool @[json: colorProvider]
declaration_provider bool @[json: declarationProvider]
execute_command_provider ExecuteCommandOptions @[json: executeCommandProvider]
folding_range_provider bool @[json: foldingRangeProvider]
semantic_tokens_provider SemanticTokensOptions @[json: semanticTokensProvider; omitempty]
experimental map[string]bool
}
pub struct ServerCapabilitiesWorkspace {
pub mut:
workspace_folders WorkspaceFoldersProviderSupport @[json: WorkspaceFoldersProviderSupport]
}
pub struct WorkspaceFoldersProviderSupport {
pub mut:
supported bool
change_notifications string @[json: changeNotifications]
}
================================================
FILE: src/lsp/client.v
================================================
module lsp
// method: ‘client/registerCapability’
// response: void
pub struct RegistrationParams {
registrations []Registration
}
pub struct Registration {
id int
method string
register_options string @[raw]
}
// base interface for registration register_options
// pub struct TextDocumentRegistrationOptions {
// document_selector []DocumentFilter @[json:documentSelector]
// }
// method: ‘client/unregisterCapability’
// response: void
pub struct UnregistrationParams {
unregistrations []Unregistration
}
pub struct Unregistration {
id int
method string
}
================================================
FILE: src/lsp/code_action.v
================================================
module lsp
// The kind of a code action.
//
// Kinds are a hierarchical list of identifiers separated by `.`,
// e.g. `"refactor.extract.function"`.
//
// The set of kinds is open and client needs to announce the kinds it supports
// to the server during initialization.
pub type CodeActionKind = string
pub struct CodeActionParams {
pub:
// The document in which the command was invoked.
text_document TextDocumentIdentifier @[json: 'textDocument']
// The range for which the command was invoked.
range Range
// Context carrying additional information.
context CodeActionContext
}
// A set of predefined code action kinds.
pub enum CodeActionTriggerKind {
// Code actions were explicitly requested by the user or by an extension.
invoked
// Code actions were requested automatically.
//
// This typically happens when current selection in a file changes, but can
// also be triggered when file content changes.
automatic
}
// type CodeActionKind string
pub const empty = ''
pub const quick_fix = 'quickfix'
pub const refactor = 'refactor'
pub const refactor_extract = 'refactor.extract'
pub const refactor_inline = 'refactor.inline'
pub const refactor_rewrite = 'refactor.rewrite'
pub const source = 'source'
pub const source_organize_imports = 'source.organizeImports'
// Contains additional diagnostic information about the context in which
// a code action is run.
pub struct CodeActionContext {
pub:
// An array of diagnostics known on the client side overlapping the range
// provided to the `textDocument/codeAction` request. They are provided so
// that the server knows which errors are currently presented to the user
// for the given range. There is no guarantee that these accurately reflect
// the error state of the resource. The primary parameter
// to compute code actions is the provided range.
diagnostics []Diagnostic
// Requested kind of actions to return.
//
// Actions not of this kind are filtered out by the client before being
// shown. So servers can omit computing them.
only []CodeActionKind
// The reason why code actions were requested
trigger_kind CodeActionTriggerKind @[json: 'triggerKind']
}
pub struct CodeAction {
pub:
// A short, human-readable, title for this code action.
title string
// The kind of the code action.
// Used to filter code actions.
kind CodeActionKind
// The diagnostics that this code action resolves.
diagnostics []Diagnostic
// Marks this as a preferred action. Preferred actions are used by the
// `auto fix` command and can be targeted by keybindings.
//
// A quick fix should be marked preferred if it properly addresses the
// underlying error. A refactoring should be marked preferred if it is the
// most reasonable choice of actions to take.
//
// @since 3.15.0
is_preferred bool @[json: 'isPreferred']
// The workspace edit this code action performs.
edit WorkspaceEdit
// A command this code action executes. If a code action
// provides an edit and a command, first the edit is
// executed and then the command.
command Command
// A data entry field that is preserved on a code action between
// a `textDocument/codeAction` and a `codeAction/resolve` request.
//
// @since 3.16.0
data string @[raw]
}
pub struct CodeActionOptions {
pub:
// CodeActionKinds that this server may return.
//
// The list of kinds may be generic, such as `CodeActionKind.Refactor`,
// or the server may list out every specific kind they provide.
code_action_kinds []string @[json: 'codeActionKinds']
}
================================================
FILE: src/lsp/code_lens.v
================================================
module lsp
pub struct CodeLensOptions {
pub mut:
resolve_provider bool @[json: 'resolveProvider'; omitempty]
work_done_progress bool @[json: 'workDoneProgress'; omitempty]
}
// method: ‘textDocument/codeLens’
// response: []CodeLens | none
pub struct CodeLensParams {
WorkDoneProgressParams
pub:
text_document TextDocumentIdentifier @[json: textDocument]
}
pub struct CodeLens {
pub:
// The range in which this code lens is valid. Should only span a single
// line.
range Range
// The command this code lens represents.
command Command
// A data entry field that is preserved on a code lens item between
// a code lens and a code lens resolve request.
data string @[raw]
}
pub struct CodeLensRegistrationOptions {
document_selector []DocumentFilter @[json: documentSelector]
resolve_provider bool @[json: resolveProvider]
}
// method: ‘codeLens/resolve’
// response: CodeLens
// request: CodeLens
================================================
FILE: src/lsp/color_presentation.v
================================================
module lsp
// /**
// * Color provider options.
// */
// export interface ColorProviderOptions {
// }
// method: ‘textDocument/colorPresentation’
// response: []ColorPresentation
pub struct ColorPresentationParams {
text_document TextDocumentIdentifier @[json: textDocument]
color Color
range Range
}
pub struct ColorPresentation {
label string
text_edit TextEdit @[json: textEdit]
additional_text_edits []TextEdit @[json: additionalTextEdits]
}
================================================
FILE: src/lsp/completion.v
================================================
module lsp
pub struct CompletionOptions {
pub mut:
resolve_provider bool @[json: resolveProvider]
trigger_characters []string @[json: triggerCharacters]
}
pub struct CompletionItemSettings {
snippet_support bool @[json: snippetSupport]
commit_characters_support bool @[json: commitCharactersSupport]
preselect_support bool @[json: preselectSupport]
deprecated_support bool @[json: deprecatedSupport]
tag_support ValueSet @[json: tag_support]
}
// method: ‘textDocument/completion’
// response: []CompletionItem | CompletionList | none
pub struct CompletionParams {
pub:
// extend: TextDocumentPositionParams
text_document TextDocumentIdentifier @[json: textDocument]
position Position
context CompletionContext
}
@[json_as_number]
pub enum CompletionTriggerKind {
invoked = 1
trigger_character = 2
trigger_for_incomplete_completions = 3
}
pub struct CompletionContext {
pub:
trigger_kind CompletionTriggerKind @[json: triggerKind]
trigger_character string @[json: triggerCharacter]
}
pub struct CompletionList {
pub:
is_incomplete bool @[json: isIncomplete]
items []CompletionItem
}
@[json_as_number]
pub enum InsertTextFormat {
plain_text = 1
snippet = 2
}
pub struct CompletionItemLabelDetails {
pub:
// An optional string which is rendered less prominently directly after
// {@link CompletionItem.label label}, without any spacing. Should be
// used for function signatures or type annotations.
detail string @[omitempty]
// An optional string which is rendered less prominently after
// {@link CompletionItemLabelDetails.detail}. Should be used for fully qualified
// names or file path.
description string
}
pub struct CompletionItem {
pub mut:
// The label of this completion item.
//
// The label property is also by default the text that
// is inserted when selecting this completion.
//
// If label details are provided the label itself should
// be an unqualified name of the completion item.
label string
// Additional details for the label
label_details CompletionItemLabelDetails @[json: 'labelDetails'; omitempty]
// The kind of this completion item. Based of the kind
// an icon is chosen by the editor. The standardized set
// of available values is defined in `CompletionItemKind`.
kind CompletionItemKind
// A human-readable string with additional information
// about this item, like type or symbol information.
detail string
// A human-readable string that represents a doc-comment.
documentation string
// A string that should be used when filtering a set of
// completion items. When `falsy` the label is used.
filter_text string @[json: 'filterText'; omitempty]
// A string that should be inserted into a document when selecting
// this completion. When omitted the label is used as the insert text
// for this item.
//
// The `insertText` is subject to interpretation by the client side.
// Some tools might not take the string literally. For example
// VS Code when code complete is requested in this example
// `con` and a completion item with an `insertText` of
// `console` is provided it will only insert `sole`. Therefore it is
// recommended to use `textEdit` instead since it avoids additional client
// side interpretation.
insert_text string @[json: 'insertText'; omitempty]
// The format of the insert text. The format applies to both the
// `insertText` property and the `newText` property of a provided
// `textEdit`. If omitted defaults to `InsertTextFormat.PlainText`.
//
// Please note that the insertTextFormat doesn't apply to
// `additionalTextEdits`.
insert_text_format InsertTextFormat = .plain_text @[json: 'insertTextFormat']
// How whitespace and indentation is handled during completion
// item insertion. If not provided the client's default value depends on
// the `textDocument.completion.insertTextMode` client capability.
//
// @since 3.16.0
// @since 3.17.0 - support for `textDocument.completion.insertTextMode`
insert_text_mode InsertTextMode @[json: 'insertTextMode'; omitempty]
// A string that should be used when comparing this item
// with other items. When omitted the label is used
// as the sort text for this item.
sort_text string @[json: 'sortText']
// text_edit TextEdit @[json:textEdit]
// additional_text_edits []TextEdit @[json:additionalTextEdits]
// commit_characters []string @[json:commitCharacters]
// command Command
// data string @[raw]
}
@[json_as_number]
pub enum InsertTextMode {
as_is = 1
adjust_indentation = 2
}
@[json_as_number]
pub enum CompletionItemKind {
text = 1
method = 2
function = 3
constructor = 4
field = 5
variable = 6
class = 7
interface_ = 8
module_ = 9
property = 10
unit = 11
value = 12
enum_ = 13
keyword = 14
snippet = 15
color = 16
file = 17
reference = 18
folder = 19
enum_member = 20
constant = 21
struct_ = 22
event = 23
operator = 24
type_parameter = 25
}
pub struct CompletionRegistrationOptions {
document_selector []DocumentFilter @[json: documentSelector]
trigger_characters []string @[json: triggerCharacters]
all_commit_characters []string @[json: allCommitCharacters]
resolve_provider bool @[json: resolveProvider]
}
// method: ‘completionItem/resolve’
// response: CompletionItem
// request: CompletionItem
================================================
FILE: src/lsp/declaration.v
================================================
module lsp
// method: ‘textDocument/declaration’
// response: Location | []Location | []LocationLink | none
// request: TextDocumentPositionParams
================================================
FILE: src/lsp/definition.v
================================================
module lsp
// method: ‘textDocument/definition’
// response: Location | []Location | []LocationLink | none
// request: TextDocumentPositionParams
// method: ‘textDocument/typeDefinition’
// response: Location | []Location | []LocationLink | none
// request: TextDocumentPositionParams
================================================
FILE: src/lsp/diagnostics.v
================================================
module lsp
@[json_as_number]
pub enum DiagnosticTag {
// Unused or unnecessary code.
//
// Clients are allowed to render diagnostics with this tag faded out
// instead of having an error squiggle.
unnecessary = 1
// Deprecated or obsolete code.
//
// Clients are allowed to rendered diagnostics with this tag strike through.
deprecated
}
pub struct CodeDescription {
pub mut:
href string = 'https://github.com/vlang/v/blob/master/doc/docs.md'
}
pub struct Diagnostic {
pub mut:
// The range at which the message applies.
range Range
// The diagnostic's severity. Can be omitted. If omitted it is up to the
// client to interpret diagnostics as error, warning, info or hint.
severity DiagnosticSeverity
// The diagnostic's code, which might appear in the user interface.
code string
// An optional property to describe the error code.
code_description CodeDescription @[json: 'codeDescription']
// A human-readable string describing the source of this
// diagnostic, e.g. 'typescript' or 'super lint'.
source string
// The diagnostic's message.
message string
// Additional metadata about the diagnostic.
tags []DiagnosticTag @[json: 'tags']
// An array of related diagnostic information, e.g. when symbol-names within
// a scope collide all definitions can be marked via this property.
related_information []DiagnosticRelatedInformation @[json: 'relatedInformation']
// A data entry field that is preserved between a
// `textDocument/publishDiagnostics` notification and
// `textDocument/codeAction` request.
//
// @since 3.16.0
data string @[raw]
}
@[json_as_number]
pub enum DiagnosticSeverity {
error = 1
warning
information
hint
}
// Represents a related message and source code location for a diagnostic.
// This should be used to point to code locations that cause or are related to
// a diagnostics, e.g when duplicating a symbol in a scope.
pub struct DiagnosticRelatedInformation {
// The location of this related diagnostic information.
location Location
// The message of this related diagnostic information.
message string
}
// method: ‘textDocument/publishDiagnostics’
pub struct PublishDiagnosticsParams {
pub:
uri DocumentUri
diagnostics []Diagnostic
}
================================================
FILE: src/lsp/document_color.v
================================================
module lsp
// method: ‘textDocument/documentColor’
// response: []ColorInformation
pub struct DocumentColorParams {
text_document TextDocumentIdentifier @[json: textDocument]
}
pub struct ColorInformation {
range Range
color Color
}
pub struct Color {
red int
green int
blue int
alpha int
}
================================================
FILE: src/lsp/document_highlight.v
================================================
module lsp
// method: ‘textDocument/documentHighlight’
// response: []DocumentHighlight | none
// request: TextDocumentPositionParams
pub struct DocumentHighlight {
pub:
range Range
kind DocumentHighlightKind
}
@[json_as_number]
pub enum DocumentHighlightKind {
text = 1
read = 2
write = 3
}
================================================
FILE: src/lsp/document_link.v
================================================
module lsp
pub struct DocumentLinkOptions {
resolve_provider bool @[json: resolveProvider]
}
// method: ‘textDocument/documentLink’
// response: []DocumentLink | none
pub struct DocumentLinkParams {
text_document TextDocumentIdentifier @[json: textDocument]
}
pub struct DocumentLink {
range Range
target DocumentUri
data string @[raw]
}
pub struct DocumentLinkRegistrationOptions {
document_selector []DocumentFilter @[json: documentSelector]
resolve_provider bool @[json: resolveProvider]
}
// method: ‘documentLink/resolve’
// response: DocumentLink
// request: DocumentLink
================================================
FILE: src/lsp/document_symbol.v
================================================
module lsp
// method: ‘textDocument/documentSymbol’
// response: []DocumentSymbol | []SymbolInformation | none
pub struct DocumentSymbolParams {
pub:
text_document TextDocumentIdentifier @[json: textDocument]
}
@[json_as_number]
pub enum SymbolKind {
file = 1
module_ = 2
namespace = 3
package = 4
class = 5
method = 6
property = 7
field = 8
constructor = 9
enum_ = 10
interface_ = 11
function = 12
variable = 13
constant = 14
string = 15
number = 16
boolean = 17
array = 18
object = 19
key = 20
null = 21
enum_member = 22
struct_ = 23
event = 24
operator = 25
type_parameter = 26
}
pub struct DocumentSymbol {
pub mut:
// The name of this symbol. Will be displayed in the user interface and
// therefore must not be an empty string or a string only consisting of
// white spaces.
name string
// More detail for this symbol, e.g the signature of a function.
detail string @[omitempty]
kind SymbolKind
deprecated bool @[omitempty]
// The range enclosing this symbol not including leading/trailing whitespace
// but everything else like comments. This information is typically used to
// determine if the clients cursor is inside the symbol to reveal in the
// symbol in the UI.
range Range
// The range that should be selected and revealed when this symbol is being
// picked, e.g. the name of a function. Must be contained by the `range`.
selection_range Range @[json: 'selectionRange'; omitempty]
// Children of this symbol, e.g. properties of a class.
children []DocumentSymbol @[omitempty]
}
================================================
FILE: src/lsp/ext.v
================================================
module lsp
pub struct ServerStatusParams {
pub:
health string // "ok" | "warning" | "error";
quiescent bool
message string @[omitempty]
}
================================================
FILE: src/lsp/file_resource.v
================================================
module lsp
pub struct CreateFileOptions {
overwrite bool
ignore_if_exists bool @[json: ignoreIfExists]
}
pub struct CreateFile {
kind string = 'create'
uri DocumentUri
options CreateFileOptions
}
pub struct RenameFileOptions {
overwrite bool
ignore_if_exists bool @[json: ignoreIfExists]
}
pub struct RenameFile {
kind string = 'rename'
old_uri DocumentUri @[json: oldUri]
new_uri DocumentUri @[json: newUri]
options RenameFileOptions
}
pub struct DeleteFileOptions {
recursive bool
ignore_if_exists bool @[json: ignoreIfExists]
}
pub struct DeleteFile {
kind string = 'delete'
uri DocumentUri
options DeleteFileOptions
}
================================================
FILE: src/lsp/folding_range.v
================================================
module lsp
type FoldingRangeKind = string
pub struct FoldingRangeKindCapabilities {
pub:
// The folding range kind values the client supports. When this
// property exists the client also guarantees that it will
// handle values outside its set gracefully and falls back
// to a default value when unknown.
value_set []FoldingRangeKind @[json: 'valueSet'; omitempty]
}
pub struct FoldingRangeValuesCapabilities {
pub:
// If set, the client signals that it supports setting collapsedText on
// folding ranges to display custom labels instead of the default text.
// @since 3.17.0
collapsed_text bool @[json: 'collapsedText'; omitempty]
}
pub struct FoldingRangeCapabilities {
pub:
// The maximum number of folding ranges that the client prefers to receive
// per document. The value serves as a hint, servers are free to follow the
// limit.
range_limit u32 @[json: 'rangeLimit'; omitempty]
// If set, the client signals that it only supports folding complete lines.
// If set, client will ignore specified `startCharacter` and `endCharacter`
// properties in a FoldingRange.
line_folding_only bool @[json: 'lineFoldingOnly'; omitempty]
// Specific options for the folding range kind.
folding_range_kind FoldingRangeKindCapabilities @[json: 'foldingRangeKind'; omitempty]
// Specific options for the folding range.
folding_range FoldingRangeValuesCapabilities @[json: 'foldingRange'; omitempty]
}
pub struct FoldingRangeParams {
pub:
text_document TextDocumentIdentifier @[json: 'textDocument']
}
// Folding range for a comment
pub const folding_range_kind_comment = 'comment'
// Folding range for imports or includes
pub const folding_range_kind_imports = 'imports'
// Folding range for a region (e.g. `#region`)
pub const folding_range_kind_region = 'region'
// Represents a folding range. To be valid, start and end line must be bigger
// than zero and smaller than the number of lines in the document. Clients
// are free to ignore invalid ranges.
pub struct FoldingRange {
pub:
// The zero-based start line of the range to fold. The folded area starts
// after the line's last character. To be valid, the end must be zero or
// larger and smaller than the number of lines in the document.
start_line int @[json: 'startLine']
// The zero-based character offset from where the folded range starts. If
// not defined, defaults to the length of the start line.
start_character int @[json: 'startCharacter'; omitempty]
// The zero-based end line of the range to fold. The folded area ends with
// the line's last character. To be valid, the end must be zero or larger
// and smaller than the number of lines in the document.
end_line int @[json: 'endLine']
// The zero-based character offset before the folded range ends. If not
// defined, defaults to the length of the end line.
end_character int @[json: 'endCharacter'; omitempty]
// Describes the kind of the folding range such as `comment` or `region`.
// The kind is used to categorize folding ranges and used by commands like
// 'Fold all comments'. See [FoldingRangeKind](#FoldingRangeKind) for an
// enumeration of standardized kinds.
kind string
// The text that the client should show when the specified range is
// collapsed. If not defined or not supported by the client, a default
// will be chosen by the client.
//
// @since 3.17.0 - proposed
collapsed_text string @[json: 'collapsedText'; omitempty]
}
================================================
FILE: src/lsp/formatting.v
================================================
module lsp
// method: ‘textDocument/formatting’
// response: []TextEdit | none
pub struct DocumentFormattingParams {
pub:
text_document TextDocumentIdentifier @[json: textDocument]
options FormattingOptions
}
pub struct FormattingOptions {
tab_size int @[json: tabSize]
insert_spaces bool @[json: insertSpaces]
// [key] bool | number | string
}
// method: ‘textDocument/rangeFormatting’
// response: []TextEdit | none
pub struct DocumentRangeFormattingParams {
text_document TextDocumentIdentifier @[json: textDocument]
range Range
options FormattingOptions
}
pub struct DocumentOnTypeFormattingOptions {
first_trigger_character string @[json: firstTriggerCharacter]
more_trigger_character []string @[json: moreTriggerCharacter]
}
// method: ‘textDocument/onTypeFormatting’
// response: []TextEdit | none
pub struct DocumentOnTypeFormattingParams {
text_document TextDocumentIdentifier @[json: textDocument]
position Position
ch string
options FormattingOptions
}
pub struct DocumentOnTypeFormattingRegistrationOptions {
document_selector []DocumentFilter @[json: documentSelector]
first_trigger_character string @[json: firstTriggerCharacter]
more_trigger_character []string @[json: moreTriggerCharacter]
}
================================================
FILE: src/lsp/hover.v
================================================
module lsp
pub struct HoverSettings {
dynamic_registration bool @[json: dynamicRegistration]
content_format []string @[json: contentFormat]
}
// method: ‘textDocument/hover’
// response: Hover | none
// request: TextDocumentPositionParams
pub struct HoverParams {
pub:
text_document TextDocumentIdentifier @[json: textDocument]
position Position
}
type HoverResponseContent = MarkedString | MarkupContent | []MarkedString | string
pub struct Hover {
pub:
contents HoverResponseContent
range Range
}
// pub type MarkedString = string | MarkedString
pub struct MarkedString {
language string
value string
}
pub fn hover_v_marked_string(text string) HoverResponseContent {
return HoverResponseContent(MarkedString{
language: 'v'
value: text
})
}
pub fn hover_markdown_string(text string) HoverResponseContent {
return HoverResponseContent(MarkupContent{
kind: markup_kind_markdown
value: text
})
}
================================================
FILE: src/lsp/implementation.v
================================================
module lsp
// method: ‘textDocument/implementation’
// response: Location | []Location | []LocationLink | none
// request: TextDocumentPositionParams
================================================
FILE: src/lsp/initialize.v
================================================
module lsp
// TODO: These LSPAny need to change to `?LSPAny` too.
type LSPAny = []LSPAny | map[string]LSPAny | f64 | bool | string
// method: ‘initialize’
// response: InitializeResult
pub struct InitializeParams {
pub mut:
process_id int = -2 @[json: processId]
client_info ClientInfo @[json: clientInfo]
root_uri DocumentUri @[json: rootUri]
root_path DocumentUri @[json: rootPath]
// TODO: Change this to `?LSPAny` once V fixed its JSON decoder codegen. (or shall we use json2?)
initialization_options LSPAny @[json: initializationOptions]
capabilities ClientCapabilities
trace string
workspace_folders []WorkspaceFolder @[skip]
}
pub struct ClientInfo {
pub mut:
name string @[json: name]
version string @[json: version]
}
pub struct ServerInfo {
pub mut:
name string
version string
}
pub struct InitializeResult {
pub:
capabilities ServerCapabilities
server_info ServerInfo @[json: 'serverInfo'; omitempty]
}
// method: ‘initialized’
// notification
// pub struct InitializedParams {}
@[json_as_number]
pub enum InitializeErrorCode {
unknown_protocol_version = 1
}
pub struct InitializeError {
retry bool
}
/*
*
* The kind of resource operations supported by the client.
*/
@[json_as_number]
pub enum ResourceOperationKind {
create
rename
delete
}
@[json_as_number]
pub enum FailureHandlingKind {
abort
transactional
undo
text_only_transactional
}
pub struct ExecuteCommandOptions {
pub:
// The commands to be executed on the server
commands []string
}
pub struct StaticRegistrationOptions {
id string
}
// method: ‘shutdown’
// response: none
// method: ‘exit’
// response: none
================================================
FILE: src/lsp/inlay_hint.v
================================================
module lsp
pub type InlineHintLabel = []InlayHintLabelPart | string
pub type InlineHintTooltip = MarkupContent | string
pub struct InlayHint {
pub:
// The position of this hint.
position Position
// The label of this hint. A human readable string or an array of
// InlayHintLabelPart label parts.
//
// *Note* that neither the string nor the label part can be empty.
label InlineHintLabel
// The kind of this hint. Can be omitted in which case the client
// should fall back to a reasonable default.
kind InlayHintKind
// Optional text edits that are performed when accepting this inlay hint.
//
// *Note* that edits are expected to change the document so that the inlay
// hint (or its nearest variant) is now part of the document and the inlay
// hint itself is now obsolete.
text_edits []TextEdit @[json: 'textEdits'; omitempty]
// The tooltip text when you hover over this item.
tooltip InlineHintTooltip @[json: 'tooltip'; omitempty]
// Render padding before the hint.
//
// Note: Padding should use the editor's background color, not the
// background color of the hint itself. That means padding can be used
// to visually align/separate an inlay hint.
padding_left bool @[json: 'paddingLeft'; omitempty]
// Render padding after the hint.
//
// Note: Padding should use the editor's background color, not the
// background color of the hint itself. That means padding can be used
// to visually align/separate an inlay hint.
padding_right bool @[json: 'paddingRight'; omitempty]
// A data entry field that is preserved on an inlay hint between
// a `textDocument/inlayHint` and a `inlayHint/resolve` request.
data string @[raw]
}
pub struct InlayHintClientCapabilities {
pub:
// Whether inlay hints support dynamic registration.
dynamic_registration bool @[json: 'dynamicRegistration']
// Indicates which properties a client can resolve lazily on an inlay
// hint.
resolve_support bool @[json: 'resolveSupport']
}
@[json_as_number]
pub enum InlayHintKind {
type_
parameter
}
pub struct InlayHintLabelPart {
pub:
// The value of this label part.
value string
// The tooltip text when you hover over this label part. Depending on
// the client capability `inlayHint.resolveSupport` clients might resolve
// this property late using the resolve request.
tooltip MarkupContent @[omitempty]
// An optional source code location that represents this
// label part.
//
// The editor will use this location for the hover and for code navigation
// features: This part will become a clickable link that resolves to the
// definition of the symbol at the given location (not necessarily the
// location itself), it shows the hover that shows at the given location,
// and it shows a context menu with further code navigation commands.
//
// Depending on the client capability `inlayHint.resolveSupport` clients
// might resolve this property late using the resolve request.
location Location @[omitempty]
// An optional command for this label part.
//
// Depending on the client capability `inlayHint.resolveSupport` clients
// might resolve this property late using the resolve request.
command Command @[omitempty]
}
// A parameter literal used in inlay hint requests.
//
// @since 3.17.0
pub struct InlayHintOptions {
pub:
resolve_provider bool @[json: 'resolveProvider']
}
// A parameter literal used in inlay hint requests.
//
// @since 3.17.0
pub struct InlayHintParams {
pub:
// The text document.
text_document TextDocumentIdentifier @[json: 'textDocument']
// The document range for which inlay hints should be computed.
range Range
}
================================================
FILE: src/lsp/log/log.v
================================================
module log
import os
import time
import io
import jsonrpc
import strings
pub struct LogRecorder {
mut:
file os.File
buffer strings.Builder
file_opened bool
enabled bool
pub mut:
file_path string
}
@[json_as_number]
pub enum TransportKind {
send
receive
}
struct Payload {
id int
method string
result string @[raw]
params string @[raw]
}
@[json_as_number]
pub enum LogKind {
send_notification
recv_notification
recv_request
send_response
}
pub fn (lk LogKind) str() string {
return match lk {
.send_notification { 'send-notification' }
.recv_notification { 'recv-notification' }
.recv_request { 'recv-request' }
.send_response { 'send-response' }
}
}
pub struct LogItem {
kind LogKind
timestamp time.Time = time.now()
payload []u8 // raw JSON
}
// json is a JSON string representation of the log item.
pub fn (li LogItem) encode_json(mut wr io.Writer) ! {
wr.write('{"kind":"${li.kind}","timestamp":${li.timestamp.unix()},"payload":'.bytes())!
wr.write(li.payload)!
wr.write('}\n'.bytes())!
}
pub fn new() &LogRecorder {
return &LogRecorder{
file_opened: false
enabled: true
buffer: strings.new_builder(4096)
}
}
pub fn (l &LogRecorder) is_enabled() bool {
return l.enabled
}
// set_logpath sets the filepath of the log file and opens the file.
pub fn (mut l LogRecorder) set_logpath(path string) ! {
if l.file_opened {
l.close()
}
l.file = os.open_append(os.real_path(path))!
l.file_path = path
l.file_opened = true
l.enabled = true
}
// flush flushes the contents of the log file into the disk.
pub fn (mut l LogRecorder) flush() {
l.file.flush()
}
// close closes the log file.
pub fn (mut l LogRecorder) close() {
if !l.file_opened {
return
}
l.file_opened = false
l.file.close()
}
// enable enables/starts the logging.
pub fn (mut l LogRecorder) enable() {
l.enabled = true
}
// disable disables/stops the logging.
pub fn (mut l LogRecorder) disable() {
l.enabled = false
}
// write writes the log item into the log file or in the
// buffer if the file is not opened yet.
@[manualfree]
fn (mut l LogRecorder) log(item LogItem) {
if !l.enabled {
return
} else if l.file_opened {
if l.buffer.len != 0 {
unsafe {
l.file.write_ptr(l.buffer.data, l.buffer.len)
l.buffer.go_back_to(0)
}
}
item.encode_json(mut l.file) or { eprintln(err) }
l.flush()
} else {
item.encode_json(mut l.buffer) or { eprintln(err) }
}
}
// as a JSON-RPC interceptor
const event_prefix = '$/lspLogger'
pub const set_logpath_event = '${event_prefix}/setPath'
pub const close_event = '${event_prefix}/close'
pub const state_event = '${event_prefix}/state'
pub fn (mut l LogRecorder) on_event(name string, data jsonrpc.InterceptorData) ! {
if name == set_logpath_event && data is string {
l.set_logpath(data)!
} else if name == close_event {
l.close()
} else if name == state_event && data is bool {
if data {
l.enable()
} else {
l.disable()
}
}
}
pub fn (_ &LogRecorder) on_raw_request(req []u8) ! {}
pub fn (_ &LogRecorder) on_raw_response(raw_resp []u8) ! {}
pub fn (mut l LogRecorder) on_request(req &jsonrpc.Request) ! {
log_kind := if req.id.len == 0 {
LogKind.recv_notification
} else {
LogKind.recv_request
}
l.log(kind: log_kind, payload: req.json().bytes())
}
pub fn (mut l LogRecorder) on_encoded_response(resp []u8) {
if 15 < resp.len && 23 < resp.len && resp[15..23].bytestr() == ',"method"' {
l.log(kind: .send_response, payload: resp)
} else {
l.log(kind: .send_notification, payload: resp)
}
}
================================================
FILE: src/lsp/log/log_test.v
================================================
module log
import json
struct TestLogItem {
kind string
payload string
}
fn test_notification_send() {
mut lg := new()
lg.log(kind: .send_notification, payload: '"Hello!"'.bytes())
buf := lg.buffer.str()
result := json.decode(TestLogItem, buf)!
assert result.kind == 'send-notification'
assert result.payload == 'Hello!'
}
fn test_notification_receive() {
mut lg := new()
lg.log(kind: .recv_notification, payload: '"Received!"'.bytes())
buf := lg.buffer.str()
result := json.decode(TestLogItem, buf)!
assert result.kind == 'recv-notification'
assert result.payload == 'Received!'
}
fn test_request_send() {
mut lg := new()
lg.log(kind: .recv_request, payload: '"Request sent."'.bytes())
buf := lg.buffer.str()
result := json.decode(TestLogItem, buf)!
assert result.kind == 'recv-request'
assert result.payload == 'Request sent.'
}
fn test_request_receive() {
mut lg := new()
lg.log(kind: .recv_request, payload: '"Request received."'.bytes())
buf := lg.buffer.str()
result := json.decode(TestLogItem, buf)!
assert result.kind == 'recv-request'
assert result.payload == 'Request received.'
}
fn test_response_send() {
mut lg := new()
lg.log(kind: .send_response, payload: '"Response sent."'.bytes())
buf := lg.buffer.str()
result := json.decode(TestLogItem, buf)!
assert result.kind == 'send-response'
assert result.payload == 'Response sent.'
}
fn test_response_receive() {
mut lg := new()
lg.log(kind: .send_response, payload: '"Response received."'.bytes())
buf := lg.buffer.str()
result := json.decode(TestLogItem, buf)!
assert result.kind == 'send-response'
assert result.payload == 'Response received.'
}
================================================
FILE: src/lsp/lsp.v
================================================
module lsp
import os
pub type DocumentUri = string
pub fn (du DocumentUri) dir() string {
return os.dir(du)
}
pub fn (du DocumentUri) path() string {
scheme := 'file://'
if !du.starts_with(scheme) {
return ''
}
mut authority := du.all_after(scheme).all_before('/')
mut path := du.all_after(scheme).all_after('/')
authority = unescape(authority)
path = unescape(path)
mut result := ''
$if windows {
if authority != '' && path != '' {
result = '//${authority}/${path}'
} else if path[0].is_letter() && path[1] == `:` {
// convert driver name to upper case
drive_name := if path[0].is_capital() {
path[0]
} else {
path[0] - 32
}
result = rune(drive_name).str() + path[1..]
} else {
result = path
}
result = result.replace('/', '\\')
} $else {
result = '/' + path
}
return result
}
pub fn (du DocumentUri) dir_path() string {
return os.dir(du.path())
}
pub fn (du DocumentUri) normalize() DocumentUri {
return document_uri_from_path(du.path())
}
fn escape(s string) string {
byte_array := s.bytes()
return byte_array.map(if it.is_alnum() || it in [`-`, `.`, `_`, `~`, `/`] {
rune(it).str()
} else {
'%${it:02X}'
})
.join('')
}
fn unescape(s string) string {
rune_array := s.runes()
mut results := []u8{}
for i := 0; i < rune_array.len; i++ {
if rune_array[i] != `%` {
results << rune_array[i].bytes()
} else {
v1_rune := rune_array[i + 1] or { `\0` }
v2_rune := rune_array[i + 2] or { `\0` }
v1 := try_into_hex_int(v1_rune) or {
results << rune_array[i].bytes()
continue
}
v2 := try_into_hex_int(v2_rune) or {
results << rune_array[i].bytes()
continue
}
v := (v1 << 4) + v2
results << v
i += 2
}
}
return results.bytestr()
}
fn try_into_hex_int(r rune) ?rune {
return if r >= `0` && r <= `9` {
r - `0`
} else if r >= `A` && r <= `F` {
r - `A` + 10
} else if r >= `a` && r <= `f` {
r - `a` + 10
} else {
none
}
}
pub fn document_uri_from_path(path string) DocumentUri {
scheme := 'file://'
is_has_scheme := path.starts_with(scheme)
mut fixed_path := if is_has_scheme {
path.all_after(scheme)
} else {
path
}
mut authority := ''
$if windows {
fixed_path = fixed_path.replace('\\', '/')
// UNC paths for accessing network resources
if !is_has_scheme && fixed_path.starts_with('//') {
authority = fixed_path.find_between('//', '/')
fixed_path = fixed_path.all_after('//').all_after('/')
if fixed_path == '' {
fixed_path = '/'
}
}
}
mut is_need_prepend_slash := false
$if windows {
// paths start with '/' without specifying drive name are paths
// relative to root of current drive.
// an extra '/' needs to be prepended.
is_need_prepend_slash = !fixed_path.starts_with('/')
|| (fixed_path[1] != `/` && fixed_path[2] != `:`)
} $else {
is_need_prepend_slash = !fixed_path.starts_with('/')
}
if is_need_prepend_slash {
fixed_path = '/' + fixed_path
}
$if windows {
// convert driver name to lower case, e.g. /C:/foo -> /c:/foo
if fixed_path[2] == `:` && fixed_path[1].is_letter() {
driver_name := if fixed_path[1].is_capital() {
fixed_path[1] + 32
} else {
fixed_path[1]
}
fixed_path = '/${rune(driver_name).str()}${fixed_path[2..]}'
}
}
uri := scheme + escape(authority) + escape(fixed_path)
return uri
}
pub struct NotificationMessage {
method string
params string @[raw]
}
// // method: $/cancelRequest
pub struct CancelParams {
id int
}
pub struct Command {
pub:
title string
command string
arguments []string
}
pub struct DocumentFilter {
language string
scheme string
pattern string
}
pub struct TextDocumentRegistrationOptions {
document_selector []DocumentFilter @[json: documentSelector]
}
================================================
FILE: src/lsp/lsp_test.v
================================================
module lsp
fn test_document_uri_from_path() {
input := '/foo/bar/test.v'
uri := document_uri_from_path(input)
$if windows {
assert uri == 'file:////foo/bar/test.v'
} $else {
assert uri == 'file:///foo/bar/test.v'
}
assert document_uri_from_path(uri) == uri
}
fn test_document_uri_from_path_windows() {
$if !windows {
return
}
input := [
'C:\\coding\\test.v',
'file:///C:/my/files',
r'\\server\share\foo',
]
expected := [
'file:///c%3A/coding/test.v',
'file:///c%3A/my/files',
'file://server/share/foo',
]
for i in 0 .. input.len {
assert document_uri_from_path(input[i]) == expected[i]
}
}
fn test_document_uri_unicode() {
input := [
'/usr/home/你好世界',
r'C:/files/C%3A%5Cfiles',
]
mut expected := []string{}
$if windows {
expected << 'file:////usr/home/%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C'
expected << 'file:///c%3A/files/C%253A%255Cfiles'
} $else {
expected << 'file:///usr/home/%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C'
expected << 'file:///C%3A/files/C%253A%255Cfiles'
}
for i in 0 .. input.len {
assert document_uri_from_path(input[i]) == expected[i]
}
}
fn test_document_uri_path() {
input := [
'file:///baz/foo/hello.v',
'file:///C%3A/upper_case/files',
'file:///c%3A/lower_case/files',
'file://server/share/foo',
'file:///usr/home/%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C',
]
mut expected := []string{}
$if windows {
expected << r'baz\foo\hello.v'
expected << r'C:\upper_case\files'
expected << r'C:\lower_case\files'
expected << r'\\server\share\foo'
expected << r'usr\home\你好世界'
} $else {
expected << '/baz/foo/hello.v'
expected << '/C:/upper_case/files'
expected << '/c:/lower_case/files'
expected << '/share/foo'
expected << '/usr/home/你好世界'
}
for i in 0 .. input.len {
assert DocumentUri(input[i]).path() == expected[i]
}
}
================================================
FILE: src/lsp/progress.v
================================================
module lsp
import rand
import crypto.md5
pub type ProgressToken = string
pub fn (t ProgressToken) empty() bool {
return t == ''
}
pub fn generate_progress_token() ProgressToken {
value := rand.intn(1000000) or { 0 }
return md5.hexhash(value.str())
}
pub struct WorkDoneProgressParams {
pub:
work_done_token ProgressToken @[json: 'workDoneToken'; omitempty]
}
pub struct PartialResultParams {
pub:
partial_result_token ProgressToken @[json: 'partialResultToken'; omitempty]
}
pub struct WorkDoneProgressCreateParams {
pub:
token ProgressToken
}
pub struct ProgressParams {
pub:
token ProgressToken
value WorkDoneProgressPayload
}
pub struct WorkDoneProgressPayload {
pub:
// begin / report / end
kind string
// Mandatory title of the progress operation. Used to briefly inform about
// the kind of operation being performed.
//
// Examples: "Indexing" or "Linking dependencies".
title string @[omitempty]
// Controls if a cancel button should show to allow the user to cancel the
// long running operation. Clients that don't support cancellation are
// allowed to ignore the setting.
cancellable bool @[omitempty]
// Optional, more detailed associated progress message. Contains
// complementary information to the `title`.
//
// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep".
// If unset, the previous progress message (if any) is still valid.
message string @[omitempty]
// Optional progress percentage to display (value 100 is considered 100%).
// If not provided infinite progress is assumed and clients are allowed
// to ignore the `percentage` value in subsequent in report notifications.
//
// The value should be steadily rising. Clients are free to ignore values
// that are not following this rule. The value range is [0, 100]
percentage u32
}
pub struct WorkDoneProgressBegin {
WorkDoneProgressPayload
pub:
kind string = 'begin'
}
pub struct WorkDoneProgressReport {
WorkDoneProgressPayload
pub:
kind string = 'report'
}
pub struct WorkDoneProgressEnd {
WorkDoneProgressPayload
kind string = 'end'
}
================================================
FILE: src/lsp/references.v
================================================
module lsp
pub struct ReferencesOptions {
pub:
work_done_progress bool @[json: 'workDoneProgress']
}
// method: ‘textDocument/references’
// response: []Location | none
pub struct ReferenceParams {
pub:
text_document TextDocumentIdentifier @[json: textDocument]
position Position
context ReferenceContext
}
pub struct ReferenceContext {
include_declaration bool
}
================================================
FILE: src/lsp/rename.v
================================================
module lsp
pub struct RenameOptions {
pub:
prepare_provider bool @[json: prepareProvider]
}
// method: ‘textDocument/rename’
// response: WorkspaceEdit | none
pub struct RenameParams {
pub:
text_document TextDocumentIdentifier @[json: textDocument]
position Position
new_name string @[json: newName]
}
pub struct RenameRegistrationOptions {
pub:
document_selector []DocumentFilter @[json: documentSelector]
prepare_provider bool @[json: prepareProvider]
}
// method: ‘textDocument/prepareRename’
// response: Range | { range: Range, placeholder: string } | none
// request: TextDocumentPositionParams
pub struct PrepareRenameParams {
pub:
text_document TextDocumentIdentifier @[json: textDocument]
position Position
}
pub struct PrepareRenameResult {
pub:
range Range
placeholder string
}
================================================
FILE: src/lsp/semantic_tokens.v
================================================
module lsp
pub struct SemanticTokens {
pub:
// An optional result id. If provided and clients support delta updating
// the client will include the result id in the next semantic token request.
// A server can then instead of computing all semantic tokens again simply
// send a delta.
result_id string @[json: 'resultID']
// The actual tokens.
data []u32
}
pub struct SemanticTokensOptions {
pub:
// The legend used by the server
legend SemanticTokensLegend
// Server supports providing semantic tokens for a specific range
// of a document.
range bool @[omitempty]
// Server supports providing semantic tokens for a full document.
full bool @[omitempty]
}
pub struct SemanticTokensLegend {
pub:
// The token types a server uses.
token_types []string @[json: 'tokenTypes']
// The token modifiers a server uses.
token_modifiers []string @[json: 'tokenModifiers']
}
pub struct SemanticTokensParams {
pub:
// The text document.
text_document TextDocumentIdentifier @[json: 'textDocument']
}
pub struct SemanticTokensRangeParams {
pub:
// The text document.
text_document TextDocumentIdentifier @[json: 'textDocument']
// The range the semantic tokens are requested for.
range Range @[omitempty]
}
================================================
FILE: src/lsp/signature_help.v
================================================
module lsp
pub struct SignatureHelpOptions {
pub:
trigger_characters []string @[json: triggerCharacters]
retrigger_characters []string @[json: retriggerCharacters]
}
@[json_as_number]
pub enum SignatureHelpTriggerKind {
invoked = 1
trigger_character = 2
content_change = 3
}
// method: ‘textDocument/signatureHelp’
// response: SignatureHelp | none
pub struct SignatureHelpParams {
pub:
// TODO: utilize struct embedding feature
// for all structs that use TextDocumentPositionParams
// embed: TextDocumentPositionParams
text_document TextDocumentIdentifier @[json: textDocument]
position Position
context SignatureHelpContext
}
pub struct SignatureHelpContext {
pub:
trigger_kind SignatureHelpTriggerKind @[json: triggerKind]
trigger_character string @[json: triggerCharacter]
is_retrigger bool @[json: isRetrigger]
active_signature_help SignatureHelp @[json: activeSignatureHelp]
}
pub struct SignatureHelp {
pub:
signatures []SignatureInformation
pub mut:
active_parameter int @[json: activeParameter]
}
pub struct SignatureInformation {
pub mut:
label string
// documentation MarkupContent
parameters []ParameterInformation
}
pub struct ParameterInformation {
pub:
label string
}
pub struct SignatureHelpRegistrationOptions {
document_selector []DocumentFilter @[json: documentSelector]
trigger_characters []string @[json: triggerCharacters]
}
================================================
FILE: src/lsp/symbol.v
================================================
module lsp
// method: ‘textDocument/signatureHelp’
// response: SignatureHelp | none
// request: TextDocumentPositionParams
// struct SymbolInformation {
// }
================================================
FILE: src/lsp/text_document.v
================================================
module lsp
pub struct Position {
pub:
line int
character int
}
pub struct Range {
pub:
start Position
end Position
}
pub fn (r Range) is_empty() bool {
return r.start.line == 0 && r.end.line == 0 && r.start.character == 0 && r.end.character == 0
}
pub struct TextEdit {
pub:
range Range
new_text string @[json: 'newText']
}
pub struct TextDocumentIdentifier {
pub:
uri DocumentUri
}
pub struct TextDocumentEdit {
text_document VersionedTextDocumentIdentifier @[json: textDocument]
edits []TextEdit
}
pub struct TextDocumentItem {
pub:
uri DocumentUri
language_id string @[json: languageId]
version int
text string
}
pub struct VersionedTextDocumentIdentifier {
pub:
uri DocumentUri
version int
}
pub struct Location {
pub mut:
uri DocumentUri
range Range
}
pub struct LocationLink {
pub:
// Span of the origin of this link.
//
// Used as the underlined span for mouse interaction. Defaults to the word
// range at the mouse position.
origin_selection_range Range @[json: 'originSelectionRange']
// The target resource identifier of this link.
target_uri DocumentUri @[json: 'targetUri']
// The full target range of this link. If the target for example is a symbol
// then target range is the range enclosing this symbol not including
// leading/trailing whitespace but everything else like comments. This
// information is typically used to highlight the range in the editor.
target_range Range @[json: 'targetRange']
// The range that should be selected and revealed when this link is being
// followed, e.g the name of a function. Must be contained by the
// `targetRange`. See also `DocumentSymbol#range`
target_selection_range Range @[json: 'targetSelectionRange']
}
// pub struct TextDocumentContentChangeEvent {
// range Range
// text string
// }
pub struct TextDocumentPositionParams {
pub:
text_document TextDocumentIdentifier @[json: textDocument]
position Position
}
pub const markup_kind_plaintext = 'plaintext'
pub const markup_kind_markdown = 'markdown'
pub struct MarkupContent {
pub:
kind string
// MarkupKind
value string
}
pub struct TextDocument {
uri DocumentUri
language_id string
version int
line_count int
}
pub struct FullTextDocument {
uri DocumentUri
language_id string
version int
content string
line_offsets []int
}
================================================
FILE: src/lsp/text_sync.v
================================================
module lsp
pub struct TextDocumentSyncOptions {
pub:
// Open and close notifications are sent to the server. If omitted open
// close notifications should not be sent.
open_close bool @[json: 'openClose']
// Change notifications are sent to the server. See
// TextDocumentSyncKind.None, TextDocumentSyncKind.Full and
// TextDocumentSyncKind.Incremental. If omitted it defaults to
// TextDocumentSyncKind.None.
change TextDocumentSyncKind = TextDocumentSyncKind.full @[omitempty]
// If present will save notifications are sent to the server. If omitted
// the notification should not be sent.
will_save bool @[json: 'willSave']
// If present save notifications are sent to the server. If omitted the
// notification should not be sent.
save SaveOptions
}
pub struct SaveOptions {
include_text bool @[json: 'includeText']
}
// method: ‘textDocument/didOpen’
// notification
pub struct DidOpenTextDocumentParams {
pub:
text_document TextDocumentItem @[json: textDocument]
}
// method: ‘textDocument/didChange’
// notification
pub struct DidChangeTextDocumentParams {
pub:
// The document that did change. The version number points
// to the version after all provided content changes have
// been applied.
text_document VersionedTextDocumentIdentifier @[json: textDocument]
// The actual content changes. The content changes describe single state
// changes to the document. So if there are two content changes c1 (at
// array index 0) and c2 (at array index 1) for a document in state S then
// c1 moves the document from S to S' and c2 from S' to S''. So c1 is
// computed on the state S and c2 is computed on the state S'.
//
// To mirror the content of a document using change events use the following
// approach:
// - start with the same initial content
// - apply the 'textDocument/didChange' notifications in the order you
// receive them.
// - apply the `TextDocumentContentChangeEvent`s in a single notification
// in the order you receive them.
content_changes []TextDocumentContentChangeEvent @[json: contentChanges]
}
pub struct TextDocumentContentChangeEvent {
pub:
// The range of the document that changed.
range Range
// The optional length of the range that got replaced.
range_length int @[deprecated: 'use range instead'; json: 'rangeLength']
// The new text for the provided range or the entire document.
text string
}
pub struct TextDocumentChangeRegistrationOptions {
document_selector []DocumentFilter @[json: documentSelector]
sync_kind int @[json: syncKind]
}
// method: ‘textDocument/willSave’
// notification
pub struct WillSaveTextDocumentParams {
text_document TextDocumentIdentifier @[json: textDocument]
reason TextDocumentSaveReason
}
@[json_as_number]
pub enum TextDocumentSaveReason {
manual = 1
after_delay = 2
focus_out = 3
}
// ‘textDocument/willSaveWaitUntil’
// response: []TextEdit | null
// request: WillSaveTextDocumentParams
// method: ‘textDocument/didSave’
// notification
pub struct DidSaveTextDocumentParams {
pub:
text_document TextDocumentIdentifier @[json: textDocument]
text string
}
// method: ‘textDocument/didClose’
// notification
pub struct DidCloseTextDocumentParams {
pub:
text_document TextDocumentIdentifier @[json: textDocument]
}
================================================
FILE: src/lsp/window.v
================================================
module lsp
// method: ‘window/showMessage’
// notification
pub struct ShowMessageParams {
pub:
@type MessageType
// @type int
message string
}
@[json_as_number]
pub enum MessageType {
error = 1
warning = 2
info = 3
log = 4
}
// method: ‘window/showMessageRequest’
// response: MessageActionItem | none / null
pub struct ShowMessageRequestParams {
pub:
@type MessageType
message string
actions []MessageActionItem
}
pub struct MessageActionItem {
title string
}
// method: ‘window/logMessage’
// notification
pub struct LogMessageParams {
pub:
@type MessageType
message string
}
// method: ‘telemetry/event
// notification
// any
================================================
FILE: src/lsp/workspace.v
================================================
module lsp
pub struct WorkspaceFolder {
uri DocumentUri
name string
}
pub type ChangeAnnotationIdentifier = string
// A workspace edit represents changes to many resources managed in the workspace. The edit
// should either provide `changes` or `documentChanges`. If documentChanges are present
// they are preferred over `changes` if the client can handle versioned document edits.
//
// Since version 3.13.0 a workspace edit can contain resource operations as well. If resource
// operations are present clients need to execute the operations in the order in which they
// are provided. So a workspace edit for example can consist of the following two changes:
// (1) a create file a.txt and (2) a text document edit which insert text into file a.txt.
//
// An invalid sequence (e.g. (1) delete file a.txt and (2) insert text into file a.txt) will
// cause failure of the operation. How the client recovers from the failure is described by
// the client capability: `workspace.workspaceEdit.failureHandling`
pub struct WorkspaceEdit {
pub:
// Holds changes to existing resources.
changes map[string][]TextEdit @[json: 'changes'; omitempty]
// Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes
// are either an array of `TextDocumentEdit`s to express changes to n different text documents
// where each text document edit addresses a specific version of a text document. Or it can contain
// above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations.
//
// Whether a client supports versioned document edits is expressed via
// `workspace.workspaceEdit.documentChanges` client capability.
//
// If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then
// only plain `TextEdit`s using the `changes` property are supported.
document_changes []TextDocumentEdit @[json: 'documentChanges'; omitempty]
// A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and
// delete file / folder operations.
//
// Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`.
//
// @since 3.16.0
change_annotations map[string]ChangeAnnotation @[json: 'changeAnnotations'; omitempty]
}
pub struct ChangeAnnotation { // line 6831
// A human-readable string describing the actual change. The string
// is rendered prominent in the user interface.
label string @[omitempty]
// A flag which indicates that user confirmation is needed
// before applying the change.
needs_confirmation bool @[json: 'needsConfirmation'; omitempty]
// A human-readable string which is rendered less prominent in
// the user interface.
description string @[omitempty]
}
pub struct WorkspaceSymbol {
pub mut:
// The name of this symbol. Will be displayed in the user interface and
// therefore must not be an empty string or a string only consisting of
// white spaces.
name string
// The kind of this symbol.
kind SymbolKind
// The name of the symbol containing this symbol. This information is for
// user interface purposes (e.g. to render a qualifier in the user interface
// if necessary). It can't be used to re-infer a hierarchy for the document
// symbols.
container_name string @[json: 'containerName'; omitempty]
// The location of this symbol. Whether a server is allowed to
// return a location without a range depends on the client
// capability `workspace.symbol.resolveSupport`.
//
// See also `SymbolInformation.location`.
location Location @[omitempty]
// A data entry field that is preserved on a workspace symbol between a
// workspace symbol request and a workspace symbol resolve request.
data string @[raw]
}
pub struct DidChangeWorkspaceFoldersParams {
event WorkspaceFoldersChangeEvent
}
pub struct WorkspaceFoldersChangeEvent {
added []WorkspaceFolder
removed []WorkspaceFolder
}
// method: ‘workspace/didChangeConfiguration’,
// notification
pub struct DidChangeConfigurationParams {
settings string @[raw]
}
// method: ‘workspace/configuration’
// response: []any / []string
pub struct ConfigurationParams {
items []ConfigurationItem
}
pub struct ConfigurationItem {
scope_uri DocumentUri @[json: scopeUri]
section string
}
// method: ‘workspace/didChangeWatchedFiles’
// notification
pub struct DidChangeWatchedFilesParams {
pub:
changes []FileEvent
}
pub struct FileEvent {
pub:
uri DocumentUri
typ FileChangeType @[json: 'type']
}
@[json_as_number]
pub enum FileChangeType {
created = 1
changed = 2
deleted = 3
}
pub struct DidChangeWatchedFilesRegistrationOptions {
watchers []FileSystemWatcher
}
// The glob pattern to watch.
// Glob patterns can have the following syntax:
// - `*` to match one or more characters in a path segment
// - `?` to match on one character in a path segment
// - `**` to match any number of path segments, including none
// - `{}` to group conditions (e.g. `**/*.{ts,js}` matches all TypeScript and JavaScript files)
// - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
// - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
pub struct FileSystemWatcher {
glob_pattern string @[json: globPattern]
kind int
}
@[json_as_number]
pub enum WatchKind {
create = 1
change = 2
delete = 3
}
// method: ‘workspace/symbol’
// response: []SymbolInformation | null
pub struct WorkspaceSymbolParams {
query string
}
// method: ‘workspace/executeCommand’
// response: any | null
pub struct ExecuteCommandParams {
pub:
// The identifier of the actual command handler.
command string
// Arguments that the command should be invoked with.
arguments string @[raw]
}
pub struct ExecuteCommandRegistrationOptions {
command []string
}
// method: ‘workspace/applyEdit’
// response: ApplyWorkspaceEditResponse
//
pub struct ApplyWorkspaceEditParams {
pub:
// An optional label of the workspace edit. This label is
// presented in the user interface for example on an undo
// stack to undo the workspace edit.
label string @[omitempty]
// The edits to apply.
edit WorkspaceEdit
}
pub struct ApplyWorkspaceEditResponse {
applied bool
failure_reason string @[json: failureReason]
}
================================================
FILE: src/main.v
================================================
module main
import os
import cli
import time
import term
import config
import loglib
import server
import jsonrpc
import streams
import analyzer
import lsp.log
import metadata
// default_tcp_port is default TCP port that the analyzer uses to connect to the socket
// when the --socket flag is passed at startup.
// See also the `--port` flag to specify a custom port.
const default_tcp_port = 5007
fn run(cmd cli.Command) ! {
stdio := cmd.flags.get_bool('stdio') or { true }
socket := cmd.flags.get_bool('socket') or { false }
port := cmd.flags.get_int('port') or { default_tcp_port }
use_stdout_for_logs := cmd.flags.get_bool('log-to-stdout') or { false }
if !socket && use_stdout_for_logs {
errorln('Cannot use ${term.bold('--log-to-stdout')} flag without ${term.bold('--socket')} flag')
return
}
mut stream := if socket {
streams.new_socket_stream_server(port, true) or {
errorln('Cannot use ${port} port for socket communication, try specify another port with --port')
return
}
} else if stdio {
streams.new_stdio_stream()
} else {
errorln('Either --stdio or --socket flag must be specified')
return
}
setup_logger(!use_stdout_for_logs)
mut ls := server.LanguageServer.new(analyzer.IndexingManager.new())
mut jrpc_server := &jsonrpc.Server{
stream: stream
handler: ls
}
mut lr := log.LogRecorder{}
lr.enable()
jrpc_server.interceptors = [&lr]
defer {
mut out := loglib.get_output()
if mut out is os.File {
out.close()
}
}
jrpc_server.start()
}
fn setup_logger(to_file bool) {
if to_file {
if !os.exists(config.analyzer_logs_path) {
os.mkdir_all(config.analyzer_logs_path) or {
errorln('Failed to create analyzer logs directory: ${err}')
return
}
}
config_path := os.join_path(config.analyzer_logs_path, config.analyzer_log_file_name)
if mut file := os.open_file(config_path, 'a') {
loglib.set_output(file)
}
}
loglib.set_level(.trace)
loglib.set_flush_rate(1 * time.second)
}
fn main() {
mut cmd := cli.Command{
name: metadata.manifest.name
version: metadata.full_version
description: metadata.manifest.description
execute: run
posix_mode: true
}
cmd.add_command(cli.Command{
name: 'init'
description: 'Initialize a configuration file inside the current directory.'
execute: init_cmd
})
cmd.add_command(cli.Command{
name: 'clear-cache'
description: 'Clears the analyzer cache.'
execute: clear_cache_cmd
})
cmd.add_command(cli.Command{
name: 'up'
description: 'Updates the analyzer to the latest version.'
execute: up_cmd
posix_mode: true
flags: [
cli.Flag{
flag: .bool
name: 'nightly'
description: 'Install the latest nightly build'
},
]
})
cmd.add_command(cli.Command{
name: 'check-updates'
description: 'Checks for v-analyzer updates.'
execute: check_updates_cmd
posix_mode: true
version: metadata.full_version
})
cmd.add_flags([
cli.Flag{
flag: .bool
name: 'stdio'
description: 'Use stdio for communication.'
default_value: [
'true',
]
},
cli.Flag{
flag: .bool
name: 'socket'
description: 'Use TCP connection for communication.'
},
cli.Flag{
flag: .bool
name: 'log-to-stdout'
description: 'Use stdout for logs, can be used only with --socket flag (Only for debug purposes).'
},
cli.Flag{
flag: .int
name: 'port'
description: 'Port to use for socket communication. (Default: 5007)'
default_value: [
'${default_tcp_port}',
]
},
])
cmd.parse(os.args)
}
================================================
FILE: src/metadata/metadata.v
================================================
module metadata
import os
import v.vmod
import v.embed_file
pub const manifest = vmod.decode(@VMOD_FILE) or { panic(err) }
pub const build_datetime = $env('BUILD_DATETIME')
pub const build_commit = $env('BUILD_COMMIT')
pub const full_version = manifest.version + '.' + build_commit
struct EmbedFS {
pub mut:
files []embed_file.EmbedFileData
}
pub fn (e &EmbedFS) unpack_to(path string) ! {
for file in e.files {
new_path := os.norm_path(os.join_path(path, file.path))
dir := os.dir(new_path)
if !os.exists(dir) {
os.mkdir_all(dir) or { return error('failed to create directory ${dir}') }
}
os.write_file(new_path, file.to_string())!
}
}
pub fn embed_fs() EmbedFS {
mut files := []embed_file.EmbedFileData{}
files << $embed_file('stubs/arrays.v', .zlib)
files << $embed_file('stubs/primitives.v', .zlib)
files << $embed_file('stubs/vweb.v', .zlib)
files << $embed_file('stubs/compile_time_constants.v', .zlib)
files << $embed_file('stubs/compile_time_reflection.v', .zlib)
files << $embed_file('stubs/builtin_compile_time.v', .zlib)
files << $embed_file('stubs/channels.v', .zlib)
files << $embed_file('stubs/attributes/Deprecated.v', .zlib)
files << $embed_file('stubs/attributes/Table.v', .zlib)
files << $embed_file('stubs/attributes/Attribute.v', .zlib)
files << $embed_file('stubs/attributes/DeprecatedAfter.v', .zlib)
files << $embed_file('stubs/attributes/Unsafe.v', .zlib)
files << $embed_file('stubs/attributes/Flag.v', .zlib)
files << $embed_file('stubs/attributes/Noreturn.v', .zlib)
files << $embed_file('stubs/attributes/Manualfree.v', .zlib)
files << $embed_file('stubs/implicit.v', .zlib)
files << $embed_file('stubs/compile_time.v', .zlib)
files << $embed_file('stubs/c_decl.v', .zlib)
files << $embed_file('stubs/errors.v', .zlib)
files << $embed_file('stubs/threads.v', .zlib)
return EmbedFS{
files: files
}
}
================================================
FILE: src/metadata/stubs/README.md
================================================
## Description:
The `stubs` module contains files describing some features of the V language that are not explicitly
described in the standard library.
For example, some compile-time functions or attributes.
> **Note**
> This is not real code, it is only needed for Doki and IDE to be able to show documentation and
> jump to definitions.
================================================
FILE: src/metadata/stubs/arrays.v
================================================
module stubs
// element_type is the type of the elements in the array.
type element_type = any
// ArrayInit describes an array initializer.
// Example:
// ```
// arr := []int{}
// arr_with_len := []int{len: 1} // [0]
// arr_with_cap := []int{len: 1, cap: 100} // [0]
// arr_with_len_init := []int{len: 1, init: 1} [1]
// arr_with_init := []int{len: 2, cap: 100, init: index * 2} [0, 2]
// ```
//
// Array initializer can contain three **optional** fields:
// 1. `len` – length – number of pre-allocated and initialized elements in the array
// 2. `cap` – capacity – amount of memory space which has been reserved for elements,
// but not initialized or counted as elements
// 3. `init` – default initializer for each element
//
// All three fields can be used independently of the others.
//
// # cap field
//
// If `cap` is not specified, it is set to `len`. `cap` cannot be smaller than `len`.
// `cap` can be used for improving performance of array operations, since no reallocation will be needed.
//
// ```
// arr := []int{len: 2}
// arr << 100 // there will be a reallocation that will slow down the program a bit
//
// arr_with_cap := []int{len: 2, cap: 10}
// arr_with_cap << 100 // no reallocation
// ```
//
// # init field
//
// If `init` is not specified, it is set to `0` for numerical type, `''` for string, etc.
//
// ```
// arr := []int{len: 2}
// assert arr == [0, 0]
// ```
//
// In `init` field, you can use special `index` variable to refer to the current index.
//
// ```
// arr := []int{len: 3, init: index * 2}
// assert arr == [0, 2, 4]
// ```
pub struct ArrayInit {
// index represent the current element index that is being initialized inside `init`.
//
// **Example**
// ```
// arr := []int{len: 3, init: index * 2}
// assert arr == [0, 2, 4]
// ```
index int
pub:
// len field represent number of pre-allocated and initialized elements in the array.
// By default it is set to `0` for numerical type, `''` for string, etc.
//
// **Example**
// ```
// arr := []int{len: 3}
// assert arr.len == 3
// assert arr[0] == 0
// assert arr[1] == 0
// assert arr[2] == 0
// ```
len int
// cap field represent amount of memory space which has been reserved for elements,
// but not initialized or counted as elements
//
// If `cap` is not specified, it is set to `len`. `cap` cannot be smaller than `len`.
// `cap` can be used for improving performance of array operations, since no reallocation will be needed.
//
// **Example**
// ```
// arr := []int{len: 2}
// arr << 100 // there will be a reallocation that will slow down the program a bit
// arr_with_cap := []int{len: 2, cap: 10}
// arr_with_cap << 100 // no reallocation
// ```
cap int
// init field represent default initializer for each element.
//
// In `init` field, you can use special `index` variable to refer to the current index.
//
// **Example**
// ```
// arr := []int{len: 3, init: index * 2}
// assert arr == [0, 2, 4]
// ```
//
// If `init` is not specified, it is set to `0` for numerical type, `''` for string, etc.
//
// **Example**
// ```
// arr := []int{len: 2}
// assert arr == [0, 0]
// ```
init element_type
}
================================================
FILE: src/metadata/stubs/attributes/Attribute.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// Note: this is not an official attribute syntax, V does not provide
// any official way to define attributes. This is used only for
// documentation purposes.
// Target describes the possible places where the attribute is allowed.
enum Target {
function
field
struct_
enum_
constant
type_alias
}
// Attribute is base interface that describes the
// information and behavior of any attribute.
interface Attribute {
name string // name of the attribute
with_arg bool // whether the attribute has an argument
arg_is_optional bool // if with_arg is true, this field is used to indicate whether the argument is optional
target []Target // places where the attribute is allowed
}
================================================
FILE: src/metadata/stubs/attributes/Deprecated.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// Deprecated attribute mark declaration as deprecated.
// Only *direct* accesses to element in *other modules*, will produce deprecation notices/warnings.
// Optionally, a message can be provided. Format of the message is as follows:
//
// ```
// use instead
// use instead:
// ```
//
// See also [deprecated_after](#DeprecatedAfter) attribute.
//
// Example:
// ```
// [deprecated: 'use foo() instead']
// fn boo() {}
// ```
// ```
// [deprecated: 'use foo() instead: boo() has some issues']
// fn boo() {}
// ```
@[attribute]
pub struct Deprecated {
name string = 'deprecated'
with_arg bool = true
arg_is_optional bool = true
target []Target = [Target.struct_, Target.function, Target.field, Target.constant,
Target.type_alias]
}
================================================
FILE: src/metadata/stubs/attributes/DeprecatedAfter.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// DeprecatedAfter attribute specifies a date, after which the element will be
// considered deprecated.
//
// Before that date, calls to the function (for example) will be compiler
// notices – you will see them, but the compilation is not affected.
//
// After that date, calls will become warnings, so ordinary compiling will still
// work, but compiling with `-prod` will not (all warnings are treated like errors with `-prod`).
//
// 6 months after the deprecation date, calls will be hard
// compiler errors.
//
// Note: Must be used with `deprecated` attribute!
//
// See also [deprecated](#Deprecated) attribute.
//
// Example:
// ```
// [deprecated: 'use `foo` instead']
// [deprecated_after: '2023-05-27']
// fn boo() {}
// ```
@[attribute]
pub struct DeprecatedAfter {
name string = 'deprecated_after'
with_arg bool = true
arg_is_optional bool
target []Target = [Target.struct_, Target.function, Target.field, Target.constant,
Target.type_alias]
}
================================================
FILE: src/metadata/stubs/attributes/Flag.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// Flag attribute mark enum as bitfield.
//
// Example:
// ```
// [flag]
// enum Permissions {
// read // = 0b0001
// write // = 0b0010
// other // = 0b0100
// }
// ```
//
// Here, each subsequent element will increase the value of the previous one
// by shifting to the left.
//
// For an enum with a flag attribute, the special methods `has()` and `all()` can be used.
//
// ```
// [flag]
// enum Permissions {
// read // = 0b0001
// write // = 0b0010
// other // = 0b0100
// }
//
// fn main() {
// p := Permissions.read
// assert p.has(.read | .other) // test if *at least one* of the flags is set
//
// p1 := Permissions.read | .write
// assert p1.has(.write)
// assert p1.all(.read | .write) // test if *all* of the flags is set
// }
// ```
@[attribute]
pub struct Flag {
name string = 'flag'
with_arg bool
arg_is_optional bool
target []Target = [Target.enum_]
}
================================================
FILE: src/metadata/stubs/attributes/Heap.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// Heap attribute mark struct as always heap-allocated,
// so any struct creation will happen on the heap.
@[attribute]
pub struct Heap {
name string = 'heap'
with_arg bool
arg_is_optional bool
target []Target = [Target.struct_]
}
================================================
FILE: src/metadata/stubs/attributes/Json.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// Json attribute specifies a custom field name when marshaled to JSON.
// This is useful when you need to use a field name that is not allowed in V.
// For example, you need to specify a PascalCase name for a field, V does not
// allow such a name for a field, in which case you can use the Json attribute.
//
// Example:
// ```v
// struct User {
// first_name string [json: 'FirstName']
// }
@[attribute]
pub struct Json {
name string = 'json'
with_arg bool = true
arg_is_optional bool
target []Target = [Target.field]
}
================================================
FILE: src/metadata/stubs/attributes/JsonAsNumber.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// JsonAsNumber marks an enum.
// The fields of such enum will be encoded in JSON as numbers, not as strings
// with the field name.
//
// Example:
//
// ```
// [json_as_number]
// enum Color {
// red = 1
// green = 2
// }
//
// struct MyStruct {
// color Color = .green
// }
//
// // JSON representation of MyStruct:
// // {
// // "color": 2
// // }
//
// // JSON representation of MyStruct without the attribute:
// // {
// // "color": "green"
// // }
// ```
@[attribute]
pub struct JsonAsNumber {
name string = 'json_as_number'
with_arg bool
arg_is_optional bool
target []Target = [Target.enum_]
}
================================================
FILE: src/metadata/stubs/attributes/Manualfree.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// Manualfree attribute marks a function and the autofree engine
// will not automatically clear the memory allocated in that function.
//
// You will need to free any memory allocated in this function yourself.
@[attribute]
pub struct Manualfree {
name string = 'manualfree'
with_arg bool
arg_is_optional bool
target []Target = [Target.function]
}
================================================
FILE: src/metadata/stubs/attributes/Noinit.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// Noinit attribute mark struct.
// Such structs cannot be created in other modules through initialization (`Foo{}`).
// Instead, they must be initialized via a call to the constructor-like function, if any.
//
// This attribute is useful when you need to make sure that the structure is always created
// correctly and that all required fields are set.
@[attribute]
pub struct Noinit {
name string = 'noinit'
with_arg bool
arg_is_optional bool
target []Target = [Target.struct_]
}
================================================
FILE: src/metadata/stubs/attributes/Noreturn.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// Noreturn attribute marks a function as not return to its caller.
//
// Such functions can be used at the end of or blocks, just like
// [`exit`](#exit) or [`panic`](#panic).
//
// Such functions can not have return types, and should end either in `for {}`, or
// by calling other `[noreturn]` functions.
//
// Example:
//
// ```
// [noreturn]
// fn redirect() {
// // do something
// exit(1)
// }
//
// fn main() {
// if condition {
// redirect();
// // unreachable
// }
// }
// ```
@[attribute]
pub struct Noreturn {
name string = 'noreturn'
with_arg bool
arg_is_optional bool
target []Target = [Target.function]
}
================================================
FILE: src/metadata/stubs/attributes/Omitempty.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// Omitempty attribute marks field as omitempty.
// When field is omitempty, it will be omitted when marshaling to JSON if its value is empty.
@[attribute]
pub struct Omitempty {
name string = 'omitempty'
with_arg bool
arg_is_optional bool
target []Target = [Target.field]
}
================================================
FILE: src/metadata/stubs/attributes/Table.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// Table attribute sets a custom table name (case-sensitive).
// By default ORM uses default struct name.
@[attribute]
pub struct Table {
name string = 'table'
with_arg bool = true
arg_is_optional bool
target []Target = [Target.struct_]
}
================================================
FILE: src/metadata/stubs/attributes/Unsafe.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module attributes
// Unsafe attribute mark the function as unsafe, so
// function can be called only from unsafe code.
//
// Example:
// ```
// [unsafe]
// fn foo() {}
//
// fn main() {
// foo() // warning: function `foo` must be called from an `unsafe` block
//
// unsafe {
// foo() // ok
// }
// }
// ```
@[attribute]
pub struct Unsafe {
name string = 'unsafe'
with_arg bool
arg_is_optional bool
target []Target = [Target.function]
}
================================================
FILE: src/metadata/stubs/builtin_compile_time.v
================================================
module stubs
type placeholder = any
// TypeInfo describe type information returned by the [typeof](#$typeof) builtin function.
pub struct TypeInfo {
pub mut:
idx int // index of the type in the type table
name string // name of the type
}
// $offsetof returns the offset of the field with passed name in the passed struct.
//
// Example:
// ```
// struct Foo {
// a int
// b string
// }
//
// assert __offsetof(Foo, b) == 8
// ```
pub fn $offsetof(struct_type placeholder, field_name placeholder) int
// $isreftype returns true if the type is a reference type.
//
// This builtin function can be used in two ways:
// 1. `isreftype[type]()` – check passed type
// 2. `isreftype(expr)` – check type of passed expression
//
// Examples:
// ```
// assert isreftype[int]() == false
// assert isreftype[string]() == true
// assert isreftype[[]int]() == true
// assert isreftype[map[string]int]() == true
// assert isreftype('hello') == true
// assert isreftype(10) == true
// ```
pub fn $isreftype[T](typ T) bool
// $sizeof returns the size of a type in bytes.
//
// This builtin function can be used in two ways:
//
// 1. `sizeof[type]()` – returns the size of the type in bytes
// 2. `sizeof(expr)` – returns the size of the type of the expression in bytes
//
// The size of a type is the number of bytes it occupies in memory.
//
// Example:
// ```
// assert sizeof[i64]() == 8
// assert sizeof[[]int]() == 32
// assert sizeof('hello') == 16
// assert sizeof(i64(100)) == 8
// assert sizeof(true) == 1
// ```
pub fn $sizeof[T](typ T) int
// $typeof returns the [TypeInfo](#TypeInfo) of the given expression.
//
// Example:
// ```
// type StringOrInt = string | int
//
// fn foo(x StringOrInt) {
// if typeof(x).name == 'string' {
// println('x is a string')
// }
// }
//
pub fn $typeof[T](typ T) TypeInfo
// $dump prints the given expression with position of `dump()` call.
//
// Example:
// ```
// name := 'John'
// dump(name)
// ```
// Output:
// ```
// [/Users/petrmakhnev/intellij-v/main.v:2] name: John
// ```
pub fn $dump[T](typ T) T
================================================
FILE: src/metadata/stubs/c_decl.v
================================================
module stubs
pub struct UnknownCDeclaration {
pub mut:
unknown_field &UnknownCDeclaration
}
pub fn (c &UnknownCDeclaration) unknown_method(...any) any
================================================
FILE: src/metadata/stubs/channels.v
================================================
module stubs
// `chan` keyword defines a typed channel that is used for communication between
// several threads in multithreaded programs.
//
// Channels are a typed conduit through which you can send and receive values
// with the channel (`<-`) operator.
//
// ```
// ch := chan int{} // channel of ints
// ch2 := chan f64{} // channel of f64s
// ```
//
// Values can be sent to a channel using the arrow operator <-:
//
// ```
// ch <- 5
// ```
//
// Or obtained from a channel:
//
// ```
// i := <-ch
// ```
//
// Learn more about channels in the [documentation](https://docs.vosca.dev/concepts/concurrency/channels.html).
pub struct ChanInit {
pub:
// cap fields describes the size of the buffered channel.
//
// The channel size describes the number of elements that can be
// written to the channel without blocking.
// If more elements are written to the channel than the buffer size,
// then the write is blocked until another thread reads the element
// from the channel and there is free space.
//
// If `cap == 0` (default), then the channel is not buffered.
//
// **Example**
// ```
// ch := chan int{cap: 10} // buffered channel
// ch <- 1 // no blocking
// ```
//
// **Example**
// ```
// ch := chan int{} // unbuffered channel
// ch <- 1 // block until another thread reads from the channel
// ```
cap int
}
================================================
FILE: src/metadata/stubs/compile_time.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module stubs
// This file contains definitions of compile time functions and constants.
import v.embed_file { EmbedFileData }
// @FN replaced with the name of the current V function.
pub const @FN = ''
// @METHOD replaced with name of the current V method and receiver type: `ReceiverType.MethodName`.
pub const @METHOD = ''
// @MOD replaced with the name of the current V module.
pub const @MOD = ''
// @STRUCT replaced with the name of the current V struct.
pub const @STRUCT = ''
// @FILE replaced with the absolute path of the V source file.
pub const @FILE = ''
// @LINE replaced with the V line number where it appears (as a string).
pub const @LINE = ''
// @FILE_LINE replaced with `@FILE:@LINE`, but the file part is a relative path.
pub const @FILE_LINE = ''
// @COLUMN replaced with the column where it appears (as a string).
pub const @COLUMN = ''
// @VEXE replaced with the path to the V compiler.
pub const @VEXE = ''
// @VEXEROOT replaced with the folder, where the V executable is.
pub const @VEXEROOT = ''
// @VHASH replaced with the shortened commit hash of the V compiler.
pub const @VHASH = ''
// @VMOD_FILE replaced with the contents of the nearest v.mod file.
pub const @VMOD_FILE = ''
// @VMODROOT replaced with the folder, where the nearest v.mod file is.
pub const @VMODROOT = ''
// CompressionType is the type of compression used for the embedded file.
// See [$embed_file] for more details.
pub enum CompressionType {
zlib
}
// $embed_file embed a file in a binary.
//
// Passed file path can be absolute or relative.
//
// When the program is compiled without the `-prod` flag, the file will not be embedded. Instead,
// it will be loaded the first time your program calls
//
//
// When you compile with -prod, the file will be embedded inside your executable,
// increasing your binary size, but making it more self contained and thus easier
// to distribute.
// In this case, `embedded_file.data()` will cause no IO, and it will always return the same data.
//
// `$embed_file` supports compression of the embedded file when compiling with `-prod`.
// Currently only one compression type is supported: `zlib`. See [CompressionType](CompressionType) for more details.
//
// Example:
// ```
// embedded_file := $embed_file('v.png', .zlib) // compressed using zlib
// data := embedded_file.data() // get data as a u8 array
// path := embedded_file.path // get path to the file
// ```
pub fn $embed_file(path string, compression_type CompressionType) EmbedFileData
// $tmpl embed and parse template file.
//
// Passed file path can be absolute or relative.
//
// `$tmpl` compiles an template into V during compilation, and embeds the resulting
// code into the current function. That means that the template automatically has
// access to that function's entire environment (like variables).
//
// See [Template documentation](https://docs.vosca.dev/concepts/templates/overview.html) for more details.
//
// Example:
// ```
// fn build() string {
// name := 'Peter'
// age := 25
// numbers := [1, 2, 3]
// return $tmpl('template.txt')
// }
// ```
pub fn $tmpl(path string) string
// $env obtain the value of the environment variable with passed name at compile time.
//
// Example:
// ```
// println($env('HOME'))
// ```
pub fn $env(name string) string
// $compile_error causes a compile error with the passed message.
//
// Example:
// ```
// $if windows {
// $compile_error('Windows is not supported')
// }
// ```
@[noreturn]
pub fn $compile_error(msg string)
// $compile_warn causes a compile warning with the passed message.
//
// Example:
// ```
// $if windows {
// $compile_warn('Windows is not fully supported')
// }
// ```
pub fn $compile_warn(msg string)
// _likely_ is a hint to the compiler that the passed expression is **likely to be true**,
// so it can generate assembly code, with less chance of
// [branch misprediction](https://en.wikipedia.org/wiki/Branch_predictor).
//
// Example:
// ```
// if _likely_(x > 0) {
// // code
// }
// ```
//
// In a non-C backend, it is ignored.
pub fn _likely_(typ bool) bool
// _unlikely_ is a hint to the compiler that the passed expression is **highly improbable**.
// See also [branch predictor](https://en.wikipedia.org/wiki/Branch_predictor).
//
// Example:
// ```
// if _unlikely_(x < 0) {
// // code
// }
// ```
//
// In a non-C backend, it is ignored.
pub fn _unlikely_(typ bool) bool
================================================
FILE: src/metadata/stubs/compile_time_constants.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module stubs
// This file contains definitions of compile time constants used in $if.
// Example:
// ```v
// $if linux {
// println('linux') // this will be printed only if the current OS is Linux
// } $else $if windows {
// println('windows') // this will be printed only if the current OS is Windows
// } $else {
// println('other') // this will be printed if the current OS is neither Linux nor Windows
// }
// ```
// OSs
// windows set to `true` if the current OS is Windows.
pub const windows = false
// linux set to `true` if the current OS is Linux.
pub const linux = false
// macos set to `true` if the current OS is macOS.
pub const macos = false
// mac set to `true` if the current OS is macOS.
pub const mac = false
// darwin set to `true` if the current OS is macOS.
pub const darwin = false
// freebsd set to `true` if the current OS is FreeBSD.
pub const freebsd = false
// openbsd set to `true` if the current OS is OpenBSD.
pub const openbsd = false
// netbsd set to `true` if the current OS is NetBSD.
pub const netbsd = false
// serenity set to `true` if the current OS is Serenity.
pub const serenity = false
// vinix set to `true` if the current OS is Vinix.
pub const vinix = false
// ios set to `true` if the current OS is iOS.
pub const ios = false
// android set to `true` if the current OS is Android.
pub const android = false
// emscripten set to `true` if the current OS is Emscripten.
pub const emscripten = false
// js_node set to `true` if the current platform is Node.js.
pub const js_node = false
// js_freestanding set to `true` if the current platform is pure JavaScript.
pub const js_freestanding = false
// js_browser set to `true` if the current platform is JavaScript in a browser.
pub const js_browser = false
// js set to `true` if the current platform is JavaScript.
pub const js = false
// mach set to `true` if the current OS is Mach.
pub const mach = false
// dragonfly set to `true` if the current OS is Dragonfly.
pub const dragonfly = false
// gnu set to `true` if the current OS is GNU.
pub const gnu = false
// hpux set to `true` if the current OS is HP-UX.
pub const hpux = false
// haiku set to `true` if the current OS is Haiku.
pub const haiku = false
// qnx set to `true` if the current OS is QNX.
pub const qnx = false
// solaris set to `true` if the current OS is Solaris.
pub const solaris = false
// termux set to `true` if the current OS is Termux.
pub const termux = false
// Compilers
// gcc set to `true` if the current compiler is GCC.
pub const gcc = false
// tiny set to `true` if the current compiler is TinyCC.
pub const tiny = false
// clang set to `true` if the current compiler is Clang.
pub const clang = false
// mingw set to `true` if the current compiler is MinGW.
pub const mingw = false
// msvc set to `true` if the current compiler is MSVC.
pub const msvc = false
// cpp set to `true` if the current compiler is C++.
pub const cplusplus = false
// Platforms
// x86 set to `true` if the current platform is x86.
pub const amd64 = false
// arm set to `true` if the current platform is ARM.
pub const arm64 = false
// x64 set to `true` if the current platform is x64.
pub const x64 = false
// x32 set to `true` if the current platform is x32.
pub const x32 = false
// little_endian set to `true` if the current platform is little endian.
pub const little_endian = false
// big_endian set to `true` if the current platform is big endian.
pub const big_endian = false
// Other
// debug set to `true` if the -g flag is passed to the compiler.
pub const debug = false
// prod set to `true` if the -prod flag is passed to the compiler.
pub const prod = false
// test set to `true` file run with `v test`.
pub const test = false
// glibc set to `true` if the -glibc flag is passed to the compiler.
pub const glibc = false
// prealloc set to `true` if the -prealloc flag is passed to the compiler.
pub const prealloc = false
// no_bounds_checking set to `true` if the -no_bounds_checking flag is passed to the compiler.
pub const no_bounds_checking = false
// freestanding set to `true` if the -freestanding flag is passed to the compiler.
pub const freestanding = false
// no_segfault_handler set to `true` if the -no_segfault_handler flag is passed to the compiler.
pub const no_segfault_handler = false
// no_backtrace set to `true` if the -no_backtrace flag is passed to the compiler.
pub const no_backtrace = false
// no_main set to `true` if the -no_main flag is passed to the compiler.
pub const no_main = false
================================================
FILE: src/metadata/stubs/compile_time_reflection.v
================================================
// Copyright (c) 2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module stubs
// This file contains definitions of compile time reflection.
// $int describes any integer type.
//
// Example:
// ```
// $for f in Test.fields {
// $if f.typ is $int {
// println(f.name)
// }
// }
// ```
pub const $int = TypeInfo{}
// $float describes any float type.
//
// Example:
// ```
// $for f in Test.fields {
// $if f.typ is $float {
// println(f.name)
// }
// }
// ```
pub const $float = TypeInfo{}
// $array describes any array type.
//
// Example:
// ```
// $for f in Test.fields {
// $if f.typ is $array {
// println(f.name)
// }
// }
// ```
pub const $array = TypeInfo{}
// $map describes any map type.
//
// Example:
// ```
// $for f in Test.fields {
// $if f.typ is $map {
// println(f.name)
// }
// }
// ```
pub const $map = TypeInfo{}
// $struct describes any struct type.
//
// Example:
// ```
// $for f in Test.fields {
// $if f.typ is $struct {
// println(f.name)
// }
// }
// ```
pub const $struct = TypeInfo{}
// $interface describes any interface type.
//
// Example:
// ```
// $for f in Test.fields {
// $if f.typ is $interface {
// println(f.name)
// }
// }
// ```
pub const $interface = TypeInfo{}
// $enum describes any enum type.
//
// Example:
// ```
// $for f in Test.fields {
// $if f.typ is $enum {
// println(f.name)
// }
// }
// ```
pub const $enum = TypeInfo{}
// $alias describes any alias type.
//
// Example:
// ```
// $for f in Test.fields {
// $if f.typ is $alias {
// println(f.name)
// }
// }
// ```
pub const $alias = TypeInfo{}
// $sumtype describes any sumtype type.
//
// Example:
// ```
// $for f in Test.fields {
// $if f.typ is $sumtype {
// println(f.name)
// }
// }
// ```
pub const $sumtype = TypeInfo{}
// $function describes any function type.
//
// Example:
// ```
// $for f in Test.fields {
// $if f.typ is $function {
// println(f.name)
// }
// }
// ```
pub const $function = TypeInfo{}
// $option describes any option type.
//
// Example:
// ```
// $for f in Test.fields {
// $if f.typ is $option {
// println(f.name)
// }
// }
// ```
pub const $option = TypeInfo{}
struct CompileTimeTypeInfo {
pub:
// fields describes the list of structure fields.
// This field can only be used inside `$for`.
//
// Example:
// ```v
// struct Foo {
// a int
// b string
// }
//
// fn main() {
// $for field in Foo.fields {
// println(field.name)
// }
// }
// ```
fields []FieldData
}
================================================
FILE: src/metadata/stubs/errors.v
================================================
module stubs
// err is a special variable that is set with an error
// and is used to handle errors in V.
//
// It can be used inside two places:
//
// 1. inside `or` block:
// ```
// fn foo() !int {
// return error("not implemented");
// }
//
// foo() or {
// panic(err);
// // ^^^ err is set with error("not implemented")
// }
// ```
//
// 2. inside else block for if guard:
// ```
// fn foo() !int {
// return error("not implemented");
// }
//
// if val := foo() {
// // val is set with int
// } else {
// panic(err);
// // ^^^ err is set with error("not implemented")
// }
// ```
//
// See [Documentation](https://docs.vosca.dev/concepts/error-handling/overview.html)
// for more details.
pub const err = IError{}
================================================
FILE: src/metadata/stubs/implicit.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module stubs
// Any is any type in code.
//
// It is needed to define all implicit methods of all types.
type Any = any
// str returns a string representation of the type.
//
// **Note**
//
// This method is implicitly implemented by any type,
// you can override it for your type:
// ```
// struct MyStruct {
// name string
// }
//
// pub fn (s MyStruct) str() string {
// return s.name
// }
// ```
//
// Example:
//
// ```
// struct Foo {}
//
// fn main() {
// s := Foo{}
// println(s.str()) // Foo{}
//
// mp := map[string]int{}
// println(mp.str()) // map[string]int{}
// }
// ```
pub fn (a Any) str() string
// FlagEnum describes a enum with `[flag]` attribute.
//
// See [Flag](#flag) attribute for detail.
pub enum FlagEnum {}
// has checks if the enum value has the passed flag.
//
// Example:
// ```
// [flag]
// enum Permissions {
// read // = 0b0001
// write // = 0b0010
// other // = 0b0100
// }
//
// fn main() {
// p := Permissions.read
// assert p.has(.read) // test if p has read flag
// assert p.has(.read | .other) // test if *at least one* of the flags is set
// }
// ```
pub fn (f FlagEnum) has(flag FlagEnum) bool
// all checks if the enum value has all passed flags.
//
// Example:
// ```
// [flag]
// enum Permissions {
// read // = 0b0001
// write // = 0b0010
// other // = 0b0100
// }
//
// fn main() {
// p := Permissions.read | .write
// assert p.all(.read | .write) // test if *all* of the flags is set
// }
// ```
pub fn (f FlagEnum) all(flag FlagEnum) bool
// set sets the passed flags.
// If the flag is already set, it will be ignored.
//
// Example:
// ```
// [flag]
// enum Permissions {
// read // = 0b0001
// write // = 0b0010
// other // = 0b0100
// }
//
// fn main() {
// mut p := Permissions.read
// p.set(.write)
// assert p.has(.write)
// }
// ```
pub fn (f FlagEnum) set(flag FlagEnum)
// toggle toggles the passed flags.
// If the flag is already set, it will be unset.
// If the flag is not set, it will be set.
//
// Example:
// ```
// [flag]
// enum Permissions {
// read // = 0b0001
// write // = 0b0010
// other // = 0b0100
// }
//
// fn main() {
// mut p := Permissions.read
// p.toggle(.read)
// assert !p.has(.read)
// }
// ```
pub fn (f FlagEnum) toggle(flag FlagEnum)
// clear clears the passed flags.
// If the flag is not set, it will be ignored.
//
// Example:
// ```
// [flag]
// enum Permissions {
// read // = 0b0001
// write // = 0b0010
// other // = 0b0100
// }
//
// fn main() {
// mut p := Permissions.read
// p.clear(.read)
// assert !p.has(.read)
// }
// ```
pub fn (f FlagEnum) clear(flag FlagEnum)
================================================
FILE: src/metadata/stubs/primitives.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module stubs
// This file contains definitions of primitive types V.
// bool is the set of boolean values, true and false.
pub type bool = bool
// u8 is the set of all unsigned 8-bit integers.
// Range: 0 through 255.
pub type u8 = u8
// u16 is the set of all unsigned 16-bit integers.
// Range: 0 through 65535.
pub type u16 = u16
// u32 is the set of all unsigned 32-bit integers.
// Range: 0 through 4294967295.
pub type u32 = u32
// u64 is the set of all unsigned 64-bit integers.
// Range: 0 through 18446744073709551615.
pub type u64 = u64
// usize is platform-dependent unsigned integer type.
pub type usize = u64
// i8 is the set of all signed 8-bit integers.
// Range: -128 through 127.
pub type i8 = i8
// i16 is the set of all signed 16-bit integers.
// Range: -32768 through 32767.
pub type i16 = i16
// i32 is the set of all signed 32-bit integers.
// Range: -2147483648 through 2147483647.
pub type i32 = int
// int is the set of all signed 32-bit integers.
// Range: -2147483648 through 2147483647.
pub type int = int
// i64 is the set of all signed 64-bit integers.
// Range: -9223372036854775808 through 9223372036854775807.
pub type i64 = i64
// isize is a signed integer type, whose size varies, and is 32bit on 32bit platforms, or 64bit on 64bit platforms.
pub type isize = i64
// usize is an unsigned integer type, whose size varies and is 32bit on 32bit platforms, or 64bit on 64bit platforms.
pub type usize = u64
// f32 is the set of all IEEE-754 32-bit floating-point numbers.
pub type f32 = f32
// f64 is the set of all IEEE-754 64-bit floating-point numbers.
pub type f64 = f64
// byte is an alias for u8 and is equivalent to u8 in all ways.
// Do not use `byte` in new code, use `u8` instead.
pub type byte = u8
// rune is used, for representing individual Unicode codepoints. It is 32bit sized.
pub type rune = u32
// char is similar to u8, it is mostly used for [C interoperability](https://docs.vosca.dev/advanced-concepts/v-and-c.html).
// In C, the type `char` can be signed or unsigned, depending on platform.
pub type char = u8
// voidptr is an untyped pointer. You can pass any other type of pointer value, to a function that accepts a voidptr.
// Mostly used for [C interoperability](https://docs.vosca.dev/advanced-concepts/v-and-c.html).
pub type voidptr = voidptr
// byteptr is a pointer to bytes. Deprecated. Use `&u8` instead in new code.
// Mostly used for [C interoperability](https://docs.vosca.dev/advanced-concepts/v-and-c.html).
pub type byteptr = byteptr
// charptr is a pointer to chars.
// Mostly used for [C interoperability](https://docs.vosca.dev/advanced-concepts/v-and-c.html).
pub type charptr = charptr
================================================
FILE: src/metadata/stubs/threads.v
================================================
module stubs
// Thread represent `thread T` type.
struct Thread[T] {}
// wait waits for thread to finish and returns its result.
//
// It is a blocking call, and will block the current thread until the thread finishes.
//
// Return type is `T` where `T` is the type of the thread.
// ```
// int_thread := spawn fn () int { return 1 }()
// // ^^^ return `int` type
// int_thread.wait() // returns int
//
// arr_string_thread := spawn fn () []string { return ['Hello World'] }()
// // ^^^^^^^^ return `[]string` type
// arr_string_thread.wait() // returns []string
// ```
//
// Example:
// ```
// fn expensive_computing(i int) int {
// return i * i
// }
//
// fn main() {
// mut thread := spawn expensive_computing(100)
// // ^^^^^^ has type `thread int`, because `expensive_computing()` returns `int`
// result := thread.wait()
//
// println('Result: ${result}')
// // Output:
// // Result: 10000
// }
// ```
//
pub fn (t Thread[T]) wait() T
// ThreadPool represent a pool of threads: `[]thread T` type.
struct ThreadPool[T] {}
// wait waits for all threads in the pool to finish
// and returns result of all threads as array.
//
// It is a blocking call, and will not return until all threads are finished.
//
// Return type is `[]T` where `T` is the type of the thread.
// ```
// mut int_threads := []thread int{}
// // ^^^
// int_threads.wait() // returns []int
//
// mut arr_string_threads := []thread []string{}
// // ^^^^^^^^
// arr_string_threads.wait() // returns [][]string
// ```
//
// Example:
// ```
// fn expensive_computing(i int) int {
// return i * i
// }
//
// fn main() {
// mut threads := []thread int{}
// for i in 1 .. 10 {
// threads << spawn expensive_computing(i)
// }
//
// results := threads.wait()
// println('All jobs finished: ${results}')
//
// // Output:
// // All jobs finished: [1, 4, 9, 16, 25, 36, 49, 64, 81]
// }
// ```
//
// See [Documentation](https://docs.vosca.dev/concepts/concurrency.html) for more details.
pub fn (t ThreadPool[T]) wait() []T
================================================
FILE: src/metadata/stubs/vweb.v
================================================
// Copyright (c) 2022-2023 Petr Makhnev. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
module stubs
// This file contains stubs for the vweb module.
import vweb
struct VWebTemplate {}
// html method renders a template.
// See documentation for [$vweb]($vweb) for more information.
pub fn (v VWebTemplate) html() vweb.Result
// $vweb constant allows you to render HTML template in endpoint functions.
//
// `$vweb.html()` in method like `_() vweb.Result`
// render the `.html` in folder `./templates/`
//
// `$vweb.html()` compiles an HTML template into V during compilation, and
// embeds the resulting code into the current function.
// That means that the template automatically has access to that
// function's entire environment (like variables).
//
// See [vweb documentation](https://modules.vosca.dev/standard_library/vweb.html) for more information.
//
// Example:
// ```
// ['/']
// pub fn (mut app App) page_home() vweb.Result {
// // will render `./templates/page/home.html`
// return $vweb.html()
// }
// ```
pub const $vweb = VWebTemplate{}
================================================
FILE: src/project/flavors/MacToolchainFlavor.v
================================================
module flavors
import os
pub struct MacToolchainFlavor {}
fn (s &MacToolchainFlavor) get_home_page_candidates() []string {
return ['/usr/local/Cellar/v', '/usr/local/v', os.expand_tilde_to_home('~/v')]
.filter(os.is_dir)
}
fn (s &MacToolchainFlavor) is_applicable() bool {
$if macos {
return true
}
return false
}
================================================
FILE: src/project/flavors/SymlinkToolchainFlavor.v
================================================
module flavors
import os
pub struct SymlinkToolchainFlavor {}
fn (s &SymlinkToolchainFlavor) get_home_page_candidates() []string {
symlink_path_candidates := [
'/usr/bin/v',
'/usr/local/bin/v',
'${os.home_dir()}/.local/bin/v',
]
mut result := []string{}
for symlink_path_candidate in symlink_path_candidates {
path_to_compiler := os.real_path(symlink_path_candidate)
if path_to_compiler == '' {
continue
}
compiler_dir := os.dir(path_to_compiler)
if os.is_dir(compiler_dir) {
result << compiler_dir
}
}
return result
}
fn (s &SymlinkToolchainFlavor) is_applicable() bool {
$if linux || macos || openbsd || freebsd || netbsd {
return true
}
return false
}
================================================
FILE: src/project/flavors/SysPathToolchainFlavor.v
================================================
module flavors
import os
pub struct SysPathToolchainFlavor {}
fn (s &SysPathToolchainFlavor) get_home_page_candidates() []string {
return os.getenv('PATH')
.split(os.path_delimiter)
.filter(it != '')
.filter(os.is_dir)
.map(if os.file_name(it) == '.bin' {
os.dir(it)
} else {
it
})
}
fn (s &SysPathToolchainFlavor) is_applicable() bool {
return true
}
================================================
FILE: src/project/flavors/ToolchainFlavor.v
================================================
module flavors
import arrays
import os
pub interface ToolchainFlavor {
get_home_page_candidates() []string
is_applicable() bool
}
fn is_valid_toolchain_path(path string) bool {
return os.is_dir(path) && has_executable(path, 'v') && has_vlib(path)
}
fn has_executable(path string, exe string) bool {
mut with_exe := os.join_path(path, exe)
$if windows {
with_exe += '.exe'
}
return os.is_executable(with_exe)
}
fn has_vlib(path string) bool {
vlib_path := os.join_path(path, 'vlib')
return os.is_dir(vlib_path)
}
pub fn get_toolchain_candidates() []string {
mut flavors := []ToolchainFlavor{}
flavors << VenvToolchainFlavor{}
$if !windows {
// On Windows, a symlink to V is not created, so it makes no sense to check this option.
flavors << SymlinkToolchainFlavor{}
}
flavors << SysPathToolchainFlavor{}
flavors << UserHomeToolchainFlavor{}
$if macos {
flavors << MacToolchainFlavor{}
}
$if windows {
flavors << WinToolchainFlavor{}
}
return arrays.flatten(flavors
.filter(it.is_applicable())
.map(it.get_home_page_candidates()))
.filter(is_valid_toolchain_path)
}
================================================
FILE: src/project/flavors/UserHomeToolchainFlavor.v
================================================
module flavors
import os
pub struct UserHomeToolchainFlavor {}
fn (s &UserHomeToolchainFlavor) get_home_page_candidates() []string {
home := os.home_dir()
files := os.ls(home) or { return [] }
return files
.filter(os.is_dir)
.filter(fn (path string) bool {
name := os.file_name(path).to_lower()
return name == 'v' || name == 'vlang'
})
}
fn (s &UserHomeToolchainFlavor) is_applicable() bool {
return true
}
================================================
FILE: src/project/flavors/VenvToolchainFlavor.v
================================================
module flavors
import os
pub struct VenvToolchainFlavor {}
fn (s &VenvToolchainFlavor) get_home_page_candidates() []string {
mut res := []string{}
if vroot := os.getenv_opt('VROOT') {
res << vroot
}
if vexe := os.getenv_opt('VEXE') {
res << os.dir(vexe)
}
return res.filter(os.is_dir)
}
fn (s &VenvToolchainFlavor) is_applicable() bool {
return true
}
================================================
FILE: src/project/flavors/WinToolchainFlavor.v
================================================
module flavors
import os
pub struct WinToolchainFlavor {}
fn (s &WinToolchainFlavor) get_home_page_candidates() []string {
mut res := []string{}
result := os.execute('where v')
if result.exit_code == 0 {
res << os.dir(result.output.trim_space())
}
program_files := os.getenv('ProgramFiles')
if !os.exists(program_files) || !os.is_dir(program_files) {
return res
}
if files := os.ls(program_files) {
res << files
.filter(os.is_dir)
.filter(fn (path string) bool {
name := os.file_name(path).to_lower()
return name == 'v' || name.starts_with('vlang')
})
}
return res
}
fn (s &WinToolchainFlavor) is_applicable() bool {
$if windows {
return true
}
return false
}
================================================
FILE: src/project/project.v
================================================
module project
import project.flavors
// get_toolchain_candidates looks for possible places where the V compiler was installed.
// The function returns an array of candidates, where the first element is the highest priority.
// If no candidate is found, then an empty array is returned.
//
// A priority:
// 1. `VROOT` or `VEXE` environment variables
// 2. Symbolic link `/usr/local/bin/v` -> `v` (except Windows)
// 3. Path from `PATH` environment variable
// 4. Other additional search options
pub fn get_toolchain_candidates() []string {
return distinct_strings(flavors.get_toolchain_candidates())
}
fn distinct_strings(arr []string) []string {
mut set := map[string]bool{}
for el in arr {
set[el] = true
}
return set.keys()
}
================================================
FILE: src/server/BackgroundThread.v
================================================
module server
import time
import loglib
type Task = fn ()
enum BackgroundThreadState {
stopped
running
}
// BackgroundThread is a simple abstraction a system thread.
// It accepts tasks and executes them in FIFO order.
//
// By executing tasks in a separate thread, we can perform long
// operations without blocking the main thread.
struct BackgroundThread {
end_ch chan bool
task_ch chan Task = chan Task{cap: 20}
mut:
state BackgroundThreadState
}
// start starts a background thread.
pub fn (mut b BackgroundThread) start() {
spawn fn [mut b] () {
b.state = .running
for {
select {
task := <-b.task_ch {
task()
}
_ := <-b.end_ch {
return
}
100 * time.second {
// wait
}
}
}
}()
}
// stop stops a background thread.
pub fn (b &BackgroundThread) stop() {
if b.state == .stopped {
loglib.warn('Cannot end stopped background thread')
return
}
b.end_ch <- true
}
// queue queues a task to a background thread.
// Tasks will be executed in FIFO order.
pub fn (b &BackgroundThread) queue(cb fn ()) {
if b.state == .stopped {
loglib.warn('Cannot queue task to stopped background thread')
return
}
b.task_ch <- cb
}
================================================
FILE: src/server/README.md
================================================
# Description
`server` module describes an implementation of the Language Server Protocol server.
================================================
FILE: src/server/ResponseWriter.v
================================================
module server
import jsonrpc
import lsp
pub type ResponseWriter = jsonrpc.ResponseWriter
fn (mut wr ResponseWriter) wrap_error(err IError) IError {
if err is none {
return err
}
wr.log_message(err.msg(), .error)
return none
}
// log_message sends a window/logMessage notification to the client
pub fn (mut wr ResponseWriter) log_message(message string, typ lsp.MessageType) {
wr.write_notify('window/logMessage', lsp.LogMessageParams{
@type: typ
message: message
})
}
================================================
FILE: src/server/code_lens/CodeLensVisitor.v
================================================
module code_lens
import lsp
import config
import json
import server.tform
import analyzer.psi
import analyzer.psi.search
@[noinit]
pub struct CodeLensVisitor {
cfg config.CodeLensConfig
uri lsp.DocumentUri
containing_file &psi.PsiFile
is_test_file bool
mut:
run_lens_seen bool
first_test_seen bool
result []lsp.CodeLens
}
pub fn new_visitor(cfg config.CodeLensConfig, uri lsp.DocumentUri, containing_file &psi.PsiFile) CodeLensVisitor {
return CodeLensVisitor{
cfg: cfg
uri: uri
containing_file: containing_file
is_test_file: containing_file.is_test_file()
}
}
pub fn (mut v CodeLensVisitor) result() []lsp.CodeLens {
return v.result
}
pub fn (mut v CodeLensVisitor) accept(root psi.PsiElement) {
mut walker := psi.new_tree_walker(root.node())
defer { walker.free() }
for {
node := walker.next() or { break }
v.process_node(node)
}
}
@[inline]
pub fn (mut v CodeLensVisitor) process_node(node psi.AstNode) {
if node.type_name == .function_declaration && v.cfg.enable_run_lens {
v.add_run_lens(node)
}
if node.type_name == .function_declaration && v.is_test_file && v.cfg.enable_run_tests_lens {
v.add_run_test_lens(node)
}
if node.type_name == .interface_declaration && v.cfg.enable_inheritors_lens {
v.add_interface_implementations_lens(node)
}
if node.type_name == .struct_declaration && v.cfg.enable_super_interfaces_lens {
v.add_super_interfaces_lens(node)
}
}
// add_run_test_lens adds a CodeLens for running the test function or whole file.
pub fn (mut v CodeLensVisitor) add_run_test_lens(node psi.AstNode) {
name_node := node.child_by_field_name('name') or { return }
name := name_node.text(v.containing_file.source_text)
if !name.starts_with('test_') {
return
}
v.add_lens(node, lsp.Command{
title: '▶ Run test'
command: 'v-analyzer.runTests'
arguments: [
v.uri.path(),
name,
]
})
if !v.first_test_seen {
v.add_lens(node, lsp.Command{
title: 'all file tests'
command: 'v-analyzer.runTests'
arguments: [
v.uri.path(),
]
})
}
v.first_test_seen = true
}
// add_run_lens adds a CodeLens for running the main function.
pub fn (mut v CodeLensVisitor) add_run_lens(node psi.AstNode) {
if v.run_lens_seen {
// Since in file there can be only one main function, we don't need to process
// other functions if we already found the main function.
return
}
name := node.child_by_field_name('name') or { return }
if !name.text_matches(v.containing_file.source_text, 'main') {
return
}
v.add_lens(node, lsp.Command{
title: '▶ Run workspace'
command: 'v-analyzer.runWorkspace'
arguments: [
v.uri.path(),
]
})
v.add_lens(node, lsp.Command{
title: 'single file'
command: 'v-analyzer.runFile'
arguments: [
v.uri.path(),
]
})
v.run_lens_seen = true
}
// add_interface_implementations_lens adds a CodeLens for showing the implementations of an interface.
// If the interface has no implementations, no CodeLens is added.
//
// By clicking on the CodeLens, the user is shown the implementations.
pub fn (mut v CodeLensVisitor) add_interface_implementations_lens(node psi.AstNode) {
element := psi.create_element(node, v.containing_file)
if element is psi.InterfaceDeclaration {
implementations := search.implementations(*element)
if implementations.len == 0 {
return
}
identifier_text_range := element.identifier_text_range()
locations := tform.elements_to_locations(implementations)
lens_title := implementations.len.str() +
if implementations.len == 1 { ' implementation' } else { ' implementations' }
v.add_lens(node, lsp.Command{
title: lens_title
command: 'v-analyzer.showReferences'
arguments: [
v.uri.path(),
json.encode(lsp.Position{
line: identifier_text_range.line
character: identifier_text_range.column
}),
json.encode(locations),
]
})
}
}
// add_super_interfaces_lens adds a CodeLens for showing the super interfaces of a struct.
// If the struct has no super interfaces, no CodeLens is added.
//
// By clicking on the CodeLens, the user is shown the super interfaces.
pub fn (mut v CodeLensVisitor) add_super_interfaces_lens(node psi.AstNode) {
element := psi.create_element(node, v.containing_file)
if element is psi.StructDeclaration {
supers := search.supers(*element)
if supers.len == 0 {
return
}
identifier_text_range := element.identifier_text_range()
locations := tform.elements_to_locations(supers)
lens_title := 'implement ' + supers.len.str() +
if supers.len == 1 { ' interface' } else { ' interfaces' }
v.add_lens(node, lsp.Command{
title: lens_title
command: 'v-analyzer.showReferences'
arguments: [
v.uri.path(),
json.encode(lsp.Position{
line: identifier_text_range.line
character: identifier_text_range.column
}),
json.encode(locations),
]
})
}
}
// add_lens adds a new CodeLens with the given command.
pub fn (mut v CodeLensVisitor) add_lens(node psi.AstNode, cmd lsp.Command) {
start_point := node.start_point()
start := lsp.Position{
line: int(start_point.row)
character: int(start_point.column)
}
v.result << lsp.CodeLens{
range: lsp.Range{
start: start
end: start
}
command: cmd
}
}
================================================
FILE: src/server/completion/CompletionContext.v
================================================
module completion
import analyzer.psi
import lsp
pub const dummy_identifier = 'vAnalyzerRulezzz'
pub struct CompletionContext {
pub:
element psi.PsiElement
position lsp.Position
offset u64
trigger_kind lsp.CompletionTriggerKind
pub mut:
is_test_file bool
is_start_of_file bool
is_top_level bool
is_statement bool
is_expression bool
is_type_reference bool
is_import_name bool
is_attribute bool
is_assert_statement bool
inside_loop bool
after_dot bool
after_at bool
// if the struct is initialized with keys
// struct Foo { a: int, b: int }
inside_struct_init_with_keys bool
}
pub fn (c CompletionContext) expression() bool {
return c.is_expression && !c.after_dot && !c.after_at && !c.inside_struct_init_with_keys
}
pub fn (mut c CompletionContext) compute() {
containing_file := c.element.containing_file() or { return }
c.is_test_file = containing_file.is_test_file()
range := c.element.text_range()
line := range.line
if line < 3 {
c.is_start_of_file = true
}
symbol_at := containing_file.symbol_at(range)
c.after_dot = symbol_at == `.`
c.after_at = c.element.get_text().starts_with('@')
parent := c.element.parent() or { return }
match parent.node().type_name {
.import_name { c.is_import_name = true }
.keyed_element { c.inside_struct_init_with_keys = true }
.type_reference_expression { c.is_type_reference = true }
.for_statement, .compile_time_for_statement { c.inside_loop = true }
else {}
}
if grand := parent.parent() {
// Do not consider as reference_expression if it is inside an attribute.
if parent.node().type_name == .reference_expression {
c.is_expression = grand.node().type_name != .key_value_attribute
if grand.node().type_name == .element_list
|| grand.prev_sibling_of_type(.keyed_element) != none {
c.inside_struct_init_with_keys = true
}
}
match grand.node().type_name {
.simple_statement { c.is_statement = true }
.assert_statement { c.is_assert_statement = true }
.value_attribute { c.is_attribute = true }
.for_statement, .compile_time_for_statement { c.inside_loop = true }
else {}
}
if grand_grand := grand.parent() {
match grand_grand.node().type_name {
.source_file { c.is_top_level = true }
.for_statement, .compile_time_for_statement { c.inside_loop = true }
else {}
}
}
}
if !c.inside_loop {
// long way if the first three parents up are not loops
c.inside_loop = c.element.inside(.for_statement)
}
}
================================================
FILE: src/server/completion/CompletionProvider.v
================================================
module completion
pub interface CompletionProvider {
is_available(ctx &CompletionContext) bool
mut:
add_completion(ctx &CompletionContext, mut result CompletionResultSet)
}
================================================
FILE: src/server/completion/CompletionResultSet.v
================================================
module completion
import lsp
pub struct CompletionResultSet {
mut:
elements []lsp.CompletionItem
}
pub fn (mut c CompletionResultSet) add_element(item lsp.CompletionItem) {
c.elements << item
}
pub fn (mut c CompletionResultSet) elements() []lsp.CompletionItem {
return c.elements.filter(it.label != '')
}
================================================
FILE: src/server/completion/providers/AssertCompletionProvider.v
================================================
module providers
import server.completion
import lsp
pub struct AssertCompletionProvider {}
fn (_ &AssertCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
if !ctx.is_test_file {
return false
}
return ctx.is_statement || ctx.is_assert_statement
}
fn (mut _ AssertCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
result.add_element(lsp.CompletionItem{
label: 'assert expr'
kind: .keyword
insert_text_format: .snippet
insert_text: 'assert \${1:expr}'
})
result.add_element(lsp.CompletionItem{
label: 'assert expr, message'
kind: .keyword
insert_text_format: .snippet
insert_text: "assert \${1:expr}, '\${2:message}'$0"
})
}
================================================
FILE: src/server/completion/providers/AttributesCompletionProvider.v
================================================
module providers
import server.completion
import lsp
const attributes = [
'params',
'noinit',
'required',
'skip',
'assert_continues',
'unsafe',
'manualfree',
'heap',
'nonnull',
'primary',
'inline',
'direct_array_access',
'live',
'flag',
'noinline',
'noreturn',
'typedef',
'console',
'keep_args_alive',
'omitempty',
'json_as_number',
]
const attributes_with_colon = [
'sql',
'table',
'deprecated',
'deprecated_after',
'export',
'callconv',
]
pub struct AttributesCompletionProvider {}
fn (k &AttributesCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.is_attribute
}
fn (mut k AttributesCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
for attribute in attributes {
result.add_element(lsp.CompletionItem{
label: attribute
kind: .struct_
insert_text: attribute
})
}
for attribute in attributes_with_colon {
result.add_element(lsp.CompletionItem{
label: "${attribute}: 'value'"
kind: .struct_
insert_text: "${attribute}: '$1'$0"
insert_text_format: .snippet
})
}
}
================================================
FILE: src/server/completion/providers/CompileTimeConstantCompletionProvider.v
================================================
module providers
import server.completion
import lsp
const compile_time_constant = {
'FN': 'The name of the current function'
'METHOD': 'The name of the current method'
'MOD': 'The name of the current module'
'STRUCT': 'The name of the current struct'
'FILE': 'The absolute path:the current file'
'LINE': 'The line number of the current line (as a string)'
'FILE_LINE': 'The relative path and line number of the current line (like @FILE:@LINE)'
'COLUMN': 'The column number of the current line (as a string)'
'VEXE': 'The absolute path:the V compiler executable'
'VEXEROOT': "The absolute path:the V compiler executable's root directory"
'VHASH': "The V compiler's git hash"
'VMOD_FILE': 'The content:the nearest v.mod file'
'VMODROOT': "The absolute path:the nearest v.mod file's directory"
}
pub struct CompileTimeConstantCompletionProvider {}
fn (_ &CompileTimeConstantCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.after_at
}
fn (mut _ CompileTimeConstantCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
for constant, description in compile_time_constant {
result.add_element(lsp.CompletionItem{
label: '@${constant}'
kind: .constant
detail: description
insert_text: constant
})
}
}
================================================
FILE: src/server/completion/providers/FunctionLikeCompletionProvider.v
================================================
module providers
import server.completion
import lsp
pub const function_like_keywords = [
'dump',
'sizeof',
'typeof',
'isreftype',
'__offsetof',
]
pub struct FunctionLikeCompletionProvider {}
fn (k &FunctionLikeCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.expression()
}
fn (mut k FunctionLikeCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
for keyword in function_like_keywords {
result.add_element(lsp.CompletionItem{
label: '${keyword}()'
kind: .keyword
insert_text: '${keyword}($1)$0'
insert_text_format: .snippet
})
}
}
================================================
FILE: src/server/completion/providers/ImportsCompletionProvider.v
================================================
module providers
import server.completion
import lsp
pub struct ImportsCompletionProvider {}
fn (_ &ImportsCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return (ctx.is_expression || ctx.is_type_reference) && !ctx.after_dot && !ctx.after_at
&& !ctx.inside_struct_init_with_keys
}
fn (mut _ ImportsCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
file := ctx.element.containing_file() or { return }
imports := file.get_imports()
imports_names := imports.map(it.import_name())
for import_name in imports_names {
result.add_element(lsp.CompletionItem{
label: import_name
kind: .module_
insert_text: import_name
})
}
}
================================================
FILE: src/server/completion/providers/InitsCompletionProvider.v
================================================
module providers
import server.completion
import lsp
pub struct InitsCompletionProvider {}
fn (_ &InitsCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.expression()
}
fn (mut _ InitsCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
result.add_element(lsp.CompletionItem{
label: 'chan int{}'
kind: .snippet
detail: ''
insert_text: 'chan \${1:int}{}$0'
insert_text_format: .snippet
})
result.add_element(lsp.CompletionItem{
label: 'map[string]int{}'
kind: .snippet
detail: ''
insert_text: 'map[\${1:string}]\${2:int}{}$0'
insert_text_format: .snippet
})
result.add_element(lsp.CompletionItem{
label: 'thread int{}'
kind: .snippet
detail: ''
insert_text: 'thread \${1:int}{}$0'
insert_text_format: .snippet
})
}
================================================
FILE: src/server/completion/providers/JsonAttributeCompletionProvider.v
================================================
module providers
import analyzer.psi
import server.completion
import lsp
import utils
import v.token
pub struct JsonAttributeCompletionProvider {}
fn (p &JsonAttributeCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
if !ctx.is_attribute {
return false
}
return ctx.element.inside(.struct_field_declaration)
}
fn (mut p JsonAttributeCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
field_declaration := ctx.element.parent_of_type(.struct_field_declaration) or { return }
name := if field_declaration is psi.FieldDeclaration {
field_declaration.name()
} else {
return
}
json_name := p.json_name(name)
result.add_element(lsp.CompletionItem{
label: "json: '${json_name}'"
kind: .keyword
insert_text: "json: '\${1:${json_name}}'$0"
insert_text_format: .snippet
})
}
fn (mut p JsonAttributeCompletionProvider) json_name(field_name string) string {
without_underscore_and_at := field_name.trim_string_right('_').trim_string_left('@')
name_to_camelize := if token.is_key(without_underscore_and_at) {
without_underscore_and_at
} else {
field_name
}
return utils.snake_case_to_camel_case(name_to_camelize)
}
================================================
FILE: src/server/completion/providers/KeywordsCompletionProvider.v
================================================
module providers
import server.completion
import lsp
pub struct KeywordsCompletionProvider {}
fn (k &KeywordsCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.expression()
}
fn (mut k KeywordsCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
k.no_space_keywords([
'none',
'true',
'false',
'static',
], mut result)
}
fn (mut k KeywordsCompletionProvider) no_space_keywords(keywords []string, mut result completion.CompletionResultSet) {
for keyword in keywords {
result.add_element(lsp.CompletionItem{
label: keyword
kind: .keyword
insert_text: keyword
})
}
}
================================================
FILE: src/server/completion/providers/LoopKeywordsCompletionProvider.v
================================================
module providers
import server.completion
import lsp
pub struct LoopKeywordsCompletionProvider {}
fn (k &LoopKeywordsCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.inside_loop && !ctx.after_dot && !ctx.after_at
}
fn (mut k LoopKeywordsCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
result.add_element(lsp.CompletionItem{
label: 'break'
kind: .keyword
insert_text: 'break'
})
result.add_element(lsp.CompletionItem{
label: 'continue'
kind: .keyword
insert_text: 'continue'
})
}
================================================
FILE: src/server/completion/providers/ModuleNameCompletionProvider.v
================================================
module providers
import server.completion
import lsp
import os
pub struct ModuleNameCompletionProvider {}
fn (_ &ModuleNameCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
file := ctx.element.containing_file() or { return false }
no_module_clause := if _ := file.module_name() {
false
} else {
true
}
return ctx.is_start_of_file && ctx.is_top_level && no_module_clause
}
fn (mut p ModuleNameCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
file := ctx.element.containing_file() or { return }
dir := os.dir(file.path)
dir_name := p.transform_module_name(os.file_name(dir))
result.add_element(lsp.CompletionItem{
label: 'module ${dir_name}'
kind: .keyword
insert_text_format: .snippet
insert_text: 'module ${dir_name}'
})
result.add_element(lsp.CompletionItem{
label: 'module main'
kind: .keyword
insert_text: 'module main'
})
}
fn (mut _ ModuleNameCompletionProvider) transform_module_name(raw_name string) string {
return raw_name
.replace('-', '_')
.replace(' ', '_')
.to_lower()
}
================================================
FILE: src/server/completion/providers/ModulesImportProvider.v
================================================
module providers
import analyzer.psi
import server.completion
import lsp
pub struct ModulesImportProvider {}
fn (m &ModulesImportProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.is_import_name
}
fn (mut m ModulesImportProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
element := ctx.element
parent_path := element.parent_nth(2) or { return }
before_path := parent_path.get_text().trim_string_right(completion.dummy_identifier)
modules := psi.get_all_modules()
for module_ in modules {
if module_ == 'main' {
continue
}
if !module_.starts_with(before_path) {
continue
}
name_without_prefix := module_.trim_string_left(before_path)
result.add_element(lsp.CompletionItem{
label: name_without_prefix
kind: .module_
detail: ''
documentation: ''
insert_text: name_without_prefix
insert_text_format: .plain_text
})
}
}
================================================
FILE: src/server/completion/providers/NilKeywordCompletionProvider.v
================================================
module providers
import server.completion
import lsp
pub struct NilKeywordCompletionProvider {}
fn (k &NilKeywordCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.expression()
}
fn (mut k NilKeywordCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
inside_unsafe := ctx.element.inside(.unsafe_expression)
insert_text := if !inside_unsafe {
'unsafe { nil }'
} else {
'nil'
}
result.add_element(lsp.CompletionItem{
label: 'nil'
kind: .keyword
insert_text: insert_text
})
}
================================================
FILE: src/server/completion/providers/OrBlockExpressionCompletionProvider.v
================================================
module providers
import server.completion
import lsp
pub struct OrBlockExpressionCompletionProvider {}
fn (k &OrBlockExpressionCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.expression()
}
fn (mut k OrBlockExpressionCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
result.add_element(lsp.CompletionItem{
label: 'or { ... }'
kind: .keyword
insert_text: 'or { $0 }'
insert_text_format: .snippet
})
result.add_element(lsp.CompletionItem{
label: 'or { panic(err) }'
kind: .keyword
insert_text: 'or { panic(err) }'
})
}
================================================
FILE: src/server/completion/providers/PureBlockExpressionCompletionProvider.v
================================================
module providers
import server.completion
import lsp
pub struct PureBlockExpressionCompletionProvider {}
fn (k &PureBlockExpressionCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.expression()
}
fn (mut k PureBlockExpressionCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
one_line := if parent := ctx.element.parent_nth(2) {
if parent.node().type_name == .simple_statement {
false
} else {
true
}
} else {
true
}
insert_text := if one_line {
'unsafe { $0 }'
} else {
'unsafe {\n\t$0\n}'
}
result.add_element(lsp.CompletionItem{
label: 'unsafe { ... }'
kind: .keyword
insert_text: insert_text
insert_text_format: .snippet
insert_text_mode: .adjust_indentation
})
}
================================================
FILE: src/server/completion/providers/PureBlockStatementCompletionProvider.v
================================================
module providers
import server.completion
import lsp
pub struct PureBlockStatementCompletionProvider {}
fn (k &PureBlockStatementCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.is_statement && !ctx.after_dot && !ctx.after_at
}
fn (mut k PureBlockStatementCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
result.add_element(lsp.CompletionItem{
label: 'defer { ... }'
kind: .keyword
insert_text: 'defer {\n\t$0\n}'
insert_text_format: .snippet
insert_text_mode: .adjust_indentation
})
}
================================================
FILE: src/server/completion/providers/ReferenceCompletionProcessor.v
================================================
module providers
import lsp
import strings
import analyzer.psi
import analyzer.lang
import server.completion
pub struct ReferenceCompletionProcessor {
pub:
file &psi.PsiFile
module_fqn string
root string
ctx &completion.CompletionContext
mut:
result map[string]lsp.CompletionItem
}
pub fn (mut c ReferenceCompletionProcessor) elements() []lsp.CompletionItem {
return c.result.values()
}
fn (mut c ReferenceCompletionProcessor) is_local_resolve(element psi.PsiElement) bool {
file := element.containing_file() or { return false }
element_module_fqn := file.module_fqn()
equal := c.module_fqn == element_module_fqn
if equal && c.module_fqn == 'main' {
// We check that the module matches, but if it is main, then we need to check
// that the file is in the workspace.
return file.path.starts_with(c.root)
}
return equal
}
fn (mut c ReferenceCompletionProcessor) execute(element psi.PsiElement) bool {
is_public, name := if element is psi.PsiNamedElement {
element.is_public(), element.name()
} else {
true, ''
}
local_resolve := c.is_local_resolve(element)
if !is_public && !local_resolve {
return true
}
if element is psi.VarDefinition {
c.add_item(
label: name
kind: .variable
detail: element.get_type().readable_name()
label_details: lsp.CompletionItemLabelDetails{
description: element.get_type().readable_name()
}
documentation: ''
insert_text: name
insert_text_format: .plain_text
sort_text: '0${name}' // variables should go first
)
}
if element is psi.ParameterDeclaration {
c.add_item(
label: name
kind: .variable
detail: element.get_type().readable_name()
label_details: lsp.CompletionItemLabelDetails{
description: element.get_type().readable_name()
}
documentation: ''
insert_text: name
insert_text_format: .plain_text
sort_text: '0${name}' // parameters should go first
)
}
if element is psi.Receiver {
c.add_item(
label: element.name()
kind: .variable
detail: element.get_type().readable_name()
label_details: lsp.CompletionItemLabelDetails{
description: element.get_type().readable_name()
}
documentation: ''
insert_text: element.name()
insert_text_format: .plain_text
)
}
if element is psi.FunctionOrMethodDeclaration {
receiver_text := if receiver := element.receiver() {
receiver.get_text() + ' '
} else {
''
}
mut insert_name := element.name()
if name.starts_with('$') {
insert_name = insert_name[1..]
}
signature := element.signature() or { return true }
has_params := signature.parameters().len > 0
generic_parameters_text := if generic_parameters := element.generic_parameters() {
generic_parameters.text_presentation()
} else {
''
}
text_ranga := c.ctx.element.text_range()
paren_after_cursor := if ctx_file := c.ctx.element.containing_file() {
sym := ctx_file.symbol_at(psi.TextRange{
line: text_ranga.line
column: text_ranga.end_column + 1
})
sym == `(`
} else {
false
}
mut insert_text_builder := strings.new_builder(20)
insert_text_builder.write_string(insert_name)
// we don't want add extra parentheses if the cursor is before the parentheses
// It happens when user replaces the function name in the call, for example:
// 1. foo()
// 2. remove foo, type bar and autocomplete
// 3. bar()
// without this check we would get bar()()
if !paren_after_cursor {
if has_params {
insert_text_builder.write_string('($1)')
} else {
insert_text_builder.write_string('()')
}
}
insert_text_builder.write_string('$0')
c.add_item(
label: '${name}'
kind: if receiver_text == '' { .function } else { .method }
label_details: lsp.CompletionItemLabelDetails{
detail: signature.get_text()
}
detail: 'fn ${receiver_text}${element.name()}${generic_parameters_text}${signature.get_text()}'
documentation: element.doc_comment()
insert_text: insert_text_builder.str()
insert_text_format: .snippet
sort_text: '1${name}' // functions should go second
)
}
if element is psi.StaticMethodDeclaration {
receiver_text := if receiver := element.receiver() {
receiver.get_text() + ' '
} else {
''
}
mut insert_name := element.name()
if name.starts_with('$') {
insert_name = insert_name[1..]
}
signature := element.signature() or { return true }
has_params := signature.parameters().len > 0
generic_parameters_text := if generic_parameters := element.generic_parameters() {
generic_parameters.text_presentation()
} else {
''
}
text_ranga := c.ctx.element.text_range()
paren_after_cursor := if ctx_file := c.ctx.element.containing_file() {
sym := ctx_file.symbol_at(psi.TextRange{
line: text_ranga.line
column: text_ranga.end_column + 1
})
sym == `{`
} else {
false
}
mut insert_text_builder := strings.new_builder(20)
insert_text_builder.write_string(insert_name)
// we don't want add extra parentheses if the cursor is before the parentheses
// It happens when user replaces the function name in the call, for example:
// 1. foo()
// 2. remove foo, type bar and autocomplete
// 3. bar()
// without this check we would get bar()()
if !paren_after_cursor {
if has_params {
insert_text_builder.write_string('($1)')
} else {
insert_text_builder.write_string('()')
}
}
insert_text_builder.write_string('$0')
c.add_item(
label: '${name}'
kind: .method
label_details: lsp.CompletionItemLabelDetails{
detail: signature.get_text()
}
detail: 'fn ${receiver_text}${element.name()}${generic_parameters_text}${signature.get_text()}'
documentation: element.doc_comment()
insert_text: insert_text_builder.str()
insert_text_format: .snippet
sort_text: '1${name}' // functions should go second
)
}
if element is psi.StructDeclaration {
if name == 'map' || name == 'array' {
// it makes no sense to create these structures directly
return true
}
text_ranga := c.ctx.element.text_range()
paren_after_cursor := if ctx_file := c.ctx.element.containing_file() {
sym := ctx_file.symbol_at(psi.TextRange{
line: text_ranga.line
column: text_ranga.end_column + 1
})
sym == `{`
} else {
false
}
insert_text := if c.ctx.is_type_reference || paren_after_cursor {
name // if it is a reference to a type, then insert only the name
} else {
name + '{$1}$0'
}
c.add_item(
label: name
kind: .struct_
detail: ''
documentation: element.doc_comment()
insert_text: insert_text
insert_text_format: .snippet
)
}
if element is psi.ConstantDefinition {
c.add_item(
label: element.name()
kind: .constant
detail: element.get_type().readable_name()
label_details: lsp.CompletionItemLabelDetails{
description: element.get_type().readable_name()
}
documentation: element.doc_comment()
insert_text: element.name()
insert_text_format: .plain_text
)
}
if element is psi.FieldDeclaration {
zero_value := lang.get_zero_value_for(element.get_type()).replace('}', '\\}')
insert_text := if c.ctx.inside_struct_init_with_keys {
element.name() + ': \${1:${zero_value}}'
} else {
element.name()
}
c.add_item(
label: element.name()
kind: .field
detail: element.get_type().readable_name()
label_details: lsp.CompletionItemLabelDetails{
description: element.get_type().readable_name()
}
documentation: element.doc_comment()
insert_text: insert_text
insert_text_format: .snippet
)
}
if element is psi.InterfaceMethodDeclaration {
signature := element.signature() or { return true }
has_params := signature.parameters().len > 0
mut insert_text_builder := strings.new_builder(20)
insert_text_builder.write_string(element.name())
if has_params {
insert_text_builder.write_string('($1)')
} else {
insert_text_builder.write_string('()')
}
owner_name := if owner := element.owner() {
' of ${owner.name()}'
} else {
''
}
c.add_item(
label: element.name()
kind: .method
detail: 'fn ${element.name()}${signature.get_text()}'
label_details: lsp.CompletionItemLabelDetails{
detail: signature.get_text()
description: owner_name
}
documentation: element.doc_comment()
insert_text: insert_text_builder.str()
insert_text_format: .snippet
)
}
if element is psi.EnumDeclaration {
c.add_item(
label: element.name()
kind: .enum_
detail: ''
documentation: element.doc_comment()
insert_text: element.name()
insert_text_format: .plain_text
)
}
if element is psi.EnumFieldDeclaration {
c.add_item(
label: element.name()
kind: .enum_member
detail: ''
documentation: element.doc_comment()
insert_text: element.name()
insert_text_format: .plain_text
)
}
if element is psi.GenericParameter {
c.add_item(
label: element.name()
kind: .type_parameter
)
}
if element is psi.GlobalVarDefinition {
file := element.containing_file()
module_name := if file != none { file.module_fqn() } else { '' }
c.add_item(
label: element.name()
label_details: lsp.CompletionItemLabelDetails{
detail: ' (global defined in ${module_name})'
}
kind: .variable
insert_text: element.name()
)
}
return true
}
fn (mut c ReferenceCompletionProcessor) add_item(item lsp.CompletionItem) {
if item.label in c.result {
return
}
c.result[item.label] = item
}
================================================
FILE: src/server/completion/providers/ReferenceCompletionProvider.v
================================================
module providers
import analyzer.psi
import analyzer.lang
import server.completion
import analyzer.psi.types
pub struct ReferenceCompletionProvider {
pub mut:
processor &ReferenceCompletionProcessor
}
fn (_ &ReferenceCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.is_expression || ctx.is_type_reference
}
fn (mut r ReferenceCompletionProvider) add_completion(ctx &completion.CompletionContext, mut _ completion.CompletionResultSet) {
element := ctx.element
text := element.get_text()
if text.starts_with('@') {
// See `CompileTimeConstantCompletionProvider`
return
}
parent := element.parent() or { return }
containing_file := parent.containing_file()
if parent is psi.ReferenceExpressionBase {
sub := psi.SubResolver{
containing_file: containing_file
element: parent
for_types: parent is psi.TypeReferenceExpression
}
variants := StructLiteralCompletion{}.allowed_variants(ctx, parent)
field_initializers := StructLiteralCompletion{}.get_field_initializers(element)
already_assigned := StructLiteralCompletion{}.already_assigned_fields(field_initializers)
if variants != .none_ {
r.process_fields(ctx, parent as psi.PsiElement, already_assigned)
}
if variants != .field_name_only {
sub.process_resolve_variants(mut r.processor)
}
}
}
fn (mut r ReferenceCompletionProvider) process_fields(ctx &completion.CompletionContext, element psi.PsiElement,
already_assigned []string) {
grand := element.parent() or { return }
if grand.node().type_name != .element_list {
return
}
type_initializer := grand.parent_nth(2) or { return }
if type_initializer is psi.TypeInitializer {
typ := type_initializer.get_type()
qualified_name := if typ is types.ArrayType {
'stubs.ArrayInit'
} else if typ is types.ChannelType {
'stubs.ChanInit'
} else {
typ.qualified_name()
}
if interface_ := psi.find_interface(qualified_name) {
for field in interface_.fields() {
if field is psi.FieldDeclaration {
if !field.is_public() && !lang.is_same_module(ctx.element, *field) {
continue
}
if field.name() in already_assigned {
continue
}
r.processor.execute(field)
}
}
}
if struct_ := psi.find_struct(qualified_name) {
for field in struct_.fields() {
if field is psi.FieldDeclaration {
if !field.is_public() && !lang.is_same_module(ctx.element, *field) {
continue
}
if field.name() in already_assigned {
continue
}
r.processor.execute(field)
}
}
}
}
}
================================================
FILE: src/server/completion/providers/ReturnCompletionProvider.v
================================================
module providers
import analyzer.psi
import server.completion
import analyzer.psi.types
import analyzer.lang
import lsp
pub struct ReturnCompletionProvider {}
fn (_ &ReturnCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.is_statement && !ctx.after_at
}
fn (mut _ ReturnCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
element := ctx.element
parent_function := element.parent_of_any_type(.function_declaration, .function_literal) or {
return
}
signature := if parent_function is psi.SignatureOwner {
parent_function.signature() or { return }
} else {
return
}
function_type := signature.get_type()
if function_type is types.FunctionType {
has_result_type := !function_type.no_result
result_type := function_type.result
if result_type is types.PrimitiveType {
if result_type.name == 'bool' {
result.add_element(lsp.CompletionItem{
label: 'return true'
kind: .text
})
result.add_element(lsp.CompletionItem{
label: 'return false'
kind: .text
})
}
}
if has_result_type {
zero_value := lang.get_zero_value_for(result_type)
result.add_element(lsp.CompletionItem{
label: 'return ${zero_value}'
kind: .text
})
}
label := if !has_result_type {
'return'
} else {
'return '
}
result.add_element(lsp.CompletionItem{
label: label
kind: .text
})
}
}
================================================
FILE: src/server/completion/providers/StructLiteralCompletion.v
================================================
module providers
import analyzer.psi
import server.completion
import analyzer.psi.types
// Describes struct literal completion variants that should be suggested.
pub enum Variants {
// Only struct field names should be suggested.
// Indicates that field:value initializers are used in this struct literal.
// For example, `Struct{field1: "", caret}`.
field_name_only
// Only values should be suggested.
// Indicates that value initializers are used in this struct literal.
// For example, `Struct{"", caret}`.
value_only
// Both struct field names and values should be suggested.
// Indicates that there's no reliable way to determine whether field:value or value initializers are used.
// Example 1: `Struct{caret}`.
// Example 2: `Struct{field1:"", "", caret}`
both
// Indicates that struct literal completion should not be available.
none_
}
pub struct StructLiteralCompletion {}
pub fn (s &StructLiteralCompletion) allowed_variants(ctx &completion.CompletionContext, ref psi.ReferenceExpressionBase) Variants {
if ctx.after_dot {
return .none_
}
element := ref as psi.PsiElement
mut parent := element.parent() or { return .none_ }
for parent is psi.UnaryExpression {
parent = parent.parent() or { return .none_ }
}
if parent.node().type_name != .element_list {
return .none_
}
if type_initializer := parent.parent_nth(2) {
if type_initializer is psi.TypeInitializer {
typ := type_initializer.get_type()
if typ is types.ArrayType {
// for []int{}, allow only fields
return .field_name_only
}
if typ is types.ChannelType {
// for chan int{}, allow only fields
return .field_name_only
}
}
}
field_initializers := s.get_field_initializers(element)
mut has_value_initializers := false
mut has_field_value_initializers := false
for initializer in field_initializers {
if initializer.is_equal(element) {
continue
}
key_value := initializer.node().type_name == .keyed_element
has_field_value_initializers = has_field_value_initializers || key_value
has_value_initializers = has_value_initializers || !key_value
}
return if has_field_value_initializers && !has_value_initializers {
.field_name_only
} else if !has_field_value_initializers && has_value_initializers {
.value_only
} else {
.both
}
}
pub fn (s &StructLiteralCompletion) already_assigned_fields(elements []psi.PsiElement) []string {
mut res := []string{cap: elements.len}
for element in elements {
if element.node().type_name == .keyed_element {
if key := element.first_child() {
res << key.get_text()
}
}
}
return res
}
pub fn (s &StructLiteralCompletion) get_field_initializers(element psi.PsiElement) []psi.PsiElement {
if type_initializer := element.parent_of_type(.type_initializer) {
if type_initializer is psi.TypeInitializer {
return type_initializer.element_list()
}
}
return []
}
================================================
FILE: src/server/completion/providers/TopLevelCompletionProvider.v
================================================
module providers
import server.completion
import lsp
struct TopLevelVariant {
search_text string
insert_text string
}
pub const top_level_map = {
'fn name() { ... }': TopLevelVariant{'fn', 'fn \${1:name}($2) {\n\t$0\n}'}
'struct Name { ... }': TopLevelVariant{'struct', 'struct \${1:Name} {\n\t$0\n}'}
'interface IName { ... }': TopLevelVariant{'interface', 'interface \${1:IName} {\n\t$0\n}'}
'enum Colors { ... }': TopLevelVariant{'enum', 'enum \${1:Colors} {\n\t$0\n}'}
'type MyString = string': TopLevelVariant{'type', 'type \${1:MyString} = \${2:string}$0'}
'const secret = 100': TopLevelVariant{'const', 'const \${1:secret} = \${2:100}$0'}
}
pub struct TopLevelCompletionProvider {}
fn (k &TopLevelCompletionProvider) is_available(ctx &completion.CompletionContext) bool {
return ctx.is_top_level
}
fn (mut k TopLevelCompletionProvider) add_completion(ctx &completion.CompletionContext, mut result completion.CompletionResultSet) {
k.pub_keyword(mut result)
}
fn (mut k TopLevelCompletionProvider) pub_keyword(mut result completion.CompletionResultSet) {
for label, variant in top_level_map {
result.add_element(lsp.CompletionItem{
label: label
kind: .keyword
filter_text: variant.search_text
insert_text: variant.insert_text
insert_text_format: .snippet
})
}
for label, variant in top_level_map {
result.add_element(lsp.CompletionItem{
label: 'pub ${label}'
kind: .keyword
filter_text: 'pub ${variant.search_text}'
insert_text: 'pub ${variant.insert_text}'
insert_text_format: .snippet
})
}
result.add_element(lsp.CompletionItem{
label: 'import'
kind: .keyword
insert_text: 'import '
})
}
================================================
FILE: src/server/diagnostics.v
================================================
module server
import lsp
import time
import loglib
import server.tform
import server.inspections
import server.inspections.compiler
pub fn (mut ls LanguageServer) run_diagnostics_in_bg(uri lsp.DocumentUri) {
ls.bg.queue(fn [mut ls, uri] () {
ls.run_diagnostics(uri)
})
}
pub fn (mut ls LanguageServer) run_diagnostics(uri lsp.DocumentUri) {
watch := time.new_stopwatch(auto_start: true)
project_root := ls.project_resolver.resolve(uri)
ls.reporter.clear(uri)
ls.reporter.run_all_inspections(uri, project_root)
ls.reporter.publish(mut ls.writer, uri)
loglib.with_fields({
'caller': @METHOD
'duration': watch.elapsed().str()
}).info('Updated diagnostics')
}
pub struct DiagnosticReporter {
mut:
compiler_path string
reports map[lsp.DocumentUri][]inspections.Report
}
fn (mut d DiagnosticReporter) run_all_inspections(uri lsp.DocumentUri, project_root string) {
mut source := compiler.CompilerReportsSource{
compiler_path: d.compiler_path
}
d.reports[uri] = source.process(uri, project_root)
}
fn (mut d DiagnosticReporter) clear(uri lsp.DocumentUri) {
d.reports[uri] = []
}
fn (mut d DiagnosticReporter) publish(mut wr ResponseWriter, uri lsp.DocumentUri) {
reports := d.reports[uri] or { return }
wr.publish_diagnostics(lsp.PublishDiagnosticsParams{
uri: uri
diagnostics: reports.map(d.convert_report(it))
})
}
fn (_ &DiagnosticReporter) convert_report(report inspections.Report) lsp.Diagnostic {
possibly_unused := report.message.starts_with('unused')
possibly_deprecated := report.message.contains('deprecated')
mut tags := []lsp.DiagnosticTag{}
if possibly_unused {
tags << lsp.DiagnosticTag.unnecessary
}
if possibly_deprecated {
tags << lsp.DiagnosticTag.deprecated
}
return lsp.Diagnostic{
range: tform.text_range_to_lsp_range(report.range)
severity: match report.kind {
.error { lsp.DiagnosticSeverity.error }
.warning { lsp.DiagnosticSeverity.warning }
.notice { lsp.DiagnosticSeverity.information }
}
source: 'compiler'
message: report.message
tags: tags
}
}
// publish_diagnostics sends errors, warnings and other diagnostics to the editor
fn (mut wr ResponseWriter) publish_diagnostics(params lsp.PublishDiagnosticsParams) {
wr.write_notify('textDocument/publishDiagnostics', params)
}
================================================
FILE: src/server/documentation/keywords_provider.v
================================================
module documentation
import strings
import analyzer.psi
import analyzer.psi.types
// KeywordProvider unlike `Provider` creates documentation not for named elements i
// but for language keywords.
// This documentation is used to educate users of the language and as an entry point
// to the language documentation.
pub struct KeywordProvider {
mut:
sb strings.Builder = strings.new_builder(100)
}
pub fn (mut p KeywordProvider) documentation(element psi.PsiElement) ?string {
text := element.get_text()
if text == 'chan' {
p.chan_documentation()
return p.sb.str()
}
return none
}
fn (mut p KeywordProvider) chan_documentation() ? {
struct_ := psi.find_struct(types.chan_init_type.qualified_name())?
doc := struct_.doc_comment()
p.sb.write_string(doc)
}
================================================
FILE: src/server/documentation/provider.v
================================================
module documentation
import analyzer.psi
import strings
import os
import loglib
pub struct Provider {
mut:
sb strings.Builder = strings.new_builder(100)
}
pub fn (mut p Provider) documentation(element psi.PsiElement) ?string {
if element is psi.ModuleClause {
p.module_documentation(element)?
return p.sb.str()
}
if element is psi.FunctionOrMethodDeclaration {
p.function_documentation(element)?
return p.sb.str()
}
if element is psi.StaticMethodDeclaration {
p.static_method_documentation(element)?
return p.sb.str()
}
if element is psi.StructDeclaration {
p.struct_documentation(element)?
return p.sb.str()
}
if element is psi.InterfaceDeclaration {
p.interface_documentation(element)?
return p.sb.str()
}
if element is psi.InterfaceMethodDeclaration {
p.interface_method_declaration_documentation(element)?
return p.sb.str()
}
if element is psi.EnumDeclaration {
p.enum_documentation(element)?
return p.sb.str()
}
if element is psi.ConstantDefinition {
p.const_documentation(element)?
return p.sb.str()
}
if element is psi.VarDefinition {
p.variable_documentation(element)?
return p.sb.str()
}
if element is psi.GlobalVarDefinition {
p.global_variable_documentation(element)?
return p.sb.str()
}
if element is psi.ParameterDeclaration {
p.parameter_documentation(element)?
return p.sb.str()
}
if element is psi.Receiver {
p.receiver_documentation(element)?
return p.sb.str()
}
if element is psi.FieldDeclaration {
p.field_documentation(element)?
return p.sb.str()
}
if element is psi.EmbeddedDefinition {
p.embedded_definition_documentation(element)?
return p.sb.str()
}
if element is psi.EnumFieldDeclaration {
p.enum_field_documentation(element)?
return p.sb.str()
}
if element is psi.TypeAliasDeclaration {
p.type_alias_documentation(element)?
return p.sb.str()
}
if element is psi.ImportSpec {
p.import_spec_documentation(element)?
return p.sb.str()
}
if element is psi.GenericParameter {
p.generic_parameter_documentation(element)?
return p.sb.str()
}
return none
}
fn (mut p Provider) import_spec_documentation(element psi.ImportSpec) ? {
module_fqn := element.qualified_name()
p.sb.write_string('```v\n')
p.sb.write_string('module ${module_fqn}')
p.sb.write_string('\n')
p.sb.write_string('```')
dir := element.resolve_directory()
mut readme_path := os.join_path(dir, 'README.md')
if !os.exists(readme_path) && os.file_name(dir) == 'src' {
readme_path = os.join_path(os.dir(dir), 'README.md')
}
if os.exists(readme_path) {
p.write_separator()
mut content := os.read_file(readme_path) or { return }
mut lines := content.split_into_lines()
if lines.len > 0 && lines.first().contains('Description') {
lines = lines[1..].clone()
content = lines.join('\n')
}
content = content.replace('# ', '### ')
p.sb.write_string(content)
}
p.write_separator()
p.sb.write_string('---\n')
p.sb.write_string('```${dir}```\n')
}
fn (mut p Provider) module_documentation(element psi.ModuleClause) ? {
p.sb.write_string('```v\n')
p.sb.write_string('module ')
p.sb.write_string(element.name())
p.sb.write_string('\n')
p.sb.write_string('```')
}
fn (mut p Provider) function_documentation(element psi.FunctionOrMethodDeclaration) ? {
p.write_module_name(element.containing_file)
signature := element.signature()?
p.sb.write_string('```v\n')
if modifiers := element.visibility_modifiers() {
p.write_visibility_modifiers(modifiers)
p.sb.write_string(' ')
}
p.sb.write_string('fn ')
if receiver := element.receiver() {
p.sb.write_string(receiver.get_text())
p.sb.write_string(' ')
}
p.sb.write_string(element.name())
p.write_generic_parameters(element)
p.write_signature(signature)
p.sb.write_string('\n')
p.sb.write_string('```')
p.write_separator()
p.sb.write_string(element.doc_comment())
}
fn (mut p Provider) static_method_documentation(element psi.StaticMethodDeclaration) ? {
p.write_module_name(element.containing_file)
signature := element.signature()?
p.sb.write_string('```v\n')
if modifiers := element.visibility_modifiers() {
p.write_visibility_modifiers(modifiers)
p.sb.write_string(' ')
}
p.sb.write_string('fn ')
if receiver := element.receiver() {
p.sb.write_string(receiver.get_text())
p.sb.write_string('.')
}
p.sb.write_string(element.name())
p.write_generic_parameters(element)
p.write_signature(signature)
p.sb.write_string('\n')
p.sb.write_string('```')
p.write_separator()
p.sb.write_string(element.doc_comment())
}
fn (mut p Provider) struct_documentation(element psi.StructDeclaration) ? {
p.write_module_name(element.containing_file)
p.sb.write_string('```v\n')
if modifiers := element.visibility_modifiers() {
p.write_visibility_modifiers(modifiers)
p.sb.write_string(' ')
}
p.sb.write_string('struct ')
p.sb.write_string(element.name())
p.write_generic_parameters(element)
p.sb.write_string('\n')
p.sb.write_string('```')
p.write_separator()
p.sb.write_string(element.doc_comment())
}
fn (mut p Provider) interface_documentation(element psi.InterfaceDeclaration) ? {
p.write_module_name(element.containing_file)
p.sb.write_string('```v\n')
if modifiers := element.visibility_modifiers() {
p.write_visibility_modifiers(modifiers)
p.sb.write_string(' ')
}
p.sb.write_string('interface ')
p.sb.write_string(element.name())
p.write_generic_parameters(element)
p.sb.write_string('\n')
p.sb.write_string('```')
p.write_separator()
p.sb.write_string(element.doc_comment())
}
fn (mut p Provider) interface_method_declaration_documentation(element psi.InterfaceMethodDeclaration) ? {
p.write_module_name(element.containing_file)
signature := element.signature()?
p.sb.write_string('```v\n')
p.sb.write_string('pub fn ')
if owner := element.owner() {
p.sb.write_string(owner.name())
p.sb.write_string('.')
}
p.sb.write_string(element.name())
p.write_signature(signature)
p.sb.write_string('\n')
p.sb.write_string('```')
p.write_separator()
p.sb.write_string(element.doc_comment())
}
fn (mut p Provider) enum_documentation(element psi.EnumDeclaration) ? {
p.write_module_name(element.containing_file)
p.sb.write_string('```v\n')
if modifiers := element.visibility_modifiers() {
p.write_visibility_modifiers(modifiers)
p.sb.write_string(' ')
}
p.sb.write_string('enum ')
p.sb.write_string(element.name())
p.sb.write_string('\n')
p.sb.write_string('```')
p.write_separator()
p.sb.write_string(element.doc_comment())
}
fn (mut p Provider) const_documentation(element psi.ConstantDefinition) ? {
p.write_module_name(element.containing_file)
p.sb.write_string('```v\n')
if modifiers := element.visibility_modifiers() {
p.write_visibility_modifiers(modifiers)
p.sb.write_string(' ')
}
p.sb.write_string('const ')
p.sb.write_string(element.name())
p.sb.write_string(' ')
p.sb.write_string(element.get_type().readable_name())
p.sb.write_string(' = ')
if value := element.expression() {
p.sb.write_string(value.get_text())
if element.stub_based() {
if mut file := value.containing_file() {
file.free()
}
}
}
p.sb.write_string('\n')
p.sb.write_string('```')
p.write_separator()
p.sb.write_string(element.doc_comment())
}
fn (mut p Provider) variable_documentation(element psi.VarDefinition) ? {
p.sb.write_string('Local **variable**\n')
p.sb.write_string('```v\n')
if modifiers := element.mutability_modifiers() {
p.write_mutability_modifiers(modifiers)
p.sb.write_string(' ')
}
p.sb.write_string(element.name())
p.sb.write_string(' ')
p.sb.write_string(element.get_type().readable_name())
p.sb.write_string('\n')
p.sb.write_string('```')
}
fn (mut p Provider) global_variable_documentation(element psi.GlobalVarDefinition) ? {
p.sb.write_string('Global **variable**\n')
p.sb.write_string('```v\n')
p.sb.write_string('__global ')
p.sb.write_string(element.name())
p.sb.write_string(' ')
p.sb.write_string(element.get_type().readable_name())
p.sb.write_string('\n')
p.sb.write_string('```')
}
fn (mut p Provider) parameter_documentation(element psi.ParameterDeclaration) ? {
p.sb.write_string('Function **parameter**\n')
p.sb.write_string('```v\n')
if modifiers := element.mutability_modifiers() {
p.write_mutability_modifiers(modifiers)
p.sb.write_string(' ')
}
p.sb.write_string(element.name())
p.sb.write_string(' ')
p.sb.write_string(element.get_type().readable_name())
p.sb.write_string('\n')
p.sb.write_string('```')
}
fn (mut p Provider) field_documentation(element psi.FieldDeclaration) ? {
p.sb.write_string('```v\n')
is_mut, is_pub := element.is_mutable_public()
if is_pub {
p.sb.write_string('pub ')
}
if is_mut {
p.sb.write_string('mut ')
}
if owner := element.owner() {
if owner is psi.PsiNamedElement {
p.sb.write_string(owner.name())
p.sb.write_string('.')
}
}
p.sb.write_string(element.name())
p.sb.write_string(' ')
p.sb.write_string(element.get_type().readable_name())
p.sb.write_string('\n')
p.sb.write_string('```')
p.write_separator()
p.sb.write_string(element.doc_comment())
}
fn (mut p Provider) embedded_definition_documentation(element psi.EmbeddedDefinition) ? {
p.sb.write_string('```v\n')
p.sb.write_string('embedded ')
if owner := element.owner() {
if owner is psi.PsiNamedElement {
p.sb.write_string(owner.name())
p.sb.write_string('.')
}
}
p.sb.write_string(element.name())
p.sb.write_string('\n')
p.sb.write_string('```')
}
fn (mut p Provider) enum_field_documentation(element psi.EnumFieldDeclaration) ? {
p.sb.write_string('```v\n')
p.sb.write_string('enum ')
if owner := element.owner() {
p.sb.write_string(owner.name())
p.sb.write_string('.')
}
p.sb.write_string(element.name())
p.sb.write_string(' = ')
p.sb.write_string(element.value_presentation(true))
p.sb.write_string('\n')
p.sb.write_string('```')
p.write_separator()
p.sb.write_string(element.doc_comment())
}
fn (mut p Provider) receiver_documentation(element psi.Receiver) ? {
p.sb.write_string('Method **receiver**\n')
p.sb.write_string('```v\n')
if modifiers := element.mutability_modifiers() {
p.write_mutability_modifiers(modifiers)
p.sb.write_string(' ')
}
p.sb.write_string(element.name())
p.sb.write_string(' ')
p.sb.write_string(element.get_type().readable_name())
p.sb.write_string('\n')
p.sb.write_string('```')
}
fn (mut p Provider) type_alias_documentation(element psi.TypeAliasDeclaration) ? {
p.write_module_name(element.containing_file)
p.sb.write_string('```v\n')
if modifiers := element.visibility_modifiers() {
p.write_visibility_modifiers(modifiers)
p.sb.write_string(' ')
}
p.sb.write_string('type ')
p.sb.write_string(element.name())
p.write_generic_parameters(element)
p.sb.write_string(' = ')
for index, type_ in element.types() {
p.sb.write_string(psi.convert_type(type_).readable_name())
if index < element.types().len - 1 {
p.sb.write_string(' | ')
}
}
p.sb.write_string('\n')
p.sb.write_string('```')
p.write_separator()
p.sb.write_string(element.doc_comment())
}
fn (mut p Provider) generic_parameter_documentation(element psi.GenericParameter) ? {
p.write_module_name(element.containing_file)
p.sb.write_string('```v\n')
p.sb.write_string('generic parameter ')
p.sb.write_string(element.name())
p.sb.write_string('\n')
p.sb.write_string('```')
}
fn (mut p Provider) write_separator() {
p.sb.write_string('\n\n')
}
fn (mut p Provider) write_signature(signature psi.Signature) {
p.sb.write_string(signature.get_text())
}
fn (mut p Provider) write_mutability_modifiers(modifiers psi.MutabilityModifiers) {
p.sb.write_string(modifiers.get_text())
}
fn (mut p Provider) write_visibility_modifiers(modifiers psi.VisibilityModifiers) {
p.sb.write_string(modifiers.get_text())
}
fn (mut p Provider) write_module_name(file ?&psi.PsiFile) {
f := file or { return }
fqn := f.module_fqn()
name := if fqn.len == 0 {
f.module_name() or { '' }
} else {
fqn
}
if name != '' {
p.sb.write_string('Module: **${name}**\n')
}
}
fn (mut p Provider) write_generic_parameters(element psi.GenericParametersOwner) {
parameters := element.generic_parameters() or { return }
p.sb.write_string(parameters.text_presentation())
}
fn (mut p Provider) write_attributes(element psi.PsiElement) {
attributes := element.find_child_by_type_or_stub(.attributes) or { return }
attribute_list := attributes.find_children_by_type_or_stub(.attribute)
if attribute_list.len == 0 {
return
}
for attr in attribute_list {
p.sb.write_string(attr.get_text())
p.sb.write_string('\n')
}
}
pub fn (mut p Provider) find_documentation_element(element psi.PsiElement) ?psi.PsiElement {
if element is psi.Identifier {
parent := element.parent()?
if parent is psi.ReferenceExpressionBase {
return parent.resolve() or {
loglib.with_fields({
'name': element.get_text()
}).log(.warn, 'Cannot resolve reference for documentation')
return element
}
}
if parent is psi.PsiNamedElement {
return parent as psi.PsiElement
}
if parent is psi.ImportName {
if import_spec := parent.parent_nth(2) {
if import_spec is psi.ImportSpec {
return import_spec
}
}
}
if parent is psi.ImportAlias {
if import_spec := parent.parent() {
if import_spec is psi.ImportSpec {
return import_spec
}
}
}
}
return element
}
================================================
FILE: src/server/features_code_actions.v
================================================
module server
import lsp
import json
import server.intentions
pub fn (mut ls LanguageServer) code_actions(params lsp.CodeActionParams) ?[]lsp.CodeAction {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri)?
mut actions := []lsp.CodeAction{}
ctx := intentions.IntentionContext.from(file.psi_file, params.range.start)
for _, mut intention in ls.intentions {
if !intention.is_available(ctx) {
continue
}
actions << lsp.CodeAction{
title: intention.name
kind: lsp.refactor
command: lsp.Command{
title: intention.name
command: intention.id
arguments: [
json.encode(IntentionData{
file_uri: uri
position: params.range.start
}),
]
}
}
}
for _, mut intention in ls.compiler_quick_fixes {
if !intention.is_available(ctx) {
continue
}
if !params.context.diagnostics.any(intention.is_matched_message(it.message)) {
continue
}
actions << lsp.CodeAction{
title: intention.name
kind: lsp.quick_fix
command: lsp.Command{
title: intention.name
command: intention.id
arguments: [
json.encode(IntentionData{
file_uri: uri
position: params.range.start
}),
]
}
}
}
return actions
}
================================================
FILE: src/server/features_code_lens.v
================================================
module server
import lsp
import server.code_lens
pub fn (mut ls LanguageServer) code_lens(params lsp.CodeLensParams) ?[]lsp.CodeLens {
if !ls.cfg.code_lens.enable {
return []
}
uri := params.text_document.uri.normalize()
file := ls.get_file(uri)?
mut visitor := code_lens.new_visitor(ls.cfg.code_lens, uri, file.psi_file)
visitor.accept(file.psi_file.root())
return visitor.result()
}
================================================
FILE: src/server/features_completion.v
================================================
module server
import lsp
import analyzer.psi
import server.completion
import server.completion.providers
import loglib
pub fn (mut ls LanguageServer) completion(params lsp.CompletionParams) ![]lsp.CompletionItem {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri) or { return [] }
offset := file.find_offset(params.position)
mut source := file.psi_file.source_text
if offset >= source.len {
loglib.with_fields({
'offset': offset.str()
'source_len': source.len.str()
}).warn('Offset is out of range')
return []
}
// The idea behind this solution is:
// When we have an expression like `foo.` and we want to get the autocompletion variants,
// it can be difficult to directly try to figure out what is before the dot, since the
// parser does not parse it correctly, since there must be an identifier after the dot.
// The idea is that we add some dummy identifier at the cursor point and call go to definition,
// which goes through all the variants that may be for this place.
// Thus, we collect them, filter and show them to the user.
source = insert_to_string(source, offset, completion.dummy_identifier)
res := ls.main_parser.parse_code(source)
mut patched_psi_file := psi.new_psi_file(uri.path(), res.tree, res.source_text)
defer { patched_psi_file.free() }
element := patched_psi_file.root().find_element_at(offset) or {
loglib.with_fields({
'offset': offset.str()
}).warn('Cannot find element')
return []
}
// We use CompletionContext in order not to calculate the current partial context
// in each provider, but to calculate it once and pass it to all providers.
mut ctx := &completion.CompletionContext{
element: element
position: params.position
offset: offset
trigger_kind: params.context.trigger_kind
}
ctx.compute()
mut result_set := &completion.CompletionResultSet{}
mut processor := &providers.ReferenceCompletionProcessor{
file: file.psi_file
module_fqn: file.psi_file.module_fqn()
root: ls.root_uri.path()
ctx: ctx
}
mut completion_providers := []completion.CompletionProvider{}
completion_providers << providers.ReferenceCompletionProvider{
processor: processor
}
completion_providers << providers.ModulesImportProvider{}
completion_providers << providers.ReturnCompletionProvider{}
completion_providers << providers.CompileTimeConstantCompletionProvider{}
completion_providers << providers.InitsCompletionProvider{}
completion_providers << providers.KeywordsCompletionProvider{}
completion_providers << providers.TopLevelCompletionProvider{}
completion_providers << providers.LoopKeywordsCompletionProvider{}
completion_providers << providers.PureBlockExpressionCompletionProvider{}
completion_providers << providers.PureBlockStatementCompletionProvider{}
completion_providers << providers.OrBlockExpressionCompletionProvider{}
completion_providers << providers.FunctionLikeCompletionProvider{}
completion_providers << providers.AssertCompletionProvider{}
completion_providers << providers.ModuleNameCompletionProvider{}
completion_providers << providers.NilKeywordCompletionProvider{}
completion_providers << providers.JsonAttributeCompletionProvider{}
completion_providers << providers.AttributesCompletionProvider{}
completion_providers << providers.ImportsCompletionProvider{}
for mut provider in completion_providers {
if !provider.is_available(ctx) {
continue
}
provider.add_completion(ctx, mut result_set)
}
for el in processor.elements() {
result_set.add_element(el)
}
// unsafe { res.tree.free() }
return result_set.elements()
}
fn insert_to_string(str string, offset u32, insert string) string {
return str[..offset] + insert + str[offset..]
}
================================================
FILE: src/server/features_definition.v
================================================
module server
import lsp
import loglib
import analyzer.psi
import server.tform
pub fn (mut ls LanguageServer) definition(params lsp.TextDocumentPositionParams) ?[]lsp.LocationLink {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri)?
offset := file.find_offset(params.position)
element := file.psi_file.find_reference_at(offset) or {
loglib.with_fields({
'offset': offset.str()
}).warn('Cannot find reference')
return none
}
resolved_elements := element.reference().multi_resolve()
if resolved_elements.len == 0 {
return none
}
mut links := []lsp.LocationLink{cap: resolved_elements.len}
for resolved in resolved_elements {
containing_file := resolved.containing_file() or { continue }
if data := new_resolve_result(containing_file, resolved) {
links << data.to_location_link(element.text_range())
}
}
return links
}
struct ResolveResult {
pub:
filepath string
name string
range psi.TextRange
}
pub fn new_resolve_result(containing_file &psi.PsiFile, element psi.PsiElement) ?ResolveResult {
if element is psi.PsiNamedElement {
text_range := element.identifier_text_range()
return ResolveResult{
range: text_range
filepath: containing_file.path()
name: element.name()
}
}
return none
}
fn (r &ResolveResult) to_location_link(origin_selection_range psi.TextRange) lsp.LocationLink {
range := tform.text_range_to_lsp_range(r.range)
return lsp.LocationLink{
target_uri: lsp.document_uri_from_path(r.filepath)
origin_selection_range: tform.text_range_to_lsp_range(origin_selection_range)
target_range: range
target_selection_range: range
}
}
================================================
FILE: src/server/features_did_change.v
================================================
module server
import lsp
import analyzer
import time
import loglib
pub fn (mut ls LanguageServer) did_change(params lsp.DidChangeTextDocumentParams) {
uri := params.text_document.uri.normalize()
mut file := ls.opened_files[uri] or {
loglib.with_fields({
'uri': uri.str()
'params': params.str()
'caller': @METHOD
}).error('File not opened')
return
}
new_content := params.content_changes[0].text
file.psi_file.reparse(new_content, mut ls.main_parser)
ls.opened_files[uri] = analyzer.OpenedFile{
uri: uri
version: file.version++
psi_file: file.psi_file
}
ls.indexing_mng.indexer.mark_as_dirty(uri.path(), new_content) or {
loglib.with_fields({
'uri': uri.str()
'params': params.str()
'caller': @METHOD
'err': err.str()
}).error('Error marking document as dirty')
}
watch := time.new_stopwatch(auto_start: true)
ls.indexing_mng.update_stub_indexes([file.psi_file])
type_cache.clear()
resolve_cache.clear()
enum_fields_cache = map[string]int{}
loglib.with_fields({
'caller': @METHOD
'duration': watch.elapsed().str()
}).info('Updated stub indexes')
loglib.with_fields({
'uri': uri.str()
}).info('Reparsed file')
}
================================================
FILE: src/server/features_did_change_watched_files.v
================================================
module server
import lsp
import loglib
import os
pub fn (mut ls LanguageServer) did_change_watched_files(params lsp.DidChangeWatchedFilesParams) {
changes := params.changes
mut is_rename := false
mut structure_changed := false
// NOTE:
// 1. Renaming a file returns two events: one "created" event for the
// same file with new name and one "deleted" event for the file with
// old name.
// 2. Deleting a folder does not trigger a "deleted" event. Restoring
// the files of the folder however triggers the "created" event.
// 3. Renaming a folder triggers the "created" event for each file
// but with no "deleted" event prior to it.
for i, change in changes {
change_uri := change.uri.normalize()
filename := os.file_name(change_uri.path())
if filename == 'v.mod' || filename == '.git' {
structure_changed = true
}
match change.typ {
.created {
if next_change := changes[i + 1] {
is_rename = next_change.typ == .deleted
}
if is_rename {
prev_change := changes[i + 1] or { continue }
prev_uri := prev_change.uri.normalize()
if file_index := ls.indexing_mng.indexer.rename_file(prev_uri.path(),
change_uri.path())
{
if isnil(file_index.sink) {
continue
}
ls.indexing_mng.update_stub_indexes_from_sinks([*file_index.sink])
loglib.with_fields({
'old': prev_uri.path()
'new': change_uri.path()
}).info('Renamed file')
}
} else {
if file_index := ls.indexing_mng.indexer.add_file(change_uri.path()) {
if isnil(file_index.sink) {
continue
}
ls.indexing_mng.update_stub_indexes_from_sinks([*file_index.sink])
loglib.with_fields({
'file': change_uri.path()
}).info('Added file')
}
}
}
.deleted {
if is_rename {
continue
}
if file_index := ls.indexing_mng.indexer.remove_file(change_uri.path()) {
if isnil(file_index.sink) {
continue
}
ls.indexing_mng.update_stub_indexes_from_sinks([*file_index.sink])
loglib.with_fields({
'file': change_uri.path()
}).info('Removed file')
}
}
.changed {}
}
ls.client.log_message(change.str(), .info)
}
if structure_changed {
loglib.info('Project structure changed, clearing project root cache')
ls.project_resolver.clear()
for uri, _ in ls.opened_files {
ls.run_diagnostics_in_bg(uri)
}
}
}
================================================
FILE: src/server/features_did_close.v
================================================
module server
import lsp
import loglib
pub fn (mut ls LanguageServer) did_close(params lsp.DidCloseTextDocumentParams) {
uri := params.text_document.uri.normalize()
if mut file := ls.opened_files[uri] {
file.psi_file.free()
}
ls.opened_files.delete(uri)
loglib.with_fields({
'uri': uri.str()
'opened_files len': ls.opened_files.len.str()
}).info('closed file')
}
================================================
FILE: src/server/features_did_open.v
================================================
module server
import lsp
import loglib
import analyzer
import analyzer.psi
pub fn (mut ls LanguageServer) did_open(params lsp.DidOpenTextDocumentParams) {
src := params.text_document.text
uri := params.text_document.uri.normalize()
res := ls.main_parser.parse_code(src)
psi_file := psi.new_psi_file(uri.path(), res.tree, res.source_text)
ls.opened_files[uri] = analyzer.OpenedFile{
uri: uri
version: 0
psi_file: psi_file
}
if 'no-diagnostics' !in ls.initialization_options {
ls.run_diagnostics_in_bg(uri)
}
// Useful for debugging
//
// mut visitor := psi.PrinterVisitor{}
// psi_file.root().accept_mut(mut visitor)
// visitor.print()
//
// tree := index.build_stub_tree(psi_file, '')
// tree.print()
loglib.with_fields({
'uri': uri.str()
'opened_files len': ls.opened_files.len.str()
}).info('Opened file')
}
================================================
FILE: src/server/features_did_save.v
================================================
module server
import lsp
pub fn (mut ls LanguageServer) did_save(params lsp.DidSaveTextDocumentParams) {
uri := params.text_document.uri.normalize()
ls.run_diagnostics_in_bg(uri)
}
================================================
FILE: src/server/features_document_highlight.v
================================================
module server
import lsp
import loglib
import server.tform
import analyzer.psi
import analyzer.psi.search
pub fn (mut ls LanguageServer) document_highlight(params lsp.TextDocumentPositionParams) ?[]lsp.DocumentHighlight {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri)?
offset := file.find_offset(params.position)
element := file.psi_file.find_element_at(offset) or {
loglib.with_fields({
'caller': @METHOD
'offset': offset.str()
}).warn('Cannot find element')
return []
}
references := search.references(element, include_declaration: true, only_in_current_file: true)
mut highlights := []lsp.DocumentHighlight{}
for reference in references {
range := if reference is psi.PsiNamedElement {
reference.identifier_text_range()
} else {
reference.text_range()
}
highlights << lsp.DocumentHighlight{
range: tform.text_range_to_lsp_range(range)
kind: read_write_kind(reference)
}
}
return highlights
}
// read_write_kind returns the kind of the highlight for the given element.
//
// - `DocumentHighlightKind.read` means that the highlight is a read access,
// - `DocumentHighlightKind.write` means that the highlight is a write access.
// - `DocumentHighlightKind.text` means that the highlight is neither a read nor a write access.
fn read_write_kind(element psi.PsiElement) lsp.DocumentHighlightKind {
parent := element.parent() or { return .text }
if element is psi.VarDefinition || element is psi.ConstantDefinition
|| element is psi.ParameterDeclaration || element is psi.Receiver
|| element is psi.FieldDeclaration || element is psi.EnumFieldDeclaration {
// for parameter/receiver not an actual write, but we want to highlight it
// with different color than read access.
return .write
}
if parent.element_type() == .expression_list {
grand := parent.parent() or { return .text }
if grand.element_type() == .assignment_statement {
left := grand.first_child() or { return .text }
if left.is_parent_of(element) {
return .write
}
return .read
}
}
if parent.element_type() in [.send_statement, .append_statement] {
left := parent.first_child() or { return .text }
if left.is_parent_of(element) {
return .write
}
return .read
}
if parent.element_type() in [.send_statement, .append_statement, .field_name] {
return .write
}
return .read
}
================================================
FILE: src/server/features_document_symbol.v
================================================
module server
import lsp
import analyzer.psi
import server.tform
pub fn (mut ls LanguageServer) document_symbol(params lsp.DocumentSymbolParams) ![]lsp.DocumentSymbol {
uri := params.text_document.uri.normalize()
mut file_symbols := []lsp.DocumentSymbol{}
elements := stubs_index.get_all_elements_from_file(uri.path())
for element in elements {
file_symbols << document_symbol_presentation(element) or { continue }
}
return file_symbols
}
fn document_symbol_presentation(element psi.PsiElement) ?lsp.DocumentSymbol {
full_text_range := element.text_range()
if element is psi.PsiNamedElement {
if element.name() == '' {
return none
}
identifier_text_range := element.identifier_text_range()
children := symbol_children(element)
return lsp.DocumentSymbol{
name: name_presentation(element)
detail: detail_presentation(element)
kind: symbol_kind(element as psi.PsiElement)?
range: tform.text_range_to_lsp_range(full_text_range)
selection_range: tform.text_range_to_lsp_range(identifier_text_range)
children: children
}
}
return none
}
fn symbol_kind(element psi.PsiElement) ?lsp.SymbolKind {
match element {
psi.FunctionOrMethodDeclaration {
if _ := element.receiver() {
return .method
}
return .function
}
psi.StructDeclaration {
return .struct_
}
psi.InterfaceDeclaration {
return .interface_
}
psi.InterfaceMethodDeclaration {
return .method
}
psi.FieldDeclaration {
return .field
}
psi.EnumDeclaration {
return .enum_
}
psi.EnumFieldDeclaration {
return .enum_member
}
psi.ConstantDefinition {
return .constant
}
psi.TypeAliasDeclaration {
return .type_parameter
}
else {}
}
return none
}
fn name_presentation(element psi.PsiNamedElement) string {
name := element.name()
if element is psi.FunctionOrMethodDeclaration {
mut parts := []string{}
if receiver := element.receiver() {
parts << receiver.get_text()
}
parts << name
if parameters := element.generic_parameters() {
parts << parameters.text_presentation()
}
return parts.join(' ')
}
if element is psi.GenericParametersOwner {
parameters := element.generic_parameters() or { return name }
return name + parameters.text_presentation()
}
return name
}
fn detail_presentation(element psi.PsiNamedElement) string {
if element is psi.FunctionOrMethodDeclaration {
if signature := element.signature() {
return 'fn ' + signature.get_text()
}
}
if element is psi.FieldDeclaration {
return element.get_type().readable_name()
}
if element is psi.InterfaceMethodDeclaration {
if signature := element.signature() {
return signature.get_text()
}
}
if element is psi.ConstantDefinition {
return element.get_type().readable_name()
}
if element is psi.EnumFieldDeclaration {
return '= ' + element.value_presentation(true)
}
return ''
}
fn symbol_children(element psi.PsiNamedElement) []lsp.DocumentSymbol {
mut children := []psi.PsiElement{}
if element is psi.StructDeclaration {
children << element.own_fields()
} else if element is psi.EnumDeclaration {
children << element.fields()
} else if element is psi.InterfaceDeclaration {
children << element.fields()
children << element.methods()
}
mut symbols := []lsp.DocumentSymbol{cap: children.len}
for child in children {
symbols << document_symbol_presentation(child) or { continue }
}
return symbols
}
================================================
FILE: src/server/features_execute_command.v
================================================
module server
import lsp
import loglib
import json
import server.intentions
pub struct IntentionData {
pub:
file_uri string
position lsp.Position
}
pub fn (mut ls LanguageServer) execute_command(params lsp.ExecuteCommandParams) ? {
mut intention := ls.find_intention_or_quickfix(params.command)?
arguments := json.decode([]string, params.arguments) or { []string{} }
argument := json.decode(IntentionData, arguments[0]) or {
loglib.with_fields({
'command': params.command
'argument': params.arguments
}).warn('Got invalid argument')
return
}
file_uri := argument.file_uri
file := ls.get_file(file_uri)?
pos := argument.position
ctx := intentions.IntentionContext.from(file.psi_file, pos)
edits := intention.invoke(ctx) or { return }
ls.client.apply_edit(edit: edits)
}
pub fn (mut ls LanguageServer) find_intention_or_quickfix(name string) ?intentions.Intention {
if i := ls.intentions[name] {
return i
}
if qf := ls.compiler_quick_fixes[name] {
return qf as intentions.Intention
}
return none
}
================================================
FILE: src/server/features_folding_range.v
================================================
module server
import lsp
import loglib
import server.folding
pub fn (mut ls LanguageServer) folding_range(params lsp.FoldingRangeParams) ?[]lsp.FoldingRange {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri) or {
loglib.with_fields({
'uri': uri.str()
}).warn('Folding range requested for unopened file')
return []
}
mut visitor := folding.FoldingVisitor.new(file.psi_file)
ranges := visitor.accept(file.psi_file.root())
return ranges
}
================================================
FILE: src/server/features_formatting.v
================================================
module server
import lsp
import os
import server.tform
import loglib
const temp_formatting_file_path = os.join_path(os.temp_dir(), 'v-analyzer-formatting-temp.v')
pub fn (mut ls LanguageServer) formatting(params lsp.DocumentFormattingParams) ![]lsp.TextEdit {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri) or { return error('Cannot format not opened file') }
os.write_file(temp_formatting_file_path, file.psi_file.source_text) or {
return error('Cannot write temp file for formatting: ${err}')
}
loglib.with_fields({
'uri': file.uri.str()
}).info('Formatting file')
mut fmt_proc := ls.launch_tool('fmt', os.norm_path(temp_formatting_file_path))!
defer {
fmt_proc.close()
}
fmt_proc.run()
// read entire output until EOF
mut output := fmt_proc.stdout_slurp()
fmt_proc.wait()
loglib.with_fields({
'code': fmt_proc.code.str()
'status': fmt_proc.status.str()
}).info('Formatting finished')
$if windows {
output = output.replace('\r\r', '\r')
}
if fmt_proc.code != 0 && fmt_proc.status == .exited {
errors := fmt_proc.stderr_slurp().trim_space()
ls.client.show_message(errors, .info)
return error('Formatting failed: ${errors}')
// return []
}
return [
lsp.TextEdit{
range: tform.text_range_to_lsp_range(file.psi_file.root().text_range())
new_text: output
},
]
}
================================================
FILE: src/server/features_formatting_test.v
================================================
module server
import lsp
import os
import analyzer
import analyzer.psi
import analyzer.parser
fn test_large_file() {
test_file := os.real_path(@VMODROOT + '/src/server/general.v')
src := os.read_file(test_file) or { panic('Cannot read file') }
mut ls := LanguageServer.new(analyzer.IndexingManager.new())
ls.setup_toolchain()
ls.setup_vpaths()
uri := lsp.document_uri_from_path(test_file)
mut p := parser.Parser.new()
defer { p.free() }
res := p.parse_code(src)
psi_file := psi.new_psi_file(uri.path(), res.tree, res.source_text)
ls.opened_files[uri] = analyzer.OpenedFile{
uri: uri
version: 0
psi_file: psi_file
}
params := lsp.DocumentFormattingParams{
text_document: lsp.TextDocumentIdentifier{
uri: uri
}
}
text_edit_result := ls.formatting(params) or { panic('Cannot format file') }
assert text_edit_result.len == 1
}
================================================
FILE: src/server/features_hover.v
================================================
module server
import lsp
import server.documentation
import loglib
import server.tform
pub fn (mut ls LanguageServer) hover(params lsp.HoverParams) ?lsp.Hover {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri)?
loglib.with_fields({
'position': params.position.str()
'uri': file.uri
}).warn('Hover request')
offset := file.find_offset(params.position)
element := file.psi_file.find_element_at(offset) or {
loglib.with_fields({
'offset': offset.str()
}).warn('Cannot find element')
return none
}
if element.element_type() == .unknown {
mut provider := documentation.KeywordProvider{}
if content := provider.documentation(element) {
return lsp.Hover{
contents: lsp.hover_markdown_string(content)
range: tform.text_range_to_lsp_range(element.text_range())
}
}
}
mut provider := documentation.Provider{}
doc_element := provider.find_documentation_element(element)?
if content := provider.documentation(doc_element) {
return lsp.Hover{
contents: lsp.hover_markdown_string(content)
range: tform.text_range_to_lsp_range(element.text_range())
}
}
$if show_ast_on_hover ? {
// Show AST tree for debugging purposes.
if grand := element.parent_nth(2) {
parent := element.parent()?
this := element.type_name() + ': ' + element.node().type_name.str()
parent_elem := parent.type_name() + ': ' + parent.node().type_name.str()
grand_elem := grand.type_name() + ': ' + grand.node().type_name.str()
return lsp.Hover{
contents: lsp.hover_markdown_string('```\n' + grand_elem + '\n ' + parent_elem +
'\n ' + this + '\n```')
range: tform.text_range_to_lsp_range(element.text_range())
}
}
return lsp.Hover{
contents: lsp.hover_markdown_string(element.type_name() + ': ' +
element.node().type_name.str())
range: tform.text_range_to_lsp_range(element.text_range())
}
}
return none
}
================================================
FILE: src/server/features_implementation.v
================================================
module server
import lsp
import loglib
import server.tform
import analyzer.psi
import analyzer.psi.search
pub fn (mut ls LanguageServer) implementation(params lsp.TextDocumentPositionParams) ?[]lsp.Location {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri)?
offset := file.find_offset(params.position)
element := file.psi_file.find_element_at(offset) or {
loglib.with_fields({
'offset': offset.str()
}).warn('Cannot find element')
return none
}
if method := element.parent_of_type(.interface_method_definition) {
if method is psi.InterfaceMethodDeclaration {
methods := search.implementation_methods(*method)
return tform.elements_to_locations(methods)
}
}
if interface_declaration := element.parent_of_type(.interface_declaration) {
if interface_declaration is psi.InterfaceDeclaration {
implementations := search.implementations(*interface_declaration)
return tform.elements_to_locations(implementations)
}
}
if struct_declaration := element.parent_of_type(.struct_declaration) {
if struct_declaration is psi.StructDeclaration {
supers := search.supers(*struct_declaration)
return tform.elements_to_locations(supers)
}
}
if method := element.parent_of_type(.function_declaration) {
if method is psi.FunctionOrMethodDeclaration {
super_methods := search.super_methods(*method)
return tform.elements_to_locations(super_methods)
}
}
loglib.with_fields({
'element': element.get_text()
'text_range': element.text_range().str()
'element_type': element.element_type().str()
}).warn('Element is not inside an interface or struct declaration')
return none
}
================================================
FILE: src/server/features_inlay_hints.v
================================================
module server
import lsp
import server.hints
pub fn (mut ls LanguageServer) inlay_hints(params lsp.InlayHintParams) ?[]lsp.InlayHint {
if !ls.cfg.inlay_hints.enable {
return none
}
uri := params.text_document.uri.normalize()
file := ls.get_file(uri)?
mut visitor := hints.InlayHintsVisitor{
cfg: ls.cfg.inlay_hints
}
visitor.accept(file.psi_file.root())
return visitor.result
}
================================================
FILE: src/server/features_prepare_rename.v
================================================
module server
import lsp
import loglib
import analyzer.psi
import server.tform
pub fn (mut ls LanguageServer) prepare_rename(params lsp.PrepareRenameParams) !lsp.PrepareRenameResult {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri) or { return error('cannot rename element from not opened file') }
offset := file.find_offset(params.position)
element := file.psi_file.find_element_at(offset) or {
loglib.with_fields({
'offset': offset.str()
}).warn('Cannot find element')
return error('cannot find element at ' + offset.str())
}
if element !is psi.Identifier {
return error('cannot rename non identifier element')
}
resolved := resolve_identifier(element)
if resolved !is psi.VarDefinition {
return error('cannot rename non-variable element')
}
if resolved is psi.PsiNamedElement {
element_text_range := resolved.identifier_text_range()
return lsp.PrepareRenameResult{
range: tform.text_range_to_lsp_range(element_text_range)
placeholder: ''
}
}
return error('')
}
fn resolve_identifier(element psi.PsiElement) psi.PsiElement {
parent := element.parent() or { return element }
resolved := if parent is psi.ReferenceExpression {
parent.resolve() or { return element }
} else if parent is psi.TypeReferenceExpression {
parent.resolve() or { return element }
} else {
parent
}
return resolved
}
================================================
FILE: src/server/features_references.v
================================================
module server
import lsp
import loglib
import server.tform
import analyzer.psi.search
pub fn (mut ls LanguageServer) references(params lsp.ReferenceParams) []lsp.Location {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri) or { return [] }
offset := file.find_offset(params.position)
element := file.psi_file.find_element_at(offset) or {
loglib.with_fields({
'offset': offset.str()
}).warn('Cannot find element')
return []
}
references := search.references(element)
return tform.elements_to_locations(references)
}
================================================
FILE: src/server/features_rename.v
================================================
module server
import lsp
import loglib
import server.tform
import analyzer.psi.search
pub fn (mut ls LanguageServer) rename(params lsp.RenameParams) !lsp.WorkspaceEdit {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri) or { return error('cannot rename element from not opened file') }
offset := file.find_offset(params.position)
element := file.psi_file.find_element_at(offset) or {
loglib.with_fields({
'offset': offset.str()
}).warn('Cannot find element')
return error('cannot find element at ' + offset.str())
}
references := search.references(element, include_declaration: true)
edits := tform.elements_to_text_edits(references, params.new_name)
return lsp.WorkspaceEdit{
changes: {
uri: edits
}
}
}
================================================
FILE: src/server/features_semantic_tokens.v
================================================
module server
import lsp
import server.semantic
import time
const max_line_for_resolve_semantic_tokens = 1000
const max_line_for_any_semantic_tokens = 10000
pub fn (mut ls LanguageServer) semantic_tokens(text_document lsp.TextDocumentIdentifier, range lsp.Range) ?lsp.SemanticTokens {
if ls.cfg.enable_semantic_tokens == .none_ {
return none
}
uri := text_document.uri.normalize()
file := ls.get_file(uri)?
lines := file.psi_file.source_text.count('\n')
if lines > max_line_for_any_semantic_tokens {
// File too large, don't compute any tokens.
return lsp.SemanticTokens{}
}
if lines > max_line_for_resolve_semantic_tokens || ls.cfg.enable_semantic_tokens == .syntax {
// We don't want to send too many tokens (and compute it), so we just
// send dumb-aware tokens for large files.
dumb_aware_visitor := semantic.new_dumb_aware_semantic_visitor(range, file.psi_file)
tokens := dumb_aware_visitor.accept(file.psi_file.root)
return lsp.SemanticTokens{
result_id: time.now().unix().str()
data: semantic.encode(tokens)
}
}
mut result := semantic.new_dumb_aware_semantic_visitor(range, file.psi_file)
.accept(file.psi_file.root)
resolve_tokens := semantic.new_resolve_semantic_visitor(range, file.psi_file)
.accept(file.psi_file.root)
result << resolve_tokens
return lsp.SemanticTokens{
result_id: time.now().unix().str()
data: semantic.encode(result)
}
}
================================================
FILE: src/server/features_signature_help.v
================================================
module server
import lsp
import analyzer.psi
import loglib
pub fn (mut ls LanguageServer) signature_help(params lsp.SignatureHelpParams) ?lsp.SignatureHelp {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri)?
offset := file.find_offset(params.position)
element := file.psi_file.find_element_at(offset) or {
loglib.with_fields({
'offset': offset.str()
}).warn('Cannot find element')
return none
}
call := element.parent_of_type_or_self(.call_expression) or {
loglib.with_fields({
'offset': offset.str()
}).warn('Cannot find call expression')
return none
}
if call is psi.CallExpression {
resolved := call.resolve() or {
loglib.with_fields({
'offset': offset.str()
}).warn('Cannot resolve call expression for signature help')
return none
}
if resolved is psi.FunctionOrMethodDeclaration {
ctx := params.context
active_parameter := call.parameter_index_on_offset(offset)
if ctx.is_retrigger {
mut help := ctx.active_signature_help
help.active_parameter = active_parameter
return help
}
signature := resolved.signature()?
parameters := signature.parameters()
mut param_infos := []lsp.ParameterInformation{}
for parameter in parameters {
param_infos << lsp.ParameterInformation{
label: parameter.get_text()
}
}
return lsp.SignatureHelp{
active_parameter: active_parameter
signatures: [
lsp.SignatureInformation{
label: 'fn ${resolved.name()}${signature.get_text()}'
parameters: param_infos
},
]
}
}
}
return none
}
================================================
FILE: src/server/features_type_definition.v
================================================
module server
import lsp
import analyzer.psi
import analyzer.psi.types
import loglib
pub fn (mut ls LanguageServer) type_definition(params lsp.TextDocumentPositionParams) ?[]lsp.LocationLink {
uri := params.text_document.uri.normalize()
file := ls.get_file(uri)?
offset := file.find_offset(params.position)
element := file.psi_file.find_reference_at(offset) or {
loglib.with_fields({
'offset': offset.str()
}).warn('Cannot find reference')
return none
}
element_text_range := element.text_range()
resolved := element.resolve() or {
loglib.with_fields({
'caller': @METHOD
'name': element.name()
}).warn('Cannot resolve reference')
return none
}
typ :=
types.unwrap_generic_instantiation_type(types.unwrap_pointer_type(psi.infer_type(resolved)))
type_element := psi.find_element(typ.qualified_name())?
containing_file := type_element.containing_file() or { return [] }
data := new_resolve_result(containing_file, type_element) or { return [] }
return [
data.to_location_link(element_text_range),
]
}
================================================
FILE: src/server/features_view_stub_tree.v
================================================
module server
import lsp
import strings
import analyzer.index
pub fn (mut ls LanguageServer) view_stub_tree(params lsp.TextDocumentIdentifier) ?string {
uri := params.uri.normalize()
file := ls.get_file(uri) or { return 'file not opened' }
mut sb := strings.new_builder(100)
tree := index.build_stub_tree(file.psi_file, params.uri.dir_path())
tree.print_to(mut sb)
return sb.str()
}
================================================
FILE: src/server/features_workspace_symbol.v
================================================
module server
import lsp
import server.tform
import analyzer.psi
pub fn (mut ls LanguageServer) workspace_symbol(_ lsp.WorkspaceSymbolParams) ?[]lsp.WorkspaceSymbol {
workspace_elements := ls.indexing_mng.stub_index.get_all_elements_from(.workspace)
mut workspace_symbols := []lsp.WorkspaceSymbol{cap: workspace_elements.len}
for elem in workspace_elements {
file := elem.containing_file() or { continue }
uri := file.uri()
module_name := file.module_name() or { '' }
if elem is psi.PsiNamedElement {
name := elem.name()
if name == '_' {
continue
}
fqn := if module_name == '' { name } else { module_name + '.' + name }
text_range := elem.identifier_text_range()
workspace_symbols << lsp.WorkspaceSymbol{
name: fqn
kind: symbol_kind(elem as psi.PsiElement) or { continue }
location: lsp.Location{
uri: uri
range: tform.text_range_to_lsp_range(text_range)
}
}
}
}
return workspace_symbols
}
================================================
FILE: src/server/file_diff/Diff.v
================================================
module file_diff
import lsp
pub struct Edit {
range lsp.Range
new_text string
}
pub struct Diff {
uri lsp.DocumentUri
mut:
edits []Edit
}
pub fn Diff.for_file(uri lsp.DocumentUri) Diff {
return Diff{
uri: uri
}
}
pub fn (mut diff Diff) append_to_begin(line int, text string) {
diff.append_to(line, 0, text)
}
pub fn (mut diff Diff) append_to(line int, col int, text string) {
pos := Diff.line_pos(line, col)
diff.edits << Edit{
range: lsp.Range{
start: pos
end: pos
}
new_text: text
}
}
pub fn (mut diff Diff) append_as_prev_line(line int, text string) {
pos := Diff.line_begin_pos(line)
diff.edits << Edit{
range: lsp.Range{
start: pos
end: pos
}
new_text: '${text}\n'
}
}
pub fn (mut diff Diff) append_as_next_line(line int, text string) {
pos := Diff.line_begin_pos(line)
diff.edits << Edit{
range: lsp.Range{
start: pos
end: pos
}
new_text: '\n${text}'
}
}
pub fn (mut diff Diff) to_workspace_edit() lsp.WorkspaceEdit {
return lsp.WorkspaceEdit{
changes: {
diff.uri: diff.edits.map(lsp.TextEdit{
range: it.range
new_text: it.new_text
})
}
}
}
fn Diff.line_begin_pos(line int) lsp.Position {
return lsp.Position{
line: line
character: 0
}
}
fn Diff.line_pos(line int, col int) lsp.Position {
return lsp.Position{
line: line
character: col
}
}
================================================
FILE: src/server/folding/visitor.v
================================================
module folding
import lsp
import analyzer.psi
pub struct FoldingVisitor {
source_text string
lines []string
mut:
ranges []lsp.FoldingRange
last_comment_end_line int = -2
comment_start_line int = -1
}
pub fn FoldingVisitor.new(file &psi.PsiFile) FoldingVisitor {
return FoldingVisitor{
source_text: file.source_text
lines: file.source_text.split_into_lines()
}
}
pub fn (mut v FoldingVisitor) accept(root psi.PsiElement) []lsp.FoldingRange {
mut walker := psi.new_tree_walker(root.node())
defer { walker.free() }
for {
node := walker.next() or { break }
v.process_node(node)
}
v.flush_comment_group()
return v.ranges
}
fn (mut v FoldingVisitor) process_node(node psi.AstNode) {
if node.type_name == .line_comment {
v.process_line_comment(node)
return
}
v.flush_comment_group()
match node.type_name {
.block {
v.fold_block(node)
}
.type_initializer {
if body := node.child_by_field_name('body') {
v.fold_block(body)
}
}
.struct_declaration, .enum_declaration, .interface_declaration {
v.fold_delimiters(node, '{', '}', lsp.folding_range_kind_region)
}
.global_var_declaration {
v.fold_delimiters(node, '(', ')', lsp.folding_range_kind_region)
}
.import_list {
v.fold_simple(node, lsp.folding_range_kind_imports)
}
.block_comment {
v.fold_simple(node, lsp.folding_range_kind_comment)
}
else {}
}
}
fn (mut v FoldingVisitor) fold_block(node psi.AstNode) {
start_line := int(node.start_point().row)
end_line := int(node.end_point().row)
if start_line < end_line {
final_end_line := v.determine_end_line(end_line, '}')
v.add_range(start_line, final_end_line, lsp.folding_range_kind_region)
}
}
fn (mut v FoldingVisitor) fold_delimiters(node psi.AstNode, open string, close string, kind string) {
count := node.child_count()
mut start_line := -1
mut end_line := -1
for i in 0 .. count {
child := node.child(i) or { continue }
if child.child_count() > 0 {
continue
}
text := child.text(v.source_text)
if text == open {
start_line = int(child.start_point().row)
} else if text == close {
end_line = int(child.start_point().row)
}
}
if start_line != -1 && end_line > start_line {
final_end_line := v.determine_end_line(end_line, close)
v.add_range(start_line, final_end_line, kind)
}
}
fn (mut v FoldingVisitor) fold_simple(node psi.AstNode, kind string) {
start_line := int(node.start_point().row)
mut end_line := int(node.end_point().row)
if node.end_point().column == 0 && end_line > start_line {
end_line--
}
for end_line > start_line && end_line < v.lines.len {
if v.lines[end_line].trim_space() != '' {
break
}
end_line--
}
if start_line < end_line {
v.add_range(start_line, end_line, kind)
}
}
fn (mut v FoldingVisitor) determine_end_line(end_line int, close string) int {
if end_line >= v.lines.len {
return end_line
}
line := v.lines[end_line].trim_space()
if line == close || line == close + ',' || line == close + ';' {
return end_line
}
return end_line - 1
}
@[inline]
fn (mut v FoldingVisitor) add_range(start int, end int, kind string) {
v.ranges << lsp.FoldingRange{
start_line: start
end_line: end
kind: kind
}
}
fn (mut v FoldingVisitor) process_line_comment(node psi.AstNode) {
start_line := int(node.start_point().row)
end_line := int(node.end_point().row)
if v.last_comment_end_line != -2 && start_line == v.last_comment_end_line + 1 {
v.last_comment_end_line = end_line
} else {
v.flush_comment_group()
v.comment_start_line = start_line
v.last_comment_end_line = end_line
}
}
fn (mut v FoldingVisitor) flush_comment_group() {
start := v.comment_start_line
end := v.last_comment_end_line
v.comment_start_line = -1
v.last_comment_end_line = -2
if start != -1 && end > start {
v.add_range(start, end, lsp.folding_range_kind_comment)
}
}
================================================
FILE: src/server/general.v
================================================
module server
import lsp
import runtime
import os
import project
import metadata
import arrays
import config
import loglib
import analyzer.index
import server.protocol
import server.semantic
import server.progress
import server.intentions
import server.workspace
// initialize sends the server capabilities to the client
pub fn (mut ls LanguageServer) initialize(params lsp.InitializeParams, mut wr ResponseWriter) lsp.InitializeResult {
ls.client_pid = params.process_id
ls.client = protocol.new_client(mut wr)
ls.progress = progress.new_tracker(mut ls.client)
ls.bg.start()
ls.root_uri = params.root_uri
ls.status = .initialized
ls.project_resolver = workspace.ProjectResolver.new(ls.root_uri.path())
ls.progress.support_work_done_progress = params.capabilities.window.work_done_progress
options := params.initialization_options
if options is string {
ls.initialization_options = options.fields()
}
ls.print_info(params.process_id, params.client_info)
ls.setup()
ls.register_intention(intentions.AddFlagAttributeIntention{})
ls.register_intention(intentions.AddHeapAttributeIntention{})
ls.register_intention(intentions.MakePublicIntention{})
ls.register_compiler_quick_fix(intentions.MakeMutableQuickFix{})
ls.register_compiler_quick_fix(intentions.ImportModuleQuickFix{})
return lsp.InitializeResult{
capabilities: lsp.ServerCapabilities{
text_document_sync: lsp.TextDocumentSyncOptions{
open_close: true
change: .full
will_save: true
save: lsp.SaveOptions{}
}
hover_provider: true
definition_provider: true
type_definition_provider: true
references_provider: lsp.ReferencesOptions{}
document_formatting_provider: true
completion_provider: lsp.CompletionOptions{
resolve_provider: false
trigger_characters: ['.', ':', '(', '@']
}
signature_help_provider: lsp.SignatureHelpOptions{
trigger_characters: ['(', ',']
retrigger_characters: [',', ' ']
}
code_lens_provider: lsp.CodeLensOptions{}
inlay_hint_provider: lsp.InlayHintOptions{}
semantic_tokens_provider: lsp.SemanticTokensOptions{
legend: lsp.SemanticTokensLegend{
token_types: semantic.semantic_types
token_modifiers: semantic.semantic_modifiers
}
range: true
full: true
}
rename_provider: lsp.RenameOptions{
prepare_provider: false
}
document_symbol_provider: true
workspace_symbol_provider: true
implementation_provider: true
document_highlight_provider: true
code_action_provider: lsp.CodeActionOptions{
code_action_kinds: [lsp.quick_fix]
}
folding_range_provider: true
execute_command_provider: lsp.ExecuteCommandOptions{
commands: arrays.concat(ls.intentions.values().map(it.id),
...ls.compiler_quick_fixes.values().map(it.id))
}
}
server_info: lsp.ServerInfo{
name: metadata.manifest.name
version: metadata.manifest.version
}
}
}
pub fn (mut ls LanguageServer) initialized(mut wr ResponseWriter) {
loglib.info('-------- New session -------- ')
ls.indexing_mng.setup_empty_indexes()
if ls.paths.vexe == '' || ls.paths.vlib_root == '' {
ls.client.send_server_status(health: 'error', quiescent: true)
return
} else {
ls.client.send_server_status(health: 'ok')
}
mut work := ls.progress.start('Indexing:', 'roots...', '')
// Used in tests to avoid indexing the standard library
need_index_stdlib := 'no-stdlib' !in ls.initialization_options
if need_index_stdlib {
ls.indexing_mng.indexer.add_indexing_root(ls.paths.vmodules_root, .modules,
ls.paths.cache_dir)
for path in os.vmodules_paths()[1..] {
if path.is_blank() {
continue
}
ls.indexing_mng.indexer.add_indexing_root(path, .modules, ls.paths.cache_dir)
}
ls.indexing_mng.indexer.add_indexing_root(ls.paths.vlib_root, .standard_library,
ls.paths.cache_dir)
}
if stubs_root := ls.stubs_root() {
ls.indexing_mng.indexer.add_indexing_root(stubs_root, .stubs, ls.paths.cache_dir)
}
ls.indexing_mng.indexer.add_indexing_root(ls.root_uri.path(), .workspace, ls.paths.cache_dir)
status := ls.indexing_mng.indexer.index(fn [mut work, mut ls] (root index.IndexingRoot, i int) {
percentage := (i * 70) / ls.indexing_mng.indexer.count_roots()
work.progress('${i}/${ls.indexing_mng.indexer.count_roots()} (${root.kind.readable_name()})',
u32(percentage))
ls.client.log_message('Indexing ${root.root}', .info)
})
work.progress('Finish roots indexing', 70)
if status == .needs_ensure_indexed {
work.progress('Start ensure indexing', 71)
ls.indexing_mng.indexer.ensure_indexed()
work.progress('Finish ensure indexing', 95)
}
// Used in tests to avoid indexing the standard library
need_save_index := 'no-index-save' !in ls.initialization_options
ls.indexing_mng.indexer.set_no_save(!need_save_index)
ls.indexing_mng.indexer.save_indexes() or {
loglib.with_fields({
'err': err.str()
}).error('Failed to save index')
}
ls.indexing_mng.setup_stub_indexes()
work.end('Indexing finished')
ls.client.send_server_status(health: 'ok', quiescent: true)
}
fn (mut ls LanguageServer) setup() {
ls.setup_config_dir()
ls.setup_stubs()
config_path := ls.find_config()
if config_path == '' {
ls.client.log_message('No config found', .warning)
loglib.warn('No config found')
ls.setup_toolchain()
ls.setup_vpaths()
return
}
config_content := os.read_file(config_path) or {
ls.client.log_message('Failed to read config: ${err}', .error)
loglib.with_fields({
'err': err.str()
}).error('Failed to read config')
return
}
cfg := config.from_toml(ls.root_uri.path(), config_path, config_content) or {
ls.client.log_message('Failed to decode config: ${err}', .error)
ls.client.log_message('Using default config', .info)
loglib.with_fields({
'err': err.str()
}).error('Failed to decode config')
loglib.info('Using default config')
config.EditorConfig{}
}
config_type := if cfg.is_local() { 'local' } else { 'global' }
ls.client.log_message('Using ${config_type} config: ${config_path}', .info)
ls.cfg = cfg
if cfg.custom_vroot != '' {
ls.paths.vroot = os.expand_tilde_to_home(cfg.custom_vroot)
ls.client.log_message("Find custom VROOT path in '${cfg.path()}' config", .info)
ls.client.log_message('Using "${cfg.custom_vroot}" as toolchain', .info)
loglib.info("Find custom VROOT path in '${cfg.path()}' config")
loglib.info('Using "${cfg.custom_vroot}" as toolchain')
}
if cfg.custom_cache_dir != '' {
ls.paths.cache_dir = os.abs_path(os.expand_tilde_to_home(cfg.custom_cache_dir))
if !os.exists(ls.paths.cache_dir) {
os.mkdir_all(ls.paths.cache_dir) or {
ls.client.log_message('Failed to create custom analyzer caches directory: ${err}',
.error)
loglib.with_fields({
'err': err.str()
}).error('Failed to create custom analyzer caches directory')
}
}
ls.client.log_message("Find custom cache dir path in '${cfg.path()}' config", .info)
ls.client.log_message('Using "${cfg.custom_cache_dir}" as cache dir', .info)
loglib.info("Find custom cache dir path in '${cfg.path()}' config")
loglib.info('Using "${cfg.custom_cache_dir}" as cache dir')
}
if ls.paths.vroot == '' {
// if custom vroot is not set, try to find it
ls.setup_toolchain()
}
if ls.paths.cache_dir == '' {
ls.setup_cache_dir()
}
ls.setup_vpaths()
}
fn (mut ls LanguageServer) setup_cache_dir() {
if !os.exists(config.analyzer_caches_path) {
os.mkdir_all(config.analyzer_caches_path) or {
ls.client.log_message('Failed to create analyzer caches directory: ${err}', .error)
loglib.with_fields({
'err': err.str()
}).error('Failed to create analyzer caches directory')
return
}
}
// if custom cache dir is not set, use default
ls.paths.cache_dir = config.analyzer_caches_path
ls.client.log_message('Using "${ls.paths.cache_dir}" as cache dir', .info)
loglib.info('Using "${ls.paths.cache_dir}" as cache dir')
}
fn (mut ls LanguageServer) find_config() string {
root := ls.root_uri.path()
local_config_path := os.join_path(root, '.v-analyzer', 'config.toml')
if os.exists(local_config_path) {
return local_config_path
}
global_config_path := os.join_path(config.analyzer_configs_path, 'config.toml')
if os.exists(global_config_path) {
return global_config_path
}
return ''
}
fn (mut ls LanguageServer) setup_toolchain() {
toolchain_candidates := project.get_toolchain_candidates()
if toolchain_candidates.len == 0 {
ls.client.log_message("No toolchain candidates found, some of the features won't work properly.
Please, set `custom_vroot` in local or global config.
Global config path: ${config.analyzer_configs_path}/${config.analyzer_config_name}",
.error)
loglib.error("No toolchain candidates found, some of the features won't work properly.
Please, set `custom_vroot` in local or global config.")
return
}
ls.client.log_message('Found toolchain candidates:', .info)
loglib.info('Found toolchain candidates:')
for toolchain_candidate in toolchain_candidates {
ls.client.log_message(' ${toolchain_candidate}', .info)
loglib.info(' ${toolchain_candidate}')
}
ls.client.log_message('Using "${toolchain_candidates.first()}" as toolchain', .info)
loglib.info('Using "${toolchain_candidates.first()}" as toolchain')
ls.paths.vroot = toolchain_candidates.first()
if toolchain_candidates.len > 1 {
ls.client.log_message('To set other toolchain, use `custom_vroot` in local or global config.
Global config path: ${config.analyzer_configs_path}/${config.analyzer_config_name}',
.info)
}
}
fn (mut ls LanguageServer) setup_vpaths() {
// Prior call of `ls.setup_toolchain()` ensures `ls.paths.vroot` is set.
vexe_path := os.join_path(ls.paths.vroot, $if windows { 'v.exe' } $else { 'v' })
if !os.is_file(vexe_path) {
msg := 'Failed to find V compiler!'
ls.client.log_message(msg, .error)
loglib.error(msg)
} else {
ls.paths.vexe = vexe_path
ls.reporter.compiler_path = vexe_path
}
vlib_path := os.join_path(ls.paths.vroot, 'vlib')
if !os.is_dir(vlib_path) {
msg := 'Failed to find V standard library.'
ls.client.log_message(msg, .error)
loglib.error(msg)
} else {
ls.paths.vlib_root = vlib_path
}
vmodules_root := os.vmodules_dir()
if !os.is_dir(vmodules_root) {
msg := 'Failed to find vmodules path.'
ls.client.log_message(msg, .error)
loglib.error(msg)
} else {
ls.paths.vmodules_root = vmodules_root
msg := 'Using "${vmodules_root}" as vmodules root.'
ls.client.log_message(msg, .info)
loglib.info(msg)
}
}
fn (mut ls LanguageServer) setup_config_dir() {
if !os.exists(config.analyzer_configs_path) {
os.mkdir_all(config.analyzer_configs_path) or {
ls.client.log_message('Failed to create analyzer configs directory: ${err}', .error)
loglib.with_fields({
'err': err.str()
}).error('Failed to create analyzer configs directory')
return
}
}
if !os.exists(config.analyzer_global_config_path) {
ls.client.log_message('Global config not found', .info)
ls.client.log_message('Creating default global analyzer config', .info)
loglib.info('Global config not found')
loglib.info('Creating default global analyzer config')
os.write_file(config.analyzer_global_config_path, config.default) or {
ls.client.log_message('Failed to create global default analyzer config: ${err}', .error)
loglib.with_fields({
'err': err.str()
}).error('Failed to create global default analyzer config')
return
}
ls.client.log_message('Default analyzer config created at ${config.analyzer_global_config_path}',
.info)
loglib.info('Default analyzer config created at ${config.analyzer_global_config_path}')
}
}
fn (mut ls LanguageServer) setup_stubs() {
if os.exists(config.analyzer_stubs_path) {
if os.exists(config.analyzer_stubs_version_path) {
version_string := os.read_file(config.analyzer_stubs_version_path) or {
ls.client.log_message('Failed to read stubs version: ${err}', .error)
loglib.with_fields({
'err': err.str()
}).error('Failed to read stubs version')
'0'
}
version := version_string.int()
if version == ls.stubs_version {
return
}
}
ls.client.log_message('Stubs version mismatch, unpacking new stubs', .info)
loglib.info('Stubs version mismatch, unpacking new stubs')
os.rmdir_all(config.analyzer_stubs_path) or {
ls.client.log_message('Failed to remove old stubs: ${err}', .error)
loglib.with_fields({
'err': err.str()
}).error('Failed to remove old stubs')
}
}
stubs := metadata.embed_fs()
stubs.unpack_to(config.analyzer_stubs_path) or {
ls.client.log_message('Failed to unpack stubs: ${err}', .error)
loglib.with_fields({
'err': err.str()
}).error('Failed to unpack stubs')
}
os.write_file(config.analyzer_stubs_version_path, ls.stubs_version.str()) or {
ls.client.log_message('Failed to write stubs version: ${err}', .error)
loglib.with_fields({
'err': err.str()
}).error('Failed to write stubs version')
}
}
// shutdown sets the state to shutdown but does not exit
@[noreturn]
pub fn (mut ls LanguageServer) shutdown() {
ls.bg.stop()
ls.status = .shutdown
ls.main_parser.free()
ls.exit()
}
// exit stops the process
@[noreturn]
pub fn (mut ls LanguageServer) exit() {
// move exit to shutdown for now
// == .shutdown => 0
// != .shutdown => 1
ecode := int(ls.status != .shutdown)
loglib.info('v-analyzer exiting with ${ls.status}, exit code: ${ecode}')
exit(ecode)
}
fn (mut ls LanguageServer) print_info(process_id int, client_info lsp.ClientInfo) {
arch := if runtime.is_64bit() { 64 } else { 32 }
client_name := if client_info.name.len != 0 {
'${client_info.name} ${client_info.version}'
} else {
'Unknown'
}
ls.client.log_message('v-analyzer version: ${metadata.manifest.version}, commit: ${metadata.build_commit}, OS: ${os.user_os()} x${arch}',
.info)
ls.client.log_message('v-analyzer executable path: ${os.executable()}', .info)
ls.client.log_message('v-analyzer build with V ${@VHASH}', .info)
ls.client.log_message('v-analyzer build at ${metadata.build_datetime}', .info)
ls.client.log_message('Client / Editor: ${client_name} (PID: ${process_id})', .info)
loglib.with_fields({
'client_name': client_name
'process_id': process_id.str()
'os': os.user_os()
'arch': 'x${arch}'
'executable': os.executable()
'build_with': @VHASH
'build_at': metadata.build_datetime
'build_commit': metadata.build_commit
}).info('v-analyzer started')
}
================================================
FILE: src/server/hints/InlayHintsVisitor.v
================================================
module hints
import lsp
import config
import analyzer.psi
import analyzer.psi.types
pub struct InlayHintsVisitor {
pub:
cfg config.InlayHintsConfig
pub mut:
lines int
result []lsp.InlayHint = []lsp.InlayHint{cap: 1000}
}
pub fn (mut v InlayHintsVisitor) accept(root psi.PsiElement) {
file := root.containing_file() or { return }
v.lines = file.source_text.count('\n')
mut walker := psi.new_tree_walker(root.node())
defer { walker.free() }
for {
node := walker.next() or { break }
v.process_node(node, file)
}
}
pub fn (mut v InlayHintsVisitor) process_node(node psi.AstNode, containing_file &psi.PsiFile) {
if node.type_name == .range && v.cfg.enable_range_hints {
operator := node.child_by_field_name('operator') or { return }
start_point := operator.start_point()
end_point := operator.end_point()
need_left := if _ := node.child_by_field_name('start') { true } else { false }
need_right := if _ := node.child_by_field_name('end') { true } else { false }
if need_left {
v.result << lsp.InlayHint{
position: lsp.Position{
line: int(start_point.row)
character: int(start_point.column)
}
label: '≤'
kind: .type_
}
}
if need_right {
v.result << lsp.InlayHint{
position: lsp.Position{
line: int(end_point.row)
character: int(end_point.column)
}
label: '<'
kind: .type_
}
}
return
}
if node.type_name == .const_definition && v.cfg.enable_constant_type_hints {
element := psi.create_element(node, containing_file)
if element is psi.ConstantDefinition {
if element.name() == '_' {
return
}
range := element.identifier_text_range()
v.result << lsp.InlayHint{
position: lsp.Position{
line: range.line
character: range.end_column
}
label: ': ' + element.get_type().readable_name()
kind: .type_
}
}
}
if v.cfg.enable_type_hints {
def := psi.node_to_var_definition(node, containing_file, none)
if !isnil(def) && def.name() != '_' {
typ := def.get_type()
range := def.text_range()
v.result << lsp.InlayHint{
position: lsp.Position{
line: range.line
character: range.end_column
}
label: ': ' + typ.readable_name()
kind: .type_
}
}
}
if node.type_name == .or_block_expression && v.cfg.enable_implicit_err_hints {
expression_node := node.first_child() or { return }
expression := psi.create_element(expression_node, containing_file)
typ := psi.infer_type(expression)
if typ !is types.ResultType {
// show `err ->` hint only for `Result` type
return
}
or_block := node.last_child() or { return }
block := or_block.child_by_field_name('block') or { return }
v.handle_implicit_error_variable(block)
}
if node.type_name == .else_branch && v.cfg.enable_implicit_err_hints {
v.handle_if_unwrapping(node, containing_file)
}
if node.type_name == .call_expression && v.cfg.enable_parameter_name_hints {
v.handle_call_expression(node, containing_file)
}
if node.type_name == .enum_field_definition && v.cfg.enable_enum_field_value_hints {
v.handle_enum_field(node, containing_file)
}
}
pub fn (mut v InlayHintsVisitor) handle_enum_field(enum_field psi.AstNode, containing_file &psi.PsiFile) {
element := psi.create_element(enum_field, containing_file)
if element is psi.EnumFieldDeclaration {
if _ := element.value() {
// don't show hint for enum fields with explicit values
return
}
value_presentation := element.value_presentation(true)
text_range := element.text_range()
v.result << lsp.InlayHint{
position: lsp.Position{
line: int(text_range.line)
character: int(text_range.end_column)
}
label: ' = ${value_presentation}'
kind: .type_
}
}
}
pub fn (mut v InlayHintsVisitor) handle_call_expression(call psi.AstNode, containing_file &psi.PsiFile) {
if v.lines > 1000 {
// don't show this hints for large files
return
}
call_expression := psi.create_element(call, containing_file)
if call_expression is psi.CallExpression {
arguments := call_expression.arguments()
called := call_expression.resolve() or { return }
if called is psi.SignatureOwner {
signature := called.signature() or { return }
for i, param in signature.parameters() {
name := if param is psi.ParameterDeclaration { param.name() } else { '_' }
if name == '_' {
continue
}
arg := arguments[i] or { continue }
if arg.node().type_name == .keyed_element {
// don't show hint for named arguments
continue
}
arg_inner := if arg.node().type_name == .mutable_expression {
arg.last_child() or { continue }
} else {
arg
}
if arg_inner.text_matches(name) {
// don't show hint if argument name matches parameter name
continue
}
arg_range := arg.text_range()
v.result << lsp.InlayHint{
position: lsp.Position{
line: arg_range.line
character: arg_range.column
}
label: '${name}: '
kind: .parameter
}
}
}
}
}
pub fn (mut v InlayHintsVisitor) handle_if_unwrapping(node psi.AstNode, containing_file &psi.PsiFile) {
parent_if_expression := node.parent_of_type(.if_expression) or { return }
guard := parent_if_expression.child_by_field_name('guard') or { return }
expression_list := guard.child_by_field_name('expression_list') or { return }
expression := expression_list.first_child() or { return }
expr_type := psi.infer_type(psi.create_element(expression, containing_file))
if expr_type !is types.ResultType {
// show `err ->` hint only for `Result` type
return
}
block := node.child_by_field_name('block') or { return }
v.handle_implicit_error_variable(block)
}
pub fn (mut v InlayHintsVisitor) handle_implicit_error_variable(block psi.AstNode) {
start_point := block.start_point()
end_point := block.end_point()
if start_point.row == end_point.row {
// don't show hint if 'or { ... }'
return
}
v.result << lsp.InlayHint{
position: lsp.Position{
line: int(start_point.row)
character: int(start_point.column + 1)
}
label: ' err →'
kind: .parameter
}
}
================================================
FILE: src/server/inspections/Report.v
================================================
module inspections
import analyzer.psi
pub enum ReportKind {
error
warning
notice
}
pub struct Report {
pub:
kind ReportKind
code string
message string
filepath string
source string
range psi.TextRange
}
================================================
FILE: src/server/inspections/ReportsSource.v
================================================
module inspections
import lsp
pub interface ReportsSource {
mut:
process(uri lsp.DocumentUri) []Report
}
================================================
FILE: src/server/inspections/compiler/CompilerReportsSource.v
================================================
module compiler
import lsp
import server.inspections
pub struct CompilerReportsSource {
pub:
compiler_path string
}
pub fn (mut c CompilerReportsSource) process(uri lsp.DocumentUri, project_root string) []inspections.Report {
reports := exec_compiler_diagnostics(c.compiler_path, uri, project_root) or { return [] }
return reports
}
================================================
FILE: src/server/inspections/compiler/utils.v
================================================
module compiler
import os
import lsp
import term
import server.inspections
import analyzer.psi
fn parse_compiler_diagnostic(msg string) ?inspections.Report {
lines := msg.split_into_lines()
if lines.len == 0 {
return none
}
mut err_underline := ''
for line in lines {
if line.contains('~') {
err_underline = line
break
}
}
underline_width := err_underline.count('~')
first_line := lines.first()
line_colon_idx :=
first_line.index_after(':', 2) or { return none } // deal with `d:/v/...:2:4: error: ...`
mut filepath := first_line[..line_colon_idx]
$if windows {
filepath = filepath.replace('/', '\\')
}
col_colon_idx := first_line.index_after(':', line_colon_idx + 1) or { return none }
colon_sep_idx := first_line.index_after(':', col_colon_idx + 1) or { return none }
msg_type_colon_idx := first_line.index_after(':', colon_sep_idx + 1) or { return none }
line_nr := first_line[line_colon_idx + 1..col_colon_idx].int() - 1
col_nr := first_line[col_colon_idx + 1..colon_sep_idx].int() - 1
msg_type := first_line[colon_sep_idx + 1..msg_type_colon_idx].trim_space()
msg_content := first_line[msg_type_colon_idx + 1..].trim_space()
diag_kind := match msg_type {
'error' { inspections.ReportKind.error }
'warning' { inspections.ReportKind.warning }
//'notice' { inspections.ReportKind.notice }
else { inspections.ReportKind.notice }
}
return inspections.Report{
range: psi.TextRange{
line: line_nr
column: col_nr
end_line: line_nr
end_column: col_nr + underline_width
}
kind: diag_kind
message: msg_content
filepath: filepath
}
}
fn exec_compiler_diagnostics(compiler_path string, uri lsp.DocumentUri, project_root string) ?[]inspections.Report {
filepath := uri.path()
is_script := filepath.ends_with('.vsh') || filepath.ends_with('.vv')
check_target := if is_script { filepath } else { project_root }
res := os.execute('${compiler_path} -enable-globals -shared -check ${check_target}')
if res.exit_code == 0 {
return none
}
output_lines := res.output.split_into_lines().map(term.strip_ansi(it))
errors := split_lines_to_errors(output_lines)
current_file_abs := os.real_path(filepath)
mut reports := []inspections.Report{}
for error in errors {
report := parse_compiler_diagnostic(error) or { continue }
// ignore this error
if report.message.contains('unexpected eof') {
continue
}
report_file_abs := os.real_path(report.filepath)
if os.to_slash(report_file_abs) != os.to_slash(current_file_abs) {
continue
}
reports << inspections.Report{
...report
filepath: filepath
}
}
return reports
}
fn split_lines_to_errors(lines []string) []string {
mut result := []string{}
mut last_error := ''
for _, line in lines {
if line.starts_with(' ') {
// additional context of an error
last_error += '\n' + line
} else {
if last_error.len > 0 {
result << last_error
}
last_error = line
}
}
if last_error.len > 0 {
result << last_error
}
return result
}
================================================
FILE: src/server/intentions/AddFlagAttributeIntention.v
================================================
module intentions
import lsp
import analyzer.psi
import server.tform
import server.file_diff
pub struct AddFlagAttributeIntention {
id string = 'v-analyzer.add_flag_attribute'
name string = 'Add [flag] attribute'
}
fn (_ &AddFlagAttributeIntention) is_available(ctx IntentionContext) bool {
pos := tform.lsp_position_to_position(ctx.position)
declaration := find_declaration_at_pos(ctx.containing_file, pos) or { return false }
if declaration is psi.EnumDeclaration {
return !declaration.is_flag()
}
return false
}
fn (_ &AddFlagAttributeIntention) invoke(ctx IntentionContext) ?lsp.WorkspaceEdit {
pos := tform.lsp_position_to_position(ctx.position)
declaration := find_declaration_at_pos(ctx.containing_file, pos) or { return none }
start_line := declaration.identifier_text_range().line
uri := ctx.containing_file.uri()
mut diff := file_diff.Diff.for_file(uri)
diff.append_as_prev_line(start_line, '[flag]')
return diff.to_workspace_edit()
}
================================================
FILE: src/server/intentions/AddHeapAttributeIntention.v
================================================
module intentions
import lsp
import analyzer.psi
import server.tform
import server.file_diff
pub struct AddHeapAttributeIntention {
id string = 'v-analyzer.add_heap_attribute'
name string = 'Add [heap] attribute'
}
fn (_ &AddHeapAttributeIntention) is_available(ctx IntentionContext) bool {
pos := tform.lsp_position_to_position(ctx.position)
declaration := find_declaration_at_pos(ctx.containing_file, pos) or { return false }
if declaration is psi.StructDeclaration {
return !declaration.is_heap()
}
return false
}
fn (_ &AddHeapAttributeIntention) invoke(ctx IntentionContext) ?lsp.WorkspaceEdit {
pos := tform.lsp_position_to_position(ctx.position)
declaration := find_declaration_at_pos(ctx.containing_file, pos) or { return none }
start_line := declaration.identifier_text_range().line
uri := ctx.containing_file.uri()
mut diff := file_diff.Diff.for_file(uri)
diff.append_as_prev_line(start_line, '[heap]')
return diff.to_workspace_edit()
}
================================================
FILE: src/server/intentions/CompilerQuickFix.v
================================================
module intentions
pub interface CompilerQuickFix {
Intention
is_matched_message(msg string) bool
}
================================================
FILE: src/server/intentions/ImportModuleQuickFix.v
================================================
module intentions
import lsp
import analyzer.psi
import server.tform
import server.file_diff
pub struct ImportModuleQuickFix {
id string = 'v-analyzer.import_module'
name string = 'Import module'
}
fn (_ &ImportModuleQuickFix) is_matched_message(msg string) bool {
return msg.contains('undefined ident')
}
fn (_ &ImportModuleQuickFix) is_available(ctx IntentionContext) bool {
pos := tform.lsp_position_to_position(ctx.position)
element := ctx.containing_file.find_element_at_pos(pos) or { return false }
reference_expression := element.parent_of_type(.reference_expression) or { return false }
if reference_expression is psi.ReferenceExpression {
if _ := reference_expression.qualifier() {
return false
}
module_name := reference_expression.get_text()
modules := stubs_index.get_modules_by_name(module_name)
if modules.len == 0 {
return false
}
return true
}
return false
}
fn (_ &ImportModuleQuickFix) invoke(ctx IntentionContext) ?lsp.WorkspaceEdit {
uri := ctx.containing_file.uri()
pos := tform.lsp_position_to_position(ctx.position)
element := ctx.containing_file.find_element_at_pos(pos)?
reference_expression := element.parent_of_type(.reference_expression)?
module_name := reference_expression.get_text()
modules := stubs_index.get_modules_by_name(module_name)
if modules.len == 0 {
return none
}
mod := modules.first()
file := mod.containing_file() or { return none }
module_fqn := file.module_fqn()
imports := ctx.containing_file.get_imports()
mut extra_newline := ''
mut line_to_insert := 0
if imports.len > 0 {
line_to_insert = imports.last().text_range().line + 1
} else if mod_clause := ctx.containing_file.module_clause() {
line_to_insert = mod_clause.text_range().line + 2
extra_newline = '\n'
} else {
extra_newline = '\n'
line_to_insert = 0
}
mut diff := file_diff.Diff.for_file(uri)
diff.append_as_prev_line(line_to_insert, 'import ' + module_fqn + extra_newline)
return diff.to_workspace_edit()
}
================================================
FILE: src/server/intentions/Intention.v
================================================
module intentions
import lsp
import analyzer.psi
pub struct IntentionContext {
// file where this intention is available.
containing_file &psi.PsiFile
// position where this intention is available.
position lsp.Position
}
pub fn IntentionContext.from(containing_file &psi.PsiFile, position lsp.Position) IntentionContext {
return IntentionContext{
containing_file: containing_file
position: position
}
}
// Intention actions are invoked by pressing Alt-Enter in the code
// editor at the location where an intention is available.
pub interface Intention {
// unique id of the intention.
// This id is equivalent to the id of the command that is
// invoked when user selects this intention.
id string
name string // name to be shown in the list of available actions, if this action is available.
// is_available checks whether this intention is available at a caret offset in the file.
// If this method returns true, a light bulb for this intention is shown.
is_available(ctx IntentionContext) bool
// invoke called when user invokes intention. This method is called inside command.
invoke(ctx IntentionContext) ?lsp.WorkspaceEdit
}
================================================
FILE: src/server/intentions/MakeMutableQuickFix.v
================================================
module intentions
import lsp
import analyzer.psi
import server.file_diff
import server.tform
pub struct MakeMutableQuickFix {
id string = 'v-analyzer.make_mutable'
name string = 'Make mutable'
}
fn (_ &MakeMutableQuickFix) is_matched_message(msg string) bool {
return msg.contains('is immutable')
}
fn (_ &MakeMutableQuickFix) is_available(_ IntentionContext) bool {
return true
}
fn (_ &MakeMutableQuickFix) invoke(ctx IntentionContext) ?lsp.WorkspaceEdit {
pos := tform.lsp_position_to_position(ctx.position)
ref := find_reference_at_pos(ctx.containing_file, pos)?
uri := ctx.containing_file.uri()
mut diff := file_diff.Diff.for_file(uri)
resolved := ref.resolve()?
text_range := resolved.text_range()
if resolved is psi.MutabilityOwner {
if resolved.is_mutable() {
return none
}
mut column := text_range.column
if resolved is psi.Receiver {
column += 1
}
diff.append_to(text_range.line, column, 'mut ')
}
return diff.to_workspace_edit()
}
================================================
FILE: src/server/intentions/MakePublicIntention.v
================================================
module intentions
import lsp
import analyzer.psi
import server.tform
import server.file_diff
pub struct MakePublicIntention {
id string = 'v-analyzer.make_public'
name string = 'Make public'
}
fn (_ &MakePublicIntention) is_available(ctx IntentionContext) bool {
pos := tform.lsp_position_to_position(ctx.position)
declaration := find_declaration_at_pos(ctx.containing_file, pos) or { return false }
return !declaration.is_public()
}
fn (_ &MakePublicIntention) invoke(ctx IntentionContext) ?lsp.WorkspaceEdit {
pos := tform.lsp_position_to_position(ctx.position)
declaration := find_declaration_at_pos(ctx.containing_file, pos)?
uri := ctx.containing_file.uri()
mut start_line := declaration.identifier_text_range().line
if declaration is psi.ConstantDefinition {
decl := declaration.parent()?
if decl is psi.ConstantDeclaration {
start_line = decl.text_range().line
}
}
mut diff := file_diff.Diff.for_file(uri)
diff.append_to_begin(start_line, 'pub ')
return diff.to_workspace_edit()
}
================================================
FILE: src/server/intentions/utils.v
================================================
module intentions
import analyzer.psi
fn find_declaration_at_pos(file &psi.PsiFile, pos psi.Position) ?psi.PsiNamedElement {
element := file.find_element_at_pos(pos) or { return none }
if element !is psi.Identifier {
return none
}
parent := element.parent() or { return none }
if parent is psi.PsiNamedElement {
return parent
}
if parent.node().type_name == .overridable_operator {
grand := parent.parent() or { return none }
if grand is psi.PsiNamedElement {
return grand
}
}
return none
}
fn find_reference_at_pos(file &psi.PsiFile, pos psi.Position) ?&psi.ReferenceExpression {
element := file.find_element_at_pos(pos) or { return none }
parent := element.parent() or { return none }
if parent is psi.ReferenceExpression {
return parent
}
return none
}
================================================
FILE: src/server/language_server.v
================================================
module server
import json
import jsonrpc
import lsp
import time
import analyzer
import analyzer.parser
import os
import config
import loglib
import server.progress
import server.protocol
import server.intentions
import server.workspace
pub enum ServerStatus {
off
initialized
shutdown
}
pub struct LanguageServer {
pub mut:
// status is the current status of the server.
status ServerStatus = .off
// root_uri is the URI of the workspace root.
root_uri lsp.DocumentUri
// client is a wrapper over `jsonrpc.ResponseWriter` that
// can be used to send notifications and responses to the client.
client &protocol.Client = unsafe { nil }
// client_pid is the process ID of this server.
client_pid int
writer &ResponseWriter = unsafe { nil }
// opened_files describes all open files in the editor.
//
// When a file is opened, the `did_open` method is called,
// which adds the file to `opened_files`.
//
// When the file is closed, the `did_close` method is called,
// which removes the file from `opened_files`.
opened_files map[lsp.DocumentUri]analyzer.OpenedFile
paths struct {
mut:
// vmodules_root is the path to the vmodules directory.
vmodules_root string
// vroot is the path to the directory of the V compiler.
vroot string
// vexe is the path to the V compiler.
vexe string
// vlib_root is the path to the directory of the V standard library.
vlib_root string
// cache_dir is the path to the directory with the cache.
cache_dir string
}
// stubs_version incremented on each change in stubs
//
// See also `LanguageServer.setup_stubs()`
stubs_version int = 4
// initialization_options is a list of custom initialization options.
// Used to pass custom options in tests.
initialization_options []string
// cfg describes the editor configuration from `config.toml`.
cfg config.EditorConfig
// bg is a background thread that is designed to perform long-running operations,
// such as file analyze with third-party tools.
bg BackgroundThread
// reporter is used to report diagnostics to the client.
reporter &DiagnosticReporter = &DiagnosticReporter{}
// intentions is a map of all intentions that are available in the editor.
// Use `LanguageServer.register_intention()` to register a new intention.
intentions map[string]intentions.Intention
// compiler_quick_fixes is a map of all quick fixes for compiler errors
// that are available in the editor.
// Use `LanguageServer.register_compiler_quick_fix()` to register a new quick fix.
compiler_quick_fixes map[string]intentions.CompilerQuickFix
// progress is used to report progress to the client.
// For now it is used only to report progress of indexing.
progress &progress.Tracker = unsafe { nil }
// indexing_mng is used to manage indexing.
indexing_mng analyzer.IndexingManager
// project_resolver is used to resolve the project root for a given file.
project_resolver &workspace.ProjectResolver = unsafe { nil }
// main_parser is the parser used only in main thread for handling didChange requests.
main_parser &parser.Parser = parser.Parser.new()
}
pub fn LanguageServer.new(indexing analyzer.IndexingManager) &LanguageServer {
return &LanguageServer{
indexing_mng: indexing
writer: unsafe { nil } // will be initialized in `initialize`
client: unsafe { nil } // will be initialized in `initialize`
progress: unsafe { nil } // will be initialized in `initialize`
}
}
pub fn (mut _ LanguageServer) stubs_root() ?string {
if !os.exists(config.analyzer_stubs_path) {
return none
}
return config.analyzer_stubs_path
}
pub fn (mut ls LanguageServer) get_file(uri lsp.DocumentUri) ?analyzer.OpenedFile {
return ls.opened_files[uri] or {
loglib.with_fields({
'uri': uri.str()
}).warn('Cannot find file in opened_files')
return none
}
}
pub fn (mut ls LanguageServer) handle_jsonrpc(request &jsonrpc.Request, mut rw jsonrpc.ResponseWriter) ! {
// initialize writer upon receiving the first request
if isnil(ls.writer) {
ls.writer = rw.server.writer(own_buffer: true)
}
watch := time.new_stopwatch(auto_start: true)
mut w := unsafe { &ResponseWriter(rw) }
// The server will log a send request/notification
// log based on the the received payload since the spec
// doesn't indicate a way to log on the client side and
// notify it to the server.
//
// Notification has no ID attached so the server can detect
// if its a notification or a request payload by checking
// if the ID is empty.
match request.method {
// Note: LSP specification is unclear whether or not
// a shutdown request is allowed before server init
// but we'll just put it here since we want to formally
// shutdown the server after a certain timeout period.
'shutdown' {
ls.shutdown()
return
}
'exit' {
ls.exit()
return
}
else {}
}
if ls.status != .initialized {
if request.method == 'initialize' {
params := json.decode(lsp.InitializeParams, request.params) or { return err }
w.write(ls.initialize(params, mut rw))
} else if ls.status == .shutdown {
return jsonrpc.invalid_request
} else {
return jsonrpc.server_not_initialized
}
loglib.with_fields({
'method': request.method
'duration': watch.elapsed().str()
}).log_one(.info, 'Request finished')
return
}
match request.method {
// not only requests but also notifications
'initialized' {
ls.initialized(mut rw)
}
'textDocument/didOpen' {
params := json.decode(lsp.DidOpenTextDocumentParams, request.params) or { return err }
ls.did_open(params)
}
'textDocument/didSave' {
params := json.decode(lsp.DidSaveTextDocumentParams, request.params) or { return err }
ls.did_save(params)
}
'textDocument/didChange' {
params := json.decode(lsp.DidChangeTextDocumentParams, request.params) or { return err }
ls.did_change(params)
}
'textDocument/didClose' {
params := json.decode(lsp.DidCloseTextDocumentParams, request.params) or { return err }
ls.did_close(params)
}
'textDocument/willSave' {
// params := json.decode(lsp.WillSaveTextDocumentParams, request.params) or {
// return err
// }
// ls.will_save(params)
}
'textDocument/formatting' {
params := json.decode(lsp.DocumentFormattingParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.formatting(params) or { return w.wrap_error(err) })
}
'textDocument/documentSymbol' {
params := json.decode(lsp.DocumentSymbolParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.document_symbol(params) or { return w.wrap_error(err) })
}
'workspace/symbol' {
params := json.decode(lsp.WorkspaceSymbolParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.workspace_symbol(params) or { return w.wrap_error(err) })
}
'textDocument/signatureHelp' {
params := json.decode(lsp.SignatureHelpParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.signature_help(params) or { return w.wrap_error(err) })
}
'textDocument/completion' {
params := json.decode(lsp.CompletionParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.completion(params) or { return w.wrap_error(err) })
}
'textDocument/hover' {
params := json.decode(lsp.HoverParams, request.params) or { return w.wrap_error(err) }
hover_data := ls.hover(params) or {
w.write_empty()
return
}
w.write(hover_data)
}
'textDocument/foldingRange' {
params := json.decode(lsp.FoldingRangeParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.folding_range(params) or { return w.wrap_error(err) })
}
'textDocument/definition' {
params := json.decode(lsp.TextDocumentPositionParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.definition(params) or { return w.wrap_error(err) })
}
'textDocument/typeDefinition' {
params := json.decode(lsp.TextDocumentPositionParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.type_definition(params) or { return w.wrap_error(err) })
}
'textDocument/references' {
params := json.decode(lsp.ReferenceParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.references(params))
}
'textDocument/implementation' {
params := json.decode(lsp.TextDocumentPositionParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.implementation(params) or { return w.wrap_error(err) })
}
'workspace/didChangeWatchedFiles' {
params := json.decode(lsp.DidChangeWatchedFilesParams, request.params) or { return err }
ls.did_change_watched_files(params)
}
'textDocument/codeLens' {
params := json.decode(lsp.CodeLensParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.code_lens(params) or { return w.wrap_error(err) })
}
'textDocument/inlayHint' {
params := json.decode(lsp.InlayHintParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.inlay_hints(params) or { return w.wrap_error(err) })
}
'textDocument/prepareRename' {
params := json.decode(lsp.PrepareRenameParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.prepare_rename(params) or { return w.wrap_error(err) })
}
'textDocument/rename' {
params := json.decode(lsp.RenameParams, request.params) or { return w.wrap_error(err) }
w.write(ls.rename(params) or { return w.wrap_error(err) })
}
'textDocument/documentLink' {}
'textDocument/semanticTokens/full' {
params := json.decode(lsp.SemanticTokensParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.semantic_tokens(params.text_document, lsp.Range{}) or {
return w.wrap_error(err)
})
}
'textDocument/semanticTokens/range' {
params := json.decode(lsp.SemanticTokensRangeParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.semantic_tokens(params.text_document, params.range) or {
return w.wrap_error(err)
})
}
'textDocument/documentHighlight' {
params := json.decode(lsp.TextDocumentPositionParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.document_highlight(params) or { return w.wrap_error(err) })
}
'textDocument/codeAction' {
params := json.decode(lsp.CodeActionParams, request.params) or {
return w.wrap_error(err)
}
w.write(ls.code_actions(params) or { return w.wrap_error(err) })
}
'workspace/executeCommand' {
params := json.decode(lsp.ExecuteCommandParams, request.params) or {
return w.wrap_error(err)
}
ls.execute_command(params)
}
'v-analyzer/viewStubTree' {
params := json.decode(lsp.TextDocumentIdentifier, request.params) or {
return w.wrap_error(err)
}
w.write(ls.view_stub_tree(params) or { return w.wrap_error(err) })
}
'$/cancelRequest' {
loglib.info('got $/cancelRequest request')
}
else {
loglib.with_fields({
'method': request.method
'params': request.params
}).info('unhandled method call')
}
}
loglib.with_fields({
'method': request.method
'duration': watch.elapsed().str()
}).log_one(.info, 'Request finished')
}
pub fn (mut ls LanguageServer) register_intention(intention intentions.Intention) {
ls.intentions[intention.id] = intention
}
pub fn (mut ls LanguageServer) register_compiler_quick_fix(quickfix intentions.CompilerQuickFix) {
ls.compiler_quick_fixes[quickfix.id] = quickfix
}
// launch_tool launches a tool with the same vroot as the language server
// and returns the process.
//
// Example:
// ```
// p := ls.launch_tool('tool', 'arg1', 'arg2')!
// defer {
// p.close()
// }
// p.wait()
// ```
pub fn (mut ls LanguageServer) launch_tool(args ...string) !&os.Process {
mut p := os.new_process(ls.paths.vexe)
p.set_args(args)
p.set_redirect_stdio()
return p
}
================================================
FILE: src/server/progress/progress.v
================================================
module progress
import lsp
import server.protocol
pub struct Tracker {
pub mut:
support_work_done_progress bool
client &protocol.Client = unsafe { nil }
}
pub fn new_tracker(mut client protocol.Client) &Tracker {
return &Tracker{
client: client
}
}
pub fn (mut t Tracker) start(title string, message string, token lsp.ProgressToken) &WorkDone {
mut wd := &WorkDone{
token: token
client: t.client
}
if !t.support_work_done_progress {
t.client.show_message(message, .log)
return wd
}
if wd.token.empty() {
new_token := lsp.generate_progress_token()
t.client.work_done_progress_create(token: new_token)
wd.token = new_token
}
t.client.progress(
token: wd.token
value: lsp.WorkDoneProgressPayload{
kind: 'begin'
title: title
message: message
percentage: 0
cancellable: false
}
)
return wd
}
@[heap]
pub struct WorkDone {
pub mut:
token lsp.ProgressToken
client &protocol.Client
}
pub fn (mut wd WorkDone) progress(message string, percentage u32) {
if wd.token.empty() {
return
}
wd.client.progress(
token: wd.token
value: lsp.WorkDoneProgressPayload{
kind: 'report'
message: message.trim_string_right('\n')
percentage: percentage
}
)
}
pub fn (mut wd WorkDone) end(message string) {
if wd.token.empty() {
wd.client.show_message(message, .info)
return
}
wd.client.progress(
token: wd.token
value: lsp.WorkDoneProgressPayload{
kind: 'end'
message: message
percentage: 100
}
)
}
================================================
FILE: src/server/protocol/Client.v
================================================
module protocol
import jsonrpc
import lsp
@[heap]
pub struct Client {
mut:
wr jsonrpc.ResponseWriter
}
pub fn new_client(mut wr jsonrpc.ResponseWriter) &Client {
return &Client{
wr: wr
}
}
pub fn (mut c Client) work_done_progress_create(params lsp.WorkDoneProgressCreateParams) {
c.wr.write_request('window/workDoneProgress/create', params)
}
pub fn (mut c Client) progress(params lsp.ProgressParams) {
c.wr.write_notify('$/progress', params)
}
// log_message sends a window/logMessage notification to the client
pub fn (mut c Client) log_message(message string, typ lsp.MessageType) {
$if test {
if c == unsafe { nil } {
return
}
}
c.wr.write_notify('window/logMessage', lsp.LogMessageParams{
@type: typ
message: message
})
}
// show_message sends a window/showMessage notification to the client
pub fn (mut c Client) show_message(message string, typ lsp.MessageType) {
c.wr.write_notify('window/showMessage', lsp.ShowMessageParams{
@type: typ
message: message
})
}
pub fn (mut c Client) show_message_request(message string, actions []lsp.MessageActionItem, typ lsp.MessageType) {
c.wr.write_notify('window/showMessageRequest', lsp.ShowMessageRequestParams{
@type: typ
message: message
actions: actions
})
}
pub fn (mut c Client) send_server_status(params lsp.ServerStatusParams) {
c.wr.write_notify('experimental/serverStatus', params)
}
pub fn (mut c Client) apply_edit(params lsp.ApplyWorkspaceEditParams) {
c.wr.write_request('workspace/applyEdit', params)
}
================================================
FILE: src/server/semantic/DumbAwareSemanticVisitor.v
================================================
module semantic
import lsp
import utils
import analyzer.psi
// DumbAwareSemanticVisitor is a highly optimized visitor that collects information about
// semantic tokens in a file based only on their syntax tree.
// This annotator must not call resolve or use indexes.
pub struct DumbAwareSemanticVisitor {
start u32 // start offset when request range is specified
end u32 // end offset when request range is specified
with_range bool // whether request range is specified
}
pub fn new_dumb_aware_semantic_visitor(range lsp.Range, containing_file &psi.PsiFile) DumbAwareSemanticVisitor {
start := utils.compute_offset(containing_file.source_text, range.start.line,
range.start.character)
end := utils.compute_offset(containing_file.source_text, range.end.line, range.end.character)
return DumbAwareSemanticVisitor{
with_range: !range.is_empty()
start: u32(start)
end: u32(end)
}
}
pub fn (v DumbAwareSemanticVisitor) accept(root psi.PsiElement) []SemanticToken {
mut result := []SemanticToken{cap: 500}
mut walker := psi.new_tree_walker(root.node())
defer { walker.free() }
for {
node := walker.next() or { break }
range := node.range()
if v.with_range && (range.end_byte <= v.start || range.start_byte >= v.end) {
continue
}
v.highlight_node(node, root, mut result)
}
return result
}
@[inline]
fn (_ DumbAwareSemanticVisitor) highlight_node(node psi.AstNode, root psi.PsiElement, mut result []SemanticToken) {
containing_file := root.containing_file() or { return }
source_text := containing_file.source_text
match node.type_name {
.enum_field_definition {
if first_child := node.first_child() {
result << element_to_semantic(first_child, .enum_member)
}
}
.field_name {
result << element_to_semantic(node, .property)
}
.range_clause {
if first_child := node.first_child() {
result << element_to_semantic(first_child, .property)
}
}
.struct_field_declaration {
if first_child := node.first_child() {
if first_child.type_name != .embedded_definition {
result << element_to_semantic(first_child, .property)
}
}
}
.module_clause {
if last_child := node.last_child() {
result << element_to_semantic(last_child, .namespace)
}
}
.attribute {
// '['
if first_child := node.first_child() {
result << element_to_semantic(first_child, .decorator)
}
// ']'
if last_child := node.last_child() {
result << element_to_semantic(last_child, .decorator)
}
}
.key_value_attribute {
if value_child := node.child_by_field_name('value') {
if value_child.type_name == .identifier {
result << element_to_semantic(value_child, .string)
}
}
}
.qualified_type {
if first_child := node.first_child() {
result << element_to_semantic(first_child, .namespace)
}
if last_child := node.last_child() {
result << element_to_semantic(last_child, .type_)
}
}
.unknown {
text := node.text(source_text)
if text == 'sql' {
if parent := node.parent() {
if parent.type_name == .sql_expression {
result << element_to_semantic(node, .keyword)
}
}
} else if text == 'chan' {
if parent := node.parent() {
if parent.type_name == .channel_type {
result << element_to_semantic(node, .keyword)
}
}
} else if text == 'thread' {
if parent := node.parent() {
if parent.type_name == .thread_type {
result << element_to_semantic(node, .keyword)
}
}
} else if text == 'implements' {
if parent := node.parent() {
if parent.type_name in [.struct_declaration, .interface_declaration,
.implements_clause] {
result << element_to_semantic(node, .keyword)
}
}
}
}
.enum_declaration {
if identifier := node.child_by_field_name('name') {
result << element_to_semantic(identifier, .enum_)
}
}
.interface_declaration {
if identifier := node.child_by_field_name('name') {
result << element_to_semantic(identifier, .interface_)
}
}
.parameter_declaration, .receiver {
if identifier := node.child_by_field_name('name') {
if _ := node.child_by_field_name('mutability') {
result << element_to_semantic(identifier, .parameter, 'mutable')
} else {
result << element_to_semantic(identifier, .parameter)
}
}
}
.reference_expression {
def := psi.node_to_var_definition(node, containing_file, none)
if !isnil(def) {
if def.is_mutable() {
result << element_to_semantic(node, .variable, 'mutable')
} else {
result << element_to_semantic(node, .variable)
}
}
first_char := node.first_char(source_text)
if first_char == `@` || first_char == `$` {
result << element_to_semantic(node, .property) // not a best variant...
}
}
.const_definition {
if name := node.child_by_field_name('name') {
result << element_to_semantic(name, .property) // not a best variant...
}
}
.import_path {
count := node.child_count()
for i in 0 .. count {
if child := node.child(i) {
if child.type_name == .import_name {
result << element_to_semantic(child, .namespace)
}
}
}
}
.import_alias {
if last_child := node.last_child() {
result << element_to_semantic(last_child, .namespace)
}
}
.compile_time_if_expression {
if condition := node.child_by_field_name('condition') {
highlight_compile_time_condition(condition, mut result)
}
}
.asm_statement {
if first := node.first_child() {
result << element_to_semantic(first, .keyword)
}
if modifier := node.child_by_field_name('modifiers') {
result << element_to_semantic(modifier, .keyword)
}
if arch := node.child_by_field_name('arch') {
result << element_to_semantic(arch, .variable, 'readonly', 'defaultLibrary')
}
}
.interpolation_opening, .interpolation_closing {
result << element_to_semantic(node, .keyword)
}
.generic_parameter {
result << element_to_semantic(node, .type_parameter)
}
.variadic_parameter {
result << element_to_semantic(node, .operator)
}
.global_var_definition {
if identifier := node.child_by_field_name('name') {
result << element_to_semantic(identifier, .variable, 'global')
}
if modifiers := node.child_by_field_name('modifiers') {
result << element_to_semantic(modifiers, .keyword)
}
}
.function_declaration {
if first_child := node.child_by_field_name('name') {
first_char := first_child.first_char(source_text)
if first_char in [`@`, `$`] {
// tweak highlighting for @lock/@rlock
result << element_to_semantic(first_child, .function)
}
}
}
else {
$if debug {
// this useful for finding errors in parsing
if node.type_name == .error {
result << element_to_semantic(node, .namespace, 'mutable')
}
}
}
}
}
fn highlight_compile_time_condition(node psi.AstNode, mut result []SemanticToken) {
if node.type_name == .reference_expression {
result << element_to_semantic(node, .variable, 'readonly', 'defaultLibrary')
} else if node.type_name == .binary_expression || node.type_name == .unary_expression {
count := node.child_count()
for i in 0 .. count {
if child := node.child(i) {
highlight_compile_time_condition(child, mut result)
}
}
} else if node.type_name == .parenthesized_expression {
if child := node.child(1) {
highlight_compile_time_condition(child, mut result)
}
}
}
================================================
FILE: src/server/semantic/ResolvingSemanticVisitor.v
================================================
module semantic
import lsp
import utils
import analyzer.psi
pub struct ResolveSemanticVisitor {
start u32 // start offset when request range is specified
end u32 // end offset when request range is specified
with_range bool // whether request range is specified
}
pub fn new_resolve_semantic_visitor(range lsp.Range, containing_file &psi.PsiFile) ResolveSemanticVisitor {
start := utils.compute_offset(containing_file.source_text, range.start.line,
range.start.character)
end := utils.compute_offset(containing_file.source_text, range.end.line, range.end.character)
return ResolveSemanticVisitor{
with_range: !range.is_empty()
start: u32(start)
end: u32(end)
}
}
pub fn (v ResolveSemanticVisitor) accept(root psi.PsiElement) []SemanticToken {
mut result := []SemanticToken{cap: 400}
mut walker := psi.new_psi_tree_walker(root)
defer { walker.free() }
for {
node := walker.next() or { break }
range := node.node().range()
if v.with_range && (range.end_byte <= v.start || range.start_byte >= v.end) {
continue
}
v.highlight_node(node, root, mut result)
}
return result
}
@[inline]
fn (_ ResolveSemanticVisitor) highlight_node(node psi.PsiElement, root psi.PsiElement, mut result []SemanticToken) {
if node is psi.VarDefinition {
if node.is_mutable() {
if identifier := node.identifier() {
result << element_to_semantic(identifier.node(), .variable, 'mutable')
}
}
}
res, first_child := if node is psi.ReferenceExpression || node is psi.TypeReferenceExpression {
res := (node as psi.ReferenceExpressionBase).resolve() or { return }
first_child := (node as psi.PsiElement).node().first_child() or { return }
res, first_child
} else {
return
}
if res is psi.VarDefinition {
mut mods := []string{}
if res.is_mutable() {
mods << 'mutable'
}
result << element_to_semantic(first_child, .variable, ...mods)
} else if res is psi.ConstantDefinition {
result << element_to_semantic(first_child, .property)
} else if res is psi.InterfaceDeclaration {
result << element_to_semantic(first_child, .interface_)
} else if res is psi.StructDeclaration {
if res.name() != 'string' && res.module_name() != 'stubs.attributes' {
result << element_to_semantic(first_child, .struct_)
}
} else if res is psi.EnumDeclaration {
result << element_to_semantic(first_child, .enum_)
} else if res is psi.FieldDeclaration {
result << element_to_semantic(first_child, .property)
} else if res is psi.EnumFieldDeclaration {
result << element_to_semantic(first_child, .enum_member)
} else if res is psi.ParameterDeclaration {
mut mods := []string{}
if res.is_mutable() {
mods << 'mutable'
}
result << element_to_semantic(first_child, .parameter, ...mods)
} else if res is psi.Receiver {
mut mods := []string{}
if res.is_mutable() {
mods << 'mutable'
}
result << element_to_semantic(first_child, .parameter, ...mods)
} else if res is psi.ImportSpec {
result << element_to_semantic(first_child, .namespace)
} else if res is psi.ModuleClause {
result << element_to_semantic(first_child, .namespace)
} else if res is psi.TypeAliasDeclaration {
file := res.containing_file() or { return }
from_stubs := file.path.contains('stubs')
if !from_stubs {
result << element_to_semantic(first_child, .type_)
}
} else if res is psi.GenericParameter {
result << element_to_semantic(first_child, .type_parameter)
} else if res is psi.FunctionOrMethodDeclaration {
result << element_to_semantic(first_child, .function)
} else if res is psi.GlobalVarDefinition {
result << element_to_semantic(first_child, .variable, 'global')
} else if res is psi.EmbeddedDefinition {
result << element_to_semantic(first_child, .struct_)
}
}
================================================
FILE: src/server/semantic/SemanticToken.v
================================================
module semantic
import analyzer.psi
@[json_as_number]
enum SemanticTypes as u32 {
namespace
type_
class
enum_
interface_
struct_
type_parameter
parameter
variable
property
enum_member
event
function
method
macro
keyword
modifier
comment
string
number
regexp
operator
decorator
}
pub struct SemanticToken {
line u32
start u32
len u32
typ SemanticTypes
mods []string
}
@[inline]
fn element_to_semantic(element psi.AstNode, typ SemanticTypes, modifiers ...string) SemanticToken {
start_point := element.start_point()
return SemanticToken{
line: start_point.row
start: start_point.column
len: element.text_length()
typ: typ
mods: modifiers
}
}
================================================
FILE: src/server/semantic/constants.v
================================================
module semantic
pub const semantic_types = [
'namespace',
'type',
'class',
'enum',
'interface',
'struct',
'typeParameter',
'parameter',
'variable',
'property',
'enumMember',
'event',
'function',
'method',
'macro',
'keyword',
'modifier',
'comment',
'string',
'number',
'regexp',
'operator',
'decorator',
]
pub const semantic_modifiers = [
'declaration',
'definition',
'readonly',
'static',
'deprecated',
'abstract',
'async',
'modification',
'documentation',
'defaultLibrary',
'mutable',
'global',
]
================================================
FILE: src/server/semantic/encode.v
================================================
module semantic
// encode encodes an array of semantic tokens into an array of u32s.
// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens
// for more information.
pub fn encode(tokens []SemanticToken) []u32 {
mut result := tokens.clone()
// By specification, the tokens must be sorted.
result.sort_with_compare(fn (left &SemanticToken, right &SemanticToken) int {
if left.line != right.line {
if left.line < right.line {
return -1
}
if left.line > right.line {
return 1
}
}
if left.start < right.start {
return -1
}
if left.start > right.start {
return 1
}
return 0
})
mut res := []u32{len: result.len * 5}
mut cur := 0
mut last := SemanticToken{}
for tok in result {
typ := u32(tok.typ)
if cur == 0 {
res[cur] = tok.line
} else {
res[cur] = tok.line - last.line
}
res[cur + 1] = tok.start
if cur > 0 && res[cur] == 0 {
res[cur + 1] = tok.start - last.start
}
res[cur + 2] = tok.len
res[cur + 3] = typ
res[cur + 4] = if 'mutable' in tok.mods {
u32(0b010000000000)
} else if 'global' in tok.mods {
u32(0b0100000000000)
} else {
u32(0)
} // temp hack
cur += 5
last = tok
}
return res[..cur]
}
================================================
FILE: src/server/setup_test.v
================================================
module server
import os
import lsp
import loglib as _ // import to use __global `logger`
const default_vexe = @VEXE
const default_vroot = os.dir(default_vexe)
const default_vlib_root = os.join_path(default_vroot, 'vlib')
const default_vmodules_root = os.vmodules_dir()
fn test_setup_default_vpaths() {
mut ls := LanguageServer{}
ls.setup()
assert ls.paths.vexe == default_vexe
assert ls.paths.vroot == default_vroot
assert ls.paths.vlib_root == default_vlib_root
assert ls.paths.vmodules_root == default_vmodules_root
}
fn test_setup_custom_vpaths() {
custom_root := os.join_path(os.vtmp_dir(), 'v-analyzer-setup-test')
custom_root_uri := lsp.document_uri_from_path(custom_root)
cfg_dir_path := os.join_path(custom_root, '.v-analyzer')
cfg_path := os.join_path(cfg_dir_path, 'config.toml')
os.mkdir_all(cfg_dir_path)!
defer {
os.rmdir_all(cfg_dir_path) or {}
}
// Test custom_vroot with missing toolchain ==================================
// Use single quotes for literal strings so that paths keep working on Windows.
mut cfg_toml := "custom_vroot = '${custom_root}'"
os.write_file(cfg_path, cfg_toml)!
// Set output(io.Writer) for global loglib logger.
log_file_path := os.join_path(custom_root, 'log')
os.write_file(log_file_path, '')!
mut log_file := os.open_append(os.join_path(custom_root, 'log'))!
logger.out = log_file
// Run setup
mut ls := LanguageServer{}
ls.root_uri = custom_root_uri
ls.setup()
log_file.close()
mut log_out := os.read_file(log_file_path)!
println('Testlog custom_vroot missing toolchain:')
println(log_out.trim_space())
assert log_out.contains('Find custom VROOT path')
assert log_out.contains('Using "${custom_root}" as toolchain')
assert log_out.contains('Failed to find V standard library')
// Test custom_vroot with existing toolchain =================================
cfg_toml = "custom_vroot = '${default_vroot}'"
os.write_file(cfg_path, cfg_toml)!
os.write_file(log_file_path, '')!
log_file = os.open_append(os.join_path(custom_root, 'log'))!
logger.out = log_file
ls = LanguageServer{}
ls.root_uri = custom_root_uri
ls.setup()
log_file.close()
log_out = os.read_file(log_file_path)!
println('Testlog custom_vroot existing toolchain:')
println(log_out.trim_space())
assert log_out.contains('Find custom VROOT path')
assert log_out.contains('Using "${default_vroot}" as toolchain')
assert !log_out.contains('Failed to find standard library path')
}
================================================
FILE: src/server/tform/README.md
================================================
# Description
`tform` module describes various transform functions from analyzer data to LSP data.
================================================
FILE: src/server/tform/tform.v
================================================
module tform
import lsp
import analyzer.psi
// elements_to_locations converts an array of PsiElements to a slice of LSP locations.
pub fn elements_to_locations(elements []psi.PsiElement) []lsp.Location {
mut locations := []lsp.Location{cap: elements.len}
for element in elements {
file := element.containing_file() or { continue }
range := if element is psi.PsiNamedElement {
element.identifier_text_range()
} else {
element.text_range()
}
locations << lsp.Location{
uri: file.uri()
range: text_range_to_lsp_range(range)
}
}
return locations
}
// text_range_to_lsp_range converts a TextRange to an LSP Range.
pub fn text_range_to_lsp_range(pos psi.TextRange) lsp.Range {
return lsp.Range{
start: lsp.Position{
line: pos.line
character: pos.column
}
end: lsp.Position{
line: pos.end_line
character: pos.end_column
}
}
}
// elements_to_text_edits converts an array of PsiElements to an array of LSP TextEdits.
// If element is a PsiNamedElement, the edit will be applied to the identifier.
// Otherwise, the edit will be applied to the entire element.
pub fn elements_to_text_edits(elements []psi.PsiElement, new_name string) []lsp.TextEdit {
mut result := []lsp.TextEdit{cap: elements.len}
for element in elements {
range := if element is psi.PsiNamedElement {
element.identifier_text_range()
} else {
element.text_range()
}
result << lsp.TextEdit{
range: text_range_to_lsp_range(range)
new_text: new_name
}
}
return result
}
pub fn position_to_lsp_position(pos psi.Position) lsp.Position {
return lsp.Position{
line: pos.line
character: pos.character
}
}
pub fn lsp_position_to_position(pos lsp.Position) psi.Position {
return psi.Position{
line: pos.line
character: pos.character
}
}
================================================
FILE: src/server/workspace/ProjectResolver.v
================================================
module workspace
import os
import lsp
pub struct ProjectResolver {
workspace_root string
mut:
cache shared map[string]string
}
pub fn ProjectResolver.new(workspace_root string) &ProjectResolver {
return &ProjectResolver{
workspace_root: os.real_path(workspace_root)
cache: map[string]string{}
}
}
pub fn (mut p ProjectResolver) resolve(uri lsp.DocumentUri) string {
filepath := os.real_path(uri.path())
dir := os.dir(filepath)
rlock p.cache {
if cached := p.cache[dir] {
return cached
}
}
root := p.find_root(dir)
lock p.cache {
p.cache[dir] = root
}
return root
}
pub fn (mut p ProjectResolver) clear() {
lock p.cache {
p.cache.clear()
}
}
fn (p &ProjectResolver) find_root(start_dir string) string {
mut curr := start_dir
home_dir := os.home_dir()
for {
if os.exists(os.join_path(curr, 'v.mod')) || os.is_dir(os.join_path(curr, '.git'))
|| curr == p.workspace_root {
return curr
}
parent := os.dir(curr)
if curr == home_dir || parent == curr {
break
}
curr = parent
}
return if start_dir.starts_with(p.workspace_root) {
p.workspace_root
} else {
start_dir
}
}
================================================
FILE: src/streams/streams.v
================================================
module streams
import term
import net
import os
import io
import loglib
fn C._setmode(int, int)
const content_length = 'Content-Length: '
pub fn new_stdio_stream() io.ReaderWriter {
stream := &StdioStream{}
$if windows {
// 0x8000 = _O_BINARY from
// windows replaces \n => \r\n, so \r\n will be replaced to \r\r\n
// binary mode prevents this
C._setmode(C._fileno(C.stdout), 0x8000)
}
return stream
}
struct StdioStream {
mut:
stdin os.File = os.stdin()
stdout os.File = os.stdout()
}
pub fn (mut stream StdioStream) write(buf []u8) !int {
defer {
stream.stdout.flush()
}
return stream.stdout.write(buf)
}
pub fn (mut stream StdioStream) read(mut buf []u8) !int {
mut header_len := 0
mut conlen := 0
for {
len := read_line(stream.stdin, mut buf) or { return err }
st := buf.len - len
line := buf[st..].bytestr()
buf << `\r`
buf << `\n`
header_len = len + 2
if len == 0 {
// encounter empty line ('\r\n') in header, header end
break
} else if line.starts_with(content_length) {
conlen = line.all_after(content_length).int()
}
}
mut body := []u8{len: conlen}
read_cnt := stream.stdin.read(mut body) or { return err }
if read_cnt != conlen {
return IError(io.Eof{})
}
buf << body
return header_len + conlen
}
fn read_line(file &os.File, mut buf []u8) !int {
mut len := 0
mut temp := []u8{len: 256, cap: 256}
for {
read_cnt := file.read_bytes_with_newline(mut temp) or { return err }
len += read_cnt
buf << temp[0..read_cnt]
if read_cnt == 0 {
return if len == 0 {
IError(io.Eof{})
} else {
len
}
}
if buf.len > 0 && buf.last() == `\n` {
buf.pop()
len--
// check is it just '\n' or '\r\n'
if len > 0 && buf.last() == `\r` {
buf.pop()
len--
}
break
}
}
return len
}
const base_ip = '127.0.0.1'
pub fn new_socket_stream_server(port int, log bool) !io.ReaderWriter {
server_label := 'v-analyzer-server'
address := '${base_ip}:${port}'
mut listener := net.listen_tcp(.ip, address)!
if log {
eprintln(term.yellow('Warning: TCP connection is used primarily for debugging purposes only \n and may have performance issues. Use it on your own risk.\n'))
println('[${server_label}] : Established connection at ${address}\n')
}
mut conn := listener.accept() or {
listener.close() or {}
return err
}
mut reader := io.new_buffered_reader(reader: conn, cap: 1024 * 1024)
conn.set_blocking(true) or {}
mut stream := &SocketStream{
log_label: server_label
log: log
port: port
conn: conn
reader: reader
}
return stream
}
fn new_socket_stream_client(port int) !io.ReaderWriter {
address := '${base_ip}:${port}'
mut conn := net.dial_tcp(address)!
mut reader := io.new_buffered_reader(reader: conn, cap: 1024 * 1024)
conn.set_blocking(true) or {}
mut stream := &SocketStream{
log_label: 'v-analyzer-client'
log: false
port: port
conn: conn
reader: reader
}
return stream
}
struct SocketStream {
log_label string = 'v-analyzer'
log bool = true
mut:
conn &net.TcpConn = &net.TcpConn(net.listen_tcp(.ip, '80')!)
reader &io.BufferedReader = unsafe { nil }
pub mut:
port int = 5007
debug bool
}
pub fn (mut stream SocketStream) write(buf []u8) !int {
// TODO: should be an interceptor
$if !test {
if stream.log {
loglib.trace('${term.bg_green('Sent data →')} : ${buf.bytestr()}\n')
}
}
return stream.conn.write(buf)
}
const newlines = [u8(`\r`), `\n`]
@[manualfree]
pub fn (mut stream SocketStream) read(mut buf []u8) !int {
mut conlen := 0
mut header_len := 0
for {
// read header line
got_header := stream.reader.read_line() or { return IError(io.Eof{}) }
buf << got_header.bytes()
buf << newlines
header_len = got_header.len + 2
if got_header.len == 0 {
// encounter empty line ('\r\n') in header, header end
break
} else if got_header.starts_with(content_length) {
conlen = got_header.all_after(content_length).int()
}
}
if conlen > 0 {
mut rbody := []u8{len: conlen}
defer {
unsafe { rbody.free() }
}
for read_data_len := 0; read_data_len != conlen; {
read_data_len = stream.reader.read(mut rbody) or { return IError(io.Eof{}) }
}
buf << rbody
}
$if !test {
if stream.log {
loglib.trace('${term.green('Received data ←')} : ${buf.bytestr()}\n')
}
}
return conlen + header_len
}
================================================
FILE: src/testing/Benchmark.v
================================================
module testing
import time
pub struct Benchmark {
mut:
name string
watch time.StopWatch
}
pub fn (mut b Benchmark) start() {
b.watch.start()
}
pub fn (mut b Benchmark) stop() {
b.watch.stop()
}
pub fn (b &Benchmark) print_results() {
println('Benchmark: ' + b.name)
println('Time: ' + b.watch.elapsed().str())
}
================================================
FILE: src/testing/BenchmarkRunner.v
================================================
module testing
pub struct BenchmarkRunner {
mut:
benchmarks []&Benchmark
pub mut:
last_fixture &Fixture
}
pub fn (mut b BenchmarkRunner) create_or_reuse_fixture() &Fixture {
if !isnil(b.last_fixture) {
return b.last_fixture
}
mut fixture := new_fixture()
fixture.initialize(false) or {
println('Cannot initialize fixture: ${err}')
return fixture
}
b.last_fixture = fixture
return fixture
}
pub fn (mut b BenchmarkRunner) bench(name string, bench_func fn (mut bench Benchmark, mut fixture Fixture) !) {
mut fixture := b.create_or_reuse_fixture()
mut bench := &Benchmark{
name: name
}
b.benchmarks << bench
bench_func(mut bench, mut fixture) or { println('Benchmark failed: ${err}') }
}
================================================
FILE: src/testing/Test.v
================================================
module testing
import term
import lsp
import time
import strings
pub type TestFunc = fn (mut test Test, mut fixture Fixture) !
pub enum TestState {
passed
failed
skipped
}
pub struct Test {
mut:
fixture &Fixture = unsafe { nil }
name string
func TestFunc = unsafe { nil }
state TestState
message string
with_stdlib bool
duration time.Duration
}
pub fn (mut t Test) run(mut fixture Fixture) {
watch := time.new_stopwatch(auto_start: true)
t.fixture = fixture
t.func(mut t, mut fixture) or {}
t.duration = watch.elapsed()
t.print()
}
pub fn (mut t Test) fail(msg string) ! {
t.state = .failed
t.message = msg
return error(msg)
}
pub fn (mut t Test) assert_eq[T](left T, right T) ! {
if left != right {
t.fail('expected ${left}, but got ${right}')!
}
}
pub fn (mut t Test) assert_definition_name(location lsp.LocationLink, name string) ! {
link_text := t.fixture.text_at_range(location.target_selection_range)
if link_text != name {
t.fail('expected definition "${name}", but got "${link_text}"')!
}
}
pub fn (mut t Test) assert_no_definition(locations []lsp.LocationLink) ! {
if locations.len != 0 {
t.fail('expected no definition, but got ${locations.len}')!
}
}
pub fn (mut t Test) assert_has_definition(locations []lsp.LocationLink) ! {
if locations.len == 0 {
t.fail('no definition found')!
}
}
pub fn (mut t Test) assert_has_completion_with_label(items []lsp.CompletionItem, name string) ! {
for item in items {
if item.label == name {
return
}
}
t.fail('expected completion "${name}" not found')!
}
pub fn (mut t Test) assert_has_only_completion_with_labels(items []lsp.CompletionItem, names ...string) ! {
if items.len != names.len {
t.fail('expected ${names.len} completions, but got ${items.len}')!
}
for name in names {
t.assert_has_completion_with_label(items, name)!
}
}
pub fn (mut t Test) assert_has_completion_with_insert_text(items []lsp.CompletionItem, name string) ! {
if items.len == 0 {
t.fail('no completions found')!
}
for item in items {
if item.insert_text == name {
return
}
}
t.fail('expected completion "${name}" not found')!
}
pub fn (mut t Test) assert_no_completion_with_insert_text(items []lsp.CompletionItem, name string) ! {
for item in items {
if item.insert_text == name {
t.fail('unexpected completion "${name}" found')!
}
}
}
pub fn (mut t Test) assert_no_completion_with_label(items []lsp.CompletionItem, name string) ! {
for item in items {
if item.label == name {
t.fail('unexpected completion "${name}" found')!
}
}
}
pub fn (mut t Test) assert_has_implementation_with_name(items []lsp.Location, name string) ! {
for item in items {
link_text := t.fixture.text_at_range(item.range)
if link_text == name {
return
}
}
t.fail('expected implementation "${name}" not found')!
}
pub fn (mut t Test) assert_no_implementation_with_name(items []lsp.Location, name string) ! {
for item in items {
link_text := t.fixture.text_at_range(item.range)
if link_text == name {
t.fail('unexpected implementation "${name}" found')!
}
}
}
pub fn (mut t Test) assert_has_super_with_name(items []lsp.Location, name string) ! {
for item in items {
link_text := t.fixture.text_at_range(item.range)
if link_text == name {
return
}
}
t.fail('expected super "${name}" not found')!
}
pub fn (mut t Test) assert_no_super_with_name(items []lsp.Location, name string) ! {
for item in items {
link_text := t.fixture.text_at_range(item.range)
if link_text == name {
t.fail('unexpected super "${name}" found')!
}
}
}
pub fn (mut t Test) assert_uri(left lsp.DocumentUri, right lsp.DocumentUri) ! {
left_normalized := left.normalize()
right_normalized := right.normalize()
if left_normalized.compare(right_normalized) != 0 {
t.fail('expected ${left_normalized}, but got ${right_normalized}')!
}
}
pub fn (mut t Test) assert_uri_from_stdlib(left lsp.DocumentUri, filename string) ! {
if !left.contains('vlib') {
t.fail('expected ${left} to be inside "vlib"')!
}
if !left.ends_with(filename) {
t.fail('expected ${left} to end with ${filename} but got ${left}')!
}
}
pub fn (mut t Test) assert_uri_from_stubs(left lsp.DocumentUri, filename string) ! {
if !left.contains('stubs') {
t.fail('expected ${left} to be inside "stubs"')!
}
if !left.ends_with(filename) {
t.fail('expected ${left} to end with ${filename} but got ${left}')!
}
}
pub fn (t Test) print() {
mut sb := strings.new_builder(100)
sb.write_string('${t.duration:10} ')
if t.state == .failed {
sb.write_string(term.red('[FAILED] '))
sb.write_string(t.name)
sb.write_string('\n')
sb.write_string(' ${t.message}\n')
} else if t.state == .passed {
sb.write_string(term.green('[PASSED] '))
sb.write_string(t.name)
sb.write_string('\n')
}
print(sb.str())
}
================================================
FILE: src/testing/TestFixture.v
================================================
module testing
import os
import testing.client
import lsp
import jsonrpc
import server
import analyzer
pub const temp_path = os.join_path(os.temp_dir(), 'v-analyzer-test')
struct TestFile {
path string
content []string
caret lsp.Position
}
pub fn (t TestFile) uri() lsp.DocumentUri {
return lsp.document_uri_from_path(t.path)
}
@[heap; noinit]
pub struct Fixture {
mut:
ls &server.LanguageServer = unsafe { nil }
stream &client.TestStream = unsafe { nil }
server &jsonrpc.Server = unsafe { nil }
test_client client.TestClient
current_file TestFile
opened_files []string
}
pub fn new_fixture() &Fixture {
indexing_mng := analyzer.IndexingManager.new()
mut ls := server.LanguageServer.new(indexing_mng)
mut stream := &client.TestStream{}
mut jsonprc_server := &jsonrpc.Server{
stream: stream
handler: ls
}
mut test_client := client.TestClient{
server: jsonprc_server
stream: stream
}
return &Fixture{
ls: ls
stream: stream
server: jsonprc_server
test_client: test_client
}
}
pub fn (mut t Fixture) initialize(with_stdlib bool) !lsp.InitializeResult {
os.mkdir_all(temp_path)!
mut options := ['no-index-save', 'no-diagnostics']
if !with_stdlib {
options << 'no-stdlib'
}
result := t.test_client.send[lsp.InitializeParams, lsp.InitializeResult]('initialize', lsp.InitializeParams{
process_id: 75556
client_info: lsp.ClientInfo{
name: 'Testing'
version: '0.0.1'
}
root_uri: lsp.document_uri_from_path(temp_path)
root_path: temp_path
initialization_options: options.join(' ')
capabilities: lsp.ClientCapabilities{}
trace: ''
workspace_folders: []
})!
return result
}
pub fn (mut t Fixture) initialized() ! {
t.test_client.send[jsonrpc.Null, jsonrpc.Null]('initialized', jsonrpc.Null{})!
}
pub fn (mut t Fixture) configure_by_file(path string) ! {
rel_path := 'testdata/${path}'
content := os.read_file(rel_path)!
prepared_text := content + '\n\n' // add extra lines to make sure the caret is not at the end of the file
prepared_content := prepared_text.replace('/*caret*/', '')
abs_path := os.join_path(temp_path, path)
dir_path := os.dir(abs_path)
os.mkdir_all(dir_path)!
os.write_file(abs_path, prepared_content)!
if t.current_file.path == abs_path {
t.close_file(t.current_file.path)
}
t.current_file = TestFile{
path: abs_path
content: prepared_content.split_into_lines()
caret: t.caret_pos(prepared_text)
}
t.send_open_current_file_request()!
}
pub fn (mut t Fixture) configure_by_text(filename string, text string) ! {
prepared_text := text + '\n\n' // add extra lines to make sure the caret is not at the end of the file
content := prepared_text.replace('/*caret*/', '')
abs_path := os.join_path(temp_path, filename)
abs_path_without_name := os.dir(abs_path)
os.mkdir_all(abs_path_without_name)!
os.write_file(abs_path, content)!
if t.current_file.path == abs_path {
t.close_file(t.current_file.path)
}
t.current_file = TestFile{
path: abs_path
content: content.split_into_lines()
caret: t.caret_pos(prepared_text)
}
t.send_open_current_file_request()!
}
fn (mut t Fixture) send_open_current_file_request() ! {
t.test_client.send[lsp.DidOpenTextDocumentParams, jsonrpc.Null]('textDocument/didOpen', lsp.DidOpenTextDocumentParams{
text_document: lsp.TextDocumentItem{
uri: lsp.document_uri_from_path(t.current_file.path)
language_id: 'v'
version: 1
text: t.current_file.content.join('\n')
}
}) or {}
t.test_client.send[lsp.DidChangeTextDocumentParams, jsonrpc.Null]('textDocument/didChange', lsp.DidChangeTextDocumentParams{
text_document: lsp.VersionedTextDocumentIdentifier{
uri: lsp.document_uri_from_path(t.current_file.path)
version: 1
}
content_changes: [
lsp.TextDocumentContentChangeEvent{
text: t.current_file.content.join('\n')
},
]
}) or {}
t.test_client.send[lsp.DidChangeWatchedFilesParams, jsonrpc.Null]('workspace/didChangeWatchedFiles', lsp.DidChangeWatchedFilesParams{
changes: [
lsp.FileEvent{
uri: lsp.document_uri_from_path(t.current_file.path)
typ: lsp.FileChangeType.created
},
]
}) or {}
}
pub fn (mut t Fixture) definition_at_cursor() []lsp.LocationLink {
return t.definition(t.current_caret_pos())
}
pub fn (mut t Fixture) definition(pos lsp.Position) []lsp.LocationLink {
links := t.test_client.send[lsp.TextDocumentPositionParams, []lsp.LocationLink]('textDocument/definition', lsp.TextDocumentPositionParams{
text_document: lsp.TextDocumentIdentifier{
uri: lsp.document_uri_from_path(t.current_file.path)
}
position: pos
}) or { []lsp.LocationLink{} }
return links
}
pub fn (mut t Fixture) complete_at_cursor() []lsp.CompletionItem {
return t.complete(t.current_caret_pos())
}
pub fn (mut t Fixture) complete(pos lsp.Position) []lsp.CompletionItem {
items := t.test_client.send[lsp.CompletionParams, []lsp.CompletionItem]('textDocument/completion', lsp.CompletionParams{
text_document: lsp.TextDocumentIdentifier{
uri: lsp.document_uri_from_path(t.current_file.path)
}
position: pos
context: lsp.CompletionContext{
trigger_kind: .invoked
}
}) or { []lsp.CompletionItem{} }
return items
}
pub fn (mut t Fixture) compute_inlay_hints() []lsp.InlayHint {
hints := t.test_client.send[lsp.InlayHintParams, []lsp.InlayHint]('textDocument/inlayHint', lsp.InlayHintParams{
text_document: lsp.TextDocumentIdentifier{
uri: lsp.document_uri_from_path(t.current_file.path)
}
}) or { []lsp.InlayHint{} }
return hints
}
pub fn (mut t Fixture) compute_semantic_tokens() lsp.SemanticTokens {
tokens := t.test_client.send[lsp.SemanticTokensParams, lsp.SemanticTokens]('textDocument/semanticTokens/full', lsp.SemanticTokensParams{
text_document: lsp.TextDocumentIdentifier{
uri: lsp.document_uri_from_path(t.current_file.path)
}
}) or { lsp.SemanticTokens{} }
return tokens
}
pub fn (mut t Fixture) implementation_at_cursor() []lsp.Location {
return t.implementation(t.current_caret_pos())
}
pub fn (mut t Fixture) implementation(pos lsp.Position) []lsp.Location {
links := t.test_client.send[lsp.TextDocumentPositionParams, []lsp.Location]('textDocument/implementation', lsp.TextDocumentPositionParams{
text_document: lsp.TextDocumentIdentifier{
uri: lsp.document_uri_from_path(t.current_file.path)
}
position: pos
}) or { []lsp.Location{} }
return links
}
pub fn (mut t Fixture) supers_at_cursor() []lsp.Location {
return t.supers(t.current_caret_pos())
}
pub fn (mut t Fixture) supers(pos lsp.Position) []lsp.Location {
return t.implementation(pos)
}
pub fn (mut t Fixture) documentation_at_cursor() ?lsp.Hover {
return t.documentation(t.current_caret_pos())
}
pub fn (mut t Fixture) documentation(pos lsp.Position) ?lsp.Hover {
hover := t.test_client.send[lsp.HoverParams, lsp.Hover]('textDocument/hover', lsp.HoverParams{
text_document: lsp.TextDocumentIdentifier{
uri: lsp.document_uri_from_path(t.current_file.path)
}
position: pos
}) or { return none }
return hover
}
pub fn (mut t Fixture) close_file(path string) {
t.test_client.send[lsp.DidCloseTextDocumentParams, jsonrpc.Null]('textDocument/didClose', lsp.DidCloseTextDocumentParams{
text_document: lsp.TextDocumentIdentifier{
uri: lsp.document_uri_from_path(path)
}
}) or {}
}
pub fn (mut t Fixture) current_file_uri() lsp.DocumentUri {
return lsp.document_uri_from_path(t.current_file.path)
}
pub fn (mut t Fixture) text_at_range(range lsp.Range) string {
lines := t.current_file.content
start := range.start
end := range.end
if start.line == end.line {
return lines[start.line][start.character..end.character]
}
mut result := lines[start.line][start.character..]
for line in lines[start.line + 1..end.line] {
result += line
}
result += lines[end.line][..end.character]
return result
}
pub fn (mut t Fixture) current_caret_pos() lsp.Position {
return t.current_file.caret
}
pub fn (mut t Fixture) caret_pos(file string) lsp.Position {
for index, line in file.split_into_lines() {
if line.contains('/*caret*/') {
return lsp.Position{
line: index
character: line.index('/*caret*/') or { 0 }
}
}
}
return lsp.Position{
line: 0
character: 0
}
}
================================================
FILE: src/testing/Tester.v
================================================
module testing
import os
import lsp
import time
import term
import loglib
import analyzer.psi
pub struct TesterStats {
pub:
passed int
failed int
skipped int
duration time.Duration
}
pub fn (s TesterStats) print() {
println('${term.bold('Passed')}: ${s.passed}, ${term.bold('Failed')}: ${s.failed}, ${term.bold('Skipped')}: ${s.skipped}')
println('${term.bold('Duration')}: ${s.duration}')
}
pub fn (s TesterStats) merge(other TesterStats) TesterStats {
return TesterStats{
passed: s.passed + other.passed
failed: s.failed + other.failed
skipped: s.skipped + other.skipped
duration: s.duration + other.duration
}
}
pub struct Tester {
pub:
name string
mut:
tests []&Test
last_fixture &Fixture = unsafe { nil }
last_slow_fixture &Fixture = unsafe { nil }
}
pub fn with_name(name string) Tester {
return Tester{
name: name
}
}
pub fn (mut t Tester) failed_tests() []&Test {
return t.tests.filter(it.state == .failed)
}
pub fn (mut t Tester) create_or_reuse_fixture(with_stdlib bool) &Fixture {
if with_stdlib {
if !isnil(t.last_slow_fixture) {
return t.last_slow_fixture
}
} else {
if !isnil(t.last_fixture) {
return t.last_fixture
}
}
loglib.set_level(.warn) // we don't want to see info messages in tests
mut fixture := new_fixture()
fixture.initialize(with_stdlib) or {
println('Cannot initialize fixture: ${err}')
return fixture
}
fixture.initialized() or {
println('Cannot run initialized request: ${err}')
return fixture
}
if with_stdlib {
t.last_slow_fixture = fixture
} else {
t.last_fixture = fixture
}
return fixture
}
pub fn (mut t Tester) run(run_only string) {
mut fixture := t.create_or_reuse_fixture(false)
for mut test in t.tests {
if run_only != '' && test.name != run_only {
test.state = .skipped
continue
}
if test.with_stdlib {
mut fixture_with_stdlib := t.create_or_reuse_fixture(true)
test.run(mut fixture_with_stdlib)
continue
}
test.run(mut fixture)
}
}
pub fn (t Tester) stats() TesterStats {
mut passed := 0
mut failed := 0
mut skipped := 0
mut duration := 0
for test in t.tests {
if test.state == .skipped {
skipped += 1
} else if test.state == .failed {
failed += 1
} else {
passed += 1
}
duration += test.duration
}
return TesterStats{
passed: passed
failed: failed
skipped: skipped
duration: duration
}
}
pub fn (mut t Tester) test(name string, test_func TestFunc) {
mut test := &Test{
name: name
func: test_func
}
t.tests << test
}
pub fn (mut t Tester) slow_test(name string, test_func TestFunc) {
mut test := &Test{
name: name
func: test_func
with_stdlib: true
}
t.tests << test
}
pub fn (mut t Tester) type_test(name string, filepath string) {
mut test := &Test{
name: name
}
t.tests << test
test.func = fn [filepath] (mut test Test, mut fixture Fixture) ! {
fixture.configure_by_file(filepath) or {
println('Cannot configure fixture: ${err}')
return error('Cannot configure fixture: ${err}')
}
file := fixture.ls.get_file(fixture.current_file.uri()) or {
return error('File not found: ${fixture.current_file.uri()}')
}
mut expr_calls := []psi.CallExpression{}
mut expr_calls_ptr := &expr_calls
psi.inspect(file.psi_file.root, fn [mut expr_calls_ptr] (it psi.PsiElement) bool {
if it is psi.CallExpression {
expr := it.expression() or { return true }
text := expr.get_text()
if text != 'expr_type' {
return true
}
arguments := it.arguments()
if arguments.len != 2 {
return true
}
expr_calls_ptr << it
return false
}
return true
})
for call in expr_calls {
arguments := call.arguments()
if arguments.len != 2 {
continue
}
first := arguments[0]
second := arguments[1]
typ := psi.infer_type(first)
got_type_string := typ.readable_name()
if second is psi.Literal {
first_child := second.first_child() or { continue }
if first_child is psi.StringLiteral {
expected_type_string := first_child.content()
if expected_type_string != got_type_string {
containing_file := call.containing_file() or {
return error('Call expression has no containing file context')
}
test.state = .failed
test.message = '
In file ${containing_file.path}:${
call.text_range().line + 1}
Type mismatch.
Expected: ${expected_type_string}
Found: ${got_type_string}
'.trim_indent()
break
}
}
}
}
}
}
pub fn (mut t Tester) documentation_test(name string, filepath string) {
mut test := &Test{
name: name
}
t.tests << test
test.func = fn [filepath] (mut test Test, mut fixture Fixture) ! {
fixture.configure_by_file(filepath) or {
return test.fail('Cannot configure fixture: ${err}')
}
hover := fixture.documentation_at_cursor() or {
return test.fail('Cannot get documentation at cursor')
}
if hover.contents !is lsp.MarkupContent {
return test.fail('Documentation is not a MarkupContent')
}
markup := hover.contents as lsp.MarkupContent
if markup.kind != lsp.markup_kind_markdown {
return test.fail('Documentation is not a Markdown')
}
markdown_filepath := filepath + '.md'
markdown := os.read_file('testdata/${markdown_filepath}') or {
return test.fail('Cannot read expected .md file: ${err}')
}
// if true {
// os.write_file('testdata/${markdown_filepath}', markup.value) or {}
// return
// }
test.assert_eq(markup.value.trim_right('\n'), markdown.trim_right('\n'))!
}
}
pub fn (mut t Tester) scratch_test(name string, test_func fn (mut test Test, mut fixture Fixture) !) {
mut fixture := new_fixture()
fixture.initialize(false) or {
println('Test failed: ${err}')
return
}
mut test := &Test{
name: name
}
t.tests << test
test_func(mut test, mut fixture) or { println('Test failed: ${err}') }
test.print()
}
================================================
FILE: src/testing/client/TestClient.v
================================================
module client
import jsonrpc
import json
import io
import datatypes
// new_test_client creates a test client to be used for observing responses
// and notifications from the given handler and interceptors
pub fn new_test_client(handler jsonrpc.Handler, interceptors ...jsonrpc.Interceptor) &TestClient {
mut stream := &TestStream{}
mut server := &jsonrpc.Server{
handler: handler
interceptors: interceptors
stream: stream
}
return &TestClient{
server: server
stream: stream
}
}
// TestResponse is a version of jsonrpc.Response that decodes
// incoming JSON as raw JSON string.
struct TestResponse {
raw_id string @[json: id; raw]
raw_result string @[json: result; raw]
}
// TestClient is a JSONRPC Client used for simulating communication between client and
// JSONRPC server. This exposes the JSONRPC server and a test stream for sending data
// as a server or as a client
pub struct TestClient {
mut:
id int
pub mut:
server &jsonrpc.Server = unsafe { nil }
stream &TestStream = unsafe { nil }
}
// send sends a request and receives a decoded response result.
pub fn (mut tc TestClient) send[T, U](method string, params T) !U {
params_json := json.encode(params)
req := jsonrpc.Request{
id: '${tc.id}'
method: method
params: params_json
}
tc.stream.send(req)
tc.server.respond() or { return err }
raw_json_content := tc.stream.response_text(req.id)
if raw_json_content.len == 0 || raw_json_content == 'null' {
return IError(io.Eof{})
}
// println(raw_json_content)
return json.decode(U, raw_json_content)!
}
// notify is a version of send but instead of returning a response,
// it only notifies the server. Effectively sending a request as a
// notification.
pub fn (mut tc TestClient) notify[T](method string, params T) ! {
params_json := json.encode(params)
req := jsonrpc.Request{
id: ''
method: method
params: params_json
}
tc.stream.send(req)
tc.server.respond()!
}
// TestStream is a io.ReadWriter-compliant stream for sending
// and receiving responses from between the client and the server.
// Aside from being a ReaderWriter, it exposes additional methods
// for decoding JSONRPC response and notifications.
pub struct TestStream {
mut:
notif_idx int
notif_buf [][]u8 = [][]u8{len: 20, cap: 20}
resp_buf map[string]TestResponse
req_buf datatypes.Queue[[]u8]
}
// read receives the incoming request buffer.
pub fn (mut rw TestStream) read(mut buf []u8) !int {
req := rw.req_buf.pop() or { return IError(io.Eof{}) }
buf << req
return req.len
}
// write receives the outgoing response/notification buffer.
pub fn (mut rw TestStream) write(buf []u8) !int {
raw_json_content := buf.bytestr().all_after('\r\n\r\n')
if raw_json_content.contains('"result":') {
resp := json.decode(TestResponse, raw_json_content) or { return err }
rw.resp_buf[resp.raw_id] = resp
} else if raw_json_content.contains('"params":') {
idx := rw.notif_idx % 20
for i := idx + 1; i < rw.notif_buf.len; i++ {
if rw.notif_buf[i].len != 0 {
rw.notif_buf[idx].clear()
}
}
rw.notif_buf[idx] << buf
rw.notif_idx++
} else {
return error('none')
}
return buf.len
}
// send stringifies and dispatches the jsonrpc.Request into the request queue.
pub fn (mut rw TestStream) send(req jsonrpc.Request) {
req_json := req.json()
rw.req_buf.push('Content-Length: ${req_json.len}\r\n\r\n${req_json}'.bytes())
}
// response_text returns the raw response result of the given request id.
pub fn (rw &TestStream) response_text(raw_id string) string {
return rw.resp_buf[raw_id].raw_result
}
// notification_at returns the jsonrpc.Notification in a given index.
pub fn (rw &TestStream) notification_at[T](idx int) !jsonrpc.NotificationMessage[T] {
raw_json_content := rw.notif_buf[idx].bytestr().all_after('\r\n\r\n')
return json.decode(jsonrpc.NotificationMessage[T], raw_json_content)!
}
// last_notification_at_method returns the last jsonrpc.Notification from the given method name.
pub fn (rw &TestStream) last_notification_at_method[T](method_name string) !jsonrpc.NotificationMessage[T] {
for i := rw.notif_buf.len - 1; i >= 0; i-- {
raw_notif_content := rw.notif_buf[i]
if raw_notif_content.len == 0 {
continue
}
if raw_notif_content.bytestr().contains('"method":"${method_name}"') {
return rw.notification_at[T](i) or { return err }
}
}
return error('')
}
// RpcResult is a result form used for primitive types.
pub struct RpcResult[T] {
result T
}
================================================
FILE: src/tests/analyzer_test.v
================================================
module tests
import os
import term
import testing
fn test_all() {
defer {
os.rmdir_all(testing.temp_path) or {
println('Failed to remove temp path: ${testing.temp_path}')
}
}
os.chdir(os.join_path(@VMODROOT, 'src', 'tests'))!
mut testers := []testing.Tester{}
testers << definitions()
testers << implementations()
testers << supers()
testers << completion()
testers << types()
testers << documentation()
run_only := ''
for mut tester in testers {
println('Running ${term.bg_blue(' ' + tester.name + ' ')}')
tester.run(run_only)
println('')
tester.stats().print()
println('')
}
mut all_stats := testing.TesterStats{}
for tester in testers {
all_stats = all_stats.merge(tester.stats())
}
println(term.bg_blue(' All tests: '))
all_stats.print()
if all_stats.failed > 0 {
println('')
println(term.bg_red(' Failed tests: '))
for mut tester in testers {
for failed_test in tester.failed_tests() {
failed_test.print()
}
}
exit(1)
}
}
================================================
FILE: src/tests/bench.v
================================================
module tests
import testing
fn bench() testing.BenchmarkRunner {
mut b := testing.BenchmarkRunner{
last_fixture: unsafe { nil }
}
b.bench('inlay hints', fn (mut b testing.Benchmark, mut fixture testing.Fixture) ! {
fixture.configure_by_file('benchmarks/inlay_hints.vv')!
b.start()
hints := fixture.compute_inlay_hints()
println('Count: ${hints.len}')
b.stop()
b.print_results()
})
b.bench('semantic tokens', fn (mut b testing.Benchmark, mut fixture testing.Fixture) ! {
fixture.configure_by_file('benchmarks/checker.vv')!
b.start()
tokens := fixture.compute_semantic_tokens()
println('Count: ${tokens.data.len / 5}')
b.stop()
b.print_results()
})
return b
}
================================================
FILE: src/tests/completion.v
================================================
module tests
import testing
import server.completion.providers
fn completion() testing.Tester {
mut t := testing.with_name('completion')
t.test('struct field completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
struct SomeFoo {
name string
}
fn main() {
foo := SomeFoo{}
foo./*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_only_completion_with_labels(items, 'name', 'str')!
})
t.test('struct method completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
struct Foo {}
fn (foo Foo) some_method() {
}
fn main() {
foo := Foo{}
foo./*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_only_completion_with_labels(items, 'some_method', 'str')!
})
t.test('variables completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn main() {
name := 100
/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'name')!
})
t.test('assert inside test file', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1_test.v', '
fn test_something() {
asse/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'assert expr')!
t.assert_has_completion_with_label(items, 'assert expr, message')!
})
t.test('assert as expression', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1_test.v', '
fn test_something() {
a := asse/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_no_completion_with_label(items, 'assert expr')!
t.assert_no_completion_with_label(items, 'assert expr, message')!
})
t.test('assert outside test file', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn test_something() {
asse/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_no_completion_with_label(items, 'assert expr')!
t.assert_no_completion_with_label(items, 'assert expr, message')!
})
t.test('attributes over function', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
[/*caret*/]
fn main() {
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'direct_array_access')!
t.assert_has_completion_with_label(items, "sql: 'value'")!
})
t.test('json attribute for field', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
struct Foo {
some_value string [js/*caret*/]
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, "json: 'someValue'")!
})
t.test('json attribute for field with at', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
struct Foo {
@enum string [js/*caret*/]
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, "json: 'enum'")!
})
t.test('json attribute for field with underscore', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
struct Foo {
type_ string [js/*caret*/]
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, "json: 'type'")!
})
t.test('compile time constant', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
@/*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, '@FN')!
t.assert_has_completion_with_label(items, '@FILE_LINE')!
})
t.test('function like keywords', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
/*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'dump()')!
t.assert_has_completion_with_label(items, 'sizeof()')!
t.assert_has_completion_with_label(items, 'typeof()')!
t.assert_has_completion_with_label(items, 'isreftype()')!
t.assert_has_completion_with_label(items, '__offsetof()')!
})
t.test('inits completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
/*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'chan int{}')!
t.assert_has_completion_with_label(items, 'map[string]int{}')!
t.assert_has_completion_with_label(items, 'thread int{}')!
})
t.test('keywords completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
/*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'true')!
t.assert_has_completion_with_label(items, 'false')!
t.assert_has_completion_with_label(items, 'static')!
t.assert_has_completion_with_label(items, 'none')!
})
t.test('continue and break inside loop', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
for {
/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'break')!
t.assert_has_completion_with_label(items, 'continue')!
})
t.test('continue and break outside loop', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
/*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_no_completion_with_label(items, 'break')!
t.assert_no_completion_with_label(items, 'continue')!
})
t.test('module name completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
modu/*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
if items.len == 0 {
return t.fail('no completion variants')
}
t.assert_has_completion_with_label(items, 'module main')!
t.assert_has_completion_with_label(items, 'module v_analyzer_test')!
})
t.test('module name completion with module clause', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
modu/*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_no_completion_with_label(items, 'module main')!
t.assert_no_completion_with_label(items, 'module v_analyzer_test')!
})
t.test('nil keyword completion outside unsafe', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
/*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_insert_text(items, 'unsafe { nil }')!
})
t.test('nil keyword completion inside unsafe', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
unsafe {
/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_insert_text(items, 'nil')!
})
t.test('or block completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
foo() /*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'or { ... }')!
t.assert_has_completion_with_label(items, 'or { panic(err) }')!
})
t.test('unsafe block completion as expression', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
a := /*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_insert_text(items, 'unsafe { $0 }')!
})
t.test('unsafe block completion as statements', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
/*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_insert_text(items, 'unsafe {\n\t$0\n}')!
})
t.test('defer block completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
/*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'defer { ... }')!
})
t.test('return completion inside function without return type', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn foo() {
/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'return')!
})
t.test('return completion inside function with return type', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn foo() int {
/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'return ')!
})
t.test('return completion inside function with bool return type', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn foo() bool {
/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'return ')!
t.assert_has_completion_with_label(items, 'return true')!
t.assert_has_completion_with_label(items, 'return false')!
})
t.test('top level completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
/*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
for label, _ in providers.top_level_map {
t.assert_has_completion_with_label(items, label)!
t.assert_has_completion_with_label(items, 'pub ${label}')!
}
})
t.test('no top level completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn main() {
/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
for label, _ in providers.top_level_map {
t.assert_no_completion_with_label(items, label)!
t.assert_no_completion_with_label(items, 'pub ${label}')!
}
})
t.test('parameters completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn foo(param_name_1 int, param_name_2 string) {
/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'param_name_1')!
t.assert_has_completion_with_label(items, 'param_name_2')!
})
t.test('receiver name completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
struct Foo {}
fn (foo_receiver Foo) bar() {
/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'foo_receiver')!
})
t.test('struct as type completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('foo/foo.v', '
module foo
pub struct Foo {}
'.trim_indent())!
fixture.configure_by_text('1.v', '
import foo
fn bar() foo./*caret*/ {
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_insert_text(items, 'Foo')!
t.assert_no_completion_with_insert_text(items, 'Foo{$1}$0')!
})
t.test('struct as expression completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('foo/foo.v', '
module foo
pub struct Foo {}
'.trim_indent())!
fixture.configure_by_text('1.v', '
import foo
fn bar() {
foo./*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_insert_text(items, 'Foo{$1}$0')!
t.assert_no_completion_with_insert_text(items, 'Foo')!
})
t.test('imported modules completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
import arrays
import net.http
import foo as bar
import bar.baz as qux
/*caret*/
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_label(items, 'arrays')!
t.assert_has_completion_with_label(items, 'http')!
t.assert_no_completion_with_label(items, 'foo')!
t.assert_has_completion_with_label(items, 'bar')!
t.assert_no_completion_with_label(items, 'baz')!
t.assert_has_completion_with_label(items, 'qux')!
})
t.test('function without params completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
fn function_without_params() {}
fn bar() {
function_without_params/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_insert_text(items, 'function_without_params()$0')!
t.assert_no_completion_with_insert_text(items, 'function_without_params($1)$0')!
})
t.test('function with params completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
fn function_with_params(a int) {}
fn bar() {
function_with_params/*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_insert_text(items, 'function_with_params($1)$0')!
t.assert_no_completion_with_insert_text(items, 'function_with_params()$0')!
})
t.test('static method completion', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
struct TestStruct {}
fn TestStruct.static_method() {}
fn main() {
TestStruct./*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_insert_text(items, 'static_method()$0')!
})
t.test('enum methods', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
enum Colors {
red
green
}
fn (c Colors) some_method() {}
fn main() {
c := Colors.red
c./*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_insert_text(items, 'red')!
t.assert_has_completion_with_insert_text(items, 'green')!
t.assert_has_completion_with_insert_text(items, 'some_method()$0')!
t.assert_no_completion_with_insert_text(items, 'all($1)$0')!
t.assert_no_completion_with_insert_text(items, 'has($1)$0')!
t.assert_no_completion_with_insert_text(items, 'clear($1)$0')!
t.assert_no_completion_with_insert_text(items, 'set($1)$0')!
t.assert_no_completion_with_insert_text(items, 'toggle($1)$0')!
})
t.test('enum flag methods', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
[flag]
enum Colors {
red
green
}
fn main() {
c := Colors.red
c./*caret*/
}
'.trim_indent())!
items := fixture.complete_at_cursor()
t.assert_has_completion_with_insert_text(items, 'red')!
t.assert_has_completion_with_insert_text(items, 'green')!
t.assert_has_completion_with_insert_text(items, 'all($1)$0')!
t.assert_has_completion_with_insert_text(items, 'has($1)$0')!
t.assert_has_completion_with_insert_text(items, 'clear($1)$0')!
t.assert_has_completion_with_insert_text(items, 'set($1)$0')!
t.assert_has_completion_with_insert_text(items, 'toggle($1)$0')!
})
return t
}
================================================
FILE: src/tests/definitions.v
================================================
module tests
import testing
fn definitions() testing.Tester {
mut t := testing.with_name('definitions')
t.test('simple variable definition', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn main() {
name := 100
println(na/*caret*/me)
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'name')!
})
t.test('variable definition from outer scope', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn main() {
name := 100
if true {
println(na/*caret*/me)
}
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'name')!
})
t.test('variable definition from outer scope after inner scope', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn main() {
if true {
println(so/*caret*/me_variable)
}
some_variable := 100
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_no_definition(locations)!
})
t.test('variable definition from for loop', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn main() {
for index := 0; index < 100; index++ {
println(inde/*caret*/x)
}
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'index')!
})
t.test('variable definition from for in loop', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn main() {
for index in 0 .. 100 {
println(inde/*caret*/x)
}
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'index')!
})
t.test('variable definition from if unwrapping', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn main() {
if value := foo() {
println(val/*caret*/ue)
}
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'value')!
})
// TODO: This probably should be prohibited
t.test('variable definition from if unwrapping in else', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn main() {
if value := foo() {
} else {
println(val/*caret*/ue)
}
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'value')!
})
t.test('variable definition from if unwrapping from outside', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
fn main() {
if some_variable := foo() {}
println(some/*caret*/_variable)
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_no_definition(locations)!
})
t.test('field definition', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
struct FooStruct {
name string
}
fn main() {
foo := FooStruct{}
println(foo.na/*caret*/me)
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'name')!
})
t.test('method definition', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
struct Foo {
name string
}
fn (foo Foo) get_name() string {
return foo.name
}
fn main() {
foo := Foo{}
println(foo.get_na/*caret*/me())
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'get_name')!
})
t.test('top level variable', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
name := 100
println(na/*caret*/me)
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'name')!
})
t.test('top level variable from outer scope', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
name := 100
if true {
println(na/*caret*/me)
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'name')!
})
t.test('it as function parameter', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', "
fn foo(it int) {
if i/*caret*/t.str() == 1 {
println('one')
}
}
".trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'it')!
})
t.slow_test('shell script implicit os module', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.vsh', '
abs_/*caret*/path()
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri_from_stdlib(first.target_uri, 'filepath.v')!
})
t.test('shell script implicit os module constant', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.vsh', '
ar/*caret*/gs
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri_from_stdlib(first.target_uri, 'os.c.v')!
})
t.slow_test('shell script local function', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.vsh', '
fn some_fn() {}
/*caret*/some_fn()
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'some_fn')!
})
t.slow_test('shell script local constant', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.vsh', '
const some_constant = 100
/*caret*/some_constant
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'some_constant')!
})
t.test('static methods', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.vsh', '
module main
struct TestStruct {}
fn TestStruct.static_method() {}
fn main() {
TestStruct.static/*caret*/_method()
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'static_method')!
})
t.test('enum inside special flag field method call', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.vsh', '
module main
[flag]
enum Colors {
red
green
}
fn main() {
mut color := Colors.green
color.has(.r/*caret*/ed)
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'red')!
})
t.test('enum fields or', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.vsh', '
module main
[flag]
enum Colors {
red
green
}
fn main() {
mut color := Colors.green | .r/*caret*/ed
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'red')!
})
t.test('implicit str method', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.vsh', '
module main
struct Foo {}
fn main() {
mut foo := Foo{}
foo.s/*caret*/tr()
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri_from_stubs(first.target_uri, 'implicit.v')!
})
t.test('global variable with volatile modifier', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
__global volatile base_revision = 0
fn main() {
println(base/*caret*/_revision)
}
'.trim_indent())!
locations := fixture.definition_at_cursor()
t.assert_has_definition(locations)!
first := locations.first()
t.assert_uri(first.target_uri, fixture.current_file_uri())!
t.assert_definition_name(first, 'base_revision')!
})
return t
}
================================================
FILE: src/tests/documentation.v
================================================
module tests
import testing
fn documentation() testing.Tester {
mut t := testing.with_name('documentation')
t.documentation_test('rendered', 'documentation/rendered.vv')
t.documentation_test('stubs', 'documentation/stubs.vv')
return t
}
================================================
FILE: src/tests/implementations.v
================================================
module tests
import testing
fn implementations() testing.Tester {
mut t := testing.with_name('implementations')
t.test('method interface implementation', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
interface /*caret*/IFoo {
foo()
}
struct FooImpl {}
fn (f FooImpl) foo() {}
'.trim_indent())!
locations := fixture.implementation_at_cursor()
t.assert_has_implementation_with_name(locations, 'FooImpl')!
})
t.test('method interface implementation with return type', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
interface /*caret*/IFoo {
foo() string
}
struct FooImpl {}
fn (f FooImpl) foo() string {}
'.trim_indent())!
locations := fixture.implementation_at_cursor()
t.assert_has_implementation_with_name(locations, 'FooImpl')!
})
t.test('method interface implementation with parameters', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
interface /*caret*/IFoo {
foo(age int, name string) string
}
struct FooImpl {}
fn (f FooImpl) foo(age int, name string) string {}
'.trim_indent())!
locations := fixture.implementation_at_cursor()
t.assert_has_implementation_with_name(locations, 'FooImpl')!
})
t.test('method interface implementation with parameters, parameters types mismatch', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
interface /*caret*/IFoo {
foo(age int, name ?string) string
}
struct FooImpl {}
fn (f FooImpl) foo(age int, name string) string {}
'.trim_indent())!
locations := fixture.implementation_at_cursor()
t.assert_no_implementation_with_name(locations, 'FooImpl')!
})
t.test('method interface implementation with fields', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
interface /*caret*/IFoo {
foo string
}
struct FooImpl {
foo string
}
'.trim_indent())!
locations := fixture.implementation_at_cursor()
t.assert_has_implementation_with_name(locations, 'FooImpl')!
})
t.test('method interface implementation with fields, types mismatch', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
interface /*caret*/IFoo {
foo ?string
}
struct FooImpl {
foo string
}
'.trim_indent())!
locations := fixture.implementation_at_cursor()
t.assert_no_implementation_with_name(locations, 'FooImpl')!
})
t.test('method implementation', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
interface IFoo {
/*caret*/foo()
}
struct FooImpl {}
fn (f FooImpl) foo() {}
'.trim_indent())!
locations := fixture.implementation_at_cursor()
t.assert_has_implementation_with_name(locations, 'foo')!
})
return t
}
================================================
FILE: src/tests/supers.v
================================================
module tests
import testing
fn supers() testing.Tester {
mut t := testing.with_name('supers')
t.test('super interface with method', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
interface IFoo {
foo()
}
struct /*caret*/FooImpl {}
fn (f FooImpl) foo() {}
'.trim_indent())!
locations := fixture.supers_at_cursor()
t.assert_has_super_with_name(locations, 'IFoo')!
})
t.test('super interface with field', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
interface IFoo {
foo string
}
struct /*caret*/FooImpl {
foo string
}
'.trim_indent())!
locations := fixture.supers_at_cursor()
t.assert_has_super_with_name(locations, 'IFoo')!
})
t.test('super interface with method and field', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
interface IFoo {
field string
foo()
}
struct /*caret*/FooImpl {
field string
}
fn (f FooImpl) foo() {}
'.trim_indent())!
locations := fixture.supers_at_cursor()
t.assert_has_super_with_name(locations, 'IFoo')!
})
t.test('super interface with method and field with mismatched type', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
interface IFoo {
field string
foo()
}
struct /*caret*/FooImpl {
field int
}
fn (f FooImpl) foo() {}
'.trim_indent())!
locations := fixture.supers_at_cursor()
t.assert_no_super_with_name(locations, 'IFoo')!
})
t.test('super method', fn (mut t testing.Test, mut fixture testing.Fixture) ! {
fixture.configure_by_text('1.v', '
module main
interface IFoo {
foo()
}
struct FooImpl {}
fn (f FooImpl) /*caret*/foo() {}
'.trim_indent())!
locations := fixture.supers_at_cursor()
t.assert_has_super_with_name(locations, 'foo')!
})
return t
}
================================================
FILE: src/tests/testdata/benchmarks/checker.vv
================================================
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module checker
import os
import strconv
import v.ast
import v.vmod
import v.token
import v.pref
import v.util
import v.util.version
import v.errors
import v.pkgconfig
import v.transformer
import v.comptime
const int_min = int(0x80000000)
const int_max = int(0x7FFFFFFF)
// prevent stack overflows by restricting too deep recursion:
const expr_level_cutoff_limit = 40
const stmt_level_cutoff_limit = 40
const iface_level_cutoff_limit = 100
const generic_fn_cutoff_limit_per_fn = 10_000 // how many times post_process_generic_fns, can visit the same function before bailing out
const generic_fn_postprocess_iterations_cutoff_limit = 1000_000
// array_builtin_methods contains a list of all methods on array, that return other typed arrays,
// i.e. that act as *pseudogeneric* methods, that need compiler support, so that the types of the results
// are properly checked.
// Note that methods that do not return anything, or that return known types, are not listed here, since they are just ordinary non generic methods.
pub const array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort',
'sorted', 'sorted_with_compare', 'contains', 'index', 'wait', 'any', 'all', 'first', 'last',
'pop', 'delete']
pub const array_builtin_methods_chk = token.new_keywords_matcher_from_array_trie(array_builtin_methods)
// TODO: remove `byte` from this list when it is no longer supported
pub const reserved_type_names = ['byte', 'bool', 'char', 'i8', 'i16', 'int', 'i64', 'u8', 'u16',
'u32', 'u64', 'f32', 'f64', 'map', 'string', 'rune', 'usize', 'isize', 'voidptr', 'thread']
pub const reserved_type_names_chk = token.new_keywords_matcher_from_array_trie(reserved_type_names)
pub const vroot_is_deprecated_message = '@VROOT is deprecated, use @VMODROOT or @VEXEROOT instead'
@[heap; minify]
pub struct Checker {
pub mut:
pref &pref.Preferences = unsafe { nil } // Preferences shared from V struct
//
table &ast.Table = unsafe { nil }
file &ast.File = unsafe { nil }
//
nr_errors int
nr_warnings int
nr_notices int
errors []errors.Error
warnings []errors.Warning
notices []errors.Notice
error_lines map[string]bool // dedup errors
warning_lines map[string]bool // dedup warns
notice_lines map[string]bool // dedup notices
error_details []string
should_abort bool // when too many errors/warnings/notices are accumulated, .should_abort becomes true. It is checked in statement/expression loops, so the checker can return early, instead of wasting time.
//
expected_type ast.Type
expected_or_type ast.Type // fn() or { 'this type' } eg. string. expected or block type
expected_expr_type ast.Type // if/match is_expr: expected_type
mod string // current module name
const_var &ast.ConstField = unsafe { nil } // the current constant, when checking const declarations
const_deps []string
const_names []string
global_names []string
locked_names []string // vars that are currently locked
rlocked_names []string // vars that are currently read-locked
in_for_count int // if checker is currently in a for loop
returns bool
scope_returns bool
is_builtin_mod bool // true inside the 'builtin', 'os' or 'strconv' modules; TODO: remove the need for special casing this
is_just_builtin_mod bool // true only inside 'builtin'
is_generated bool // true for `[generated] module xyz` .v files
inside_unsafe bool // true inside `unsafe {}` blocks
inside_const bool // true inside `const ( ... )` blocks
inside_anon_fn bool // true inside `fn() { ... }()`
inside_lambda bool // true inside `|...| ...`
inside_ref_lit bool // true inside `a := &something`
inside_defer bool // true inside `defer {}` blocks
inside_return bool // true inside `return ...` blocks
inside_fn_arg bool // `a`, `b` in `a.f(b)`
inside_ct_attr bool // true inside `[if expr]`
inside_x_is_type bool // true inside the Type expression of `if x is Type {`
inside_generic_struct_init bool
cur_struct_generic_types []ast.Type
cur_struct_concrete_types []ast.Type
skip_flags bool // should `#flag` and `#include` be skipped
fn_level int // 0 for the top level, 1 for `fn abc() {}`, 2 for a nested fn, etc
smartcast_mut_pos token.Pos // match mut foo, if mut foo is Foo
smartcast_cond_pos token.Pos // match cond
ct_cond_stack []ast.Expr
ct_user_defines map[string]ComptimeBranchSkipState
ct_system_defines map[string]ComptimeBranchSkipState
mut:
stmt_level int // the nesting level inside each stmts list;
// .stmt_level is used to check for `evaluated but not used` ExprStmts like `1 << 1`
// 1 for statements directly at each inner scope level;
// increases for `x := if cond { statement_list1} else {statement_list2}`;
// increases for `x := optfn() or { statement_list3 }`;
// files []ast.File
expr_level int // to avoid infinite recursion segfaults due to compiler bugs
ensure_generic_type_level int // to avoid infinite recursion segfaults in ensure_generic_type_specify_type_names
cur_orm_ts ast.TypeSymbol
cur_anon_fn &ast.AnonFn = unsafe { nil }
vmod_file_content string // needed for @VMOD_FILE, contents of the file, *NOT its path**
loop_label string // set when inside a labelled for loop
vweb_gen_types []ast.Type // vweb route checks
timers &util.Timers = util.get_timers()
comptime_info_stack []comptime.ComptimeInfo // stores the values from the above on each $for loop, to make nesting them easier
comptime comptime.ComptimeInfo
fn_scope &ast.Scope = unsafe { nil }
main_fn_decl_node ast.FnDecl
match_exhaustive_cutoff_limit int = 10
is_last_stmt bool
prevent_sum_type_unwrapping_once bool // needed for assign new values to sum type, stopping unwrapping then
using_new_err_struct bool
need_recheck_generic_fns bool // need recheck generic fns because there are cascaded nested generic fn
inside_sql bool // to handle sql table fields pseudo variables
inside_selector_expr bool
inside_interface_deref bool
inside_decl_rhs bool
inside_if_guard bool // true inside the guard condition of `if x := opt() {}`
inside_assign bool
// doing_line_info int // a quick single file run when called with v -line-info (contains line nr to inspect)
// doing_line_path string // same, but stores the path being parsed
is_index_assign bool
comptime_call_pos int // needed for correctly checking use before decl for templates
goto_labels map[string]ast.GotoLabel // to check for unused goto labels
enum_data_type ast.Type
field_data_type ast.Type
variant_data_type ast.Type
fn_return_type ast.Type
orm_table_fields map[string][]ast.StructField // known table structs
//
v_current_commit_hash string // same as old C.V_CURRENT_COMMIT_HASH
}
pub fn new_checker(table &ast.Table, pref_ &pref.Preferences) &Checker {
mut timers_should_print := false
$if time_checking ? {
timers_should_print = true
}
mut checker := &Checker{
table: table
pref: pref_
timers: util.new_timers(should_print: timers_should_print, label: 'checker')
match_exhaustive_cutoff_limit: pref_.checker_match_exhaustive_cutoff_limit
v_current_commit_hash: version.githash(pref_.building_v)
}
checker.comptime = &comptime.ComptimeInfo{
resolver: checker
table: table
}
return checker
}
fn (mut c Checker) reset_checker_state_at_start_of_new_file() {
c.expected_type = ast.void_type
c.expected_or_type = ast.void_type
c.const_var = unsafe { nil }
c.in_for_count = 0
c.returns = false
c.scope_returns = false
c.mod = ''
c.is_builtin_mod = false
c.is_just_builtin_mod = false
c.inside_unsafe = false
c.inside_const = false
c.inside_anon_fn = false
c.inside_ref_lit = false
c.inside_defer = false
c.inside_fn_arg = false
c.inside_ct_attr = false
c.inside_x_is_type = false
c.skip_flags = false
c.fn_level = 0
c.expr_level = 0
c.stmt_level = 0
c.inside_sql = false
c.cur_orm_ts = ast.TypeSymbol{}
c.prevent_sum_type_unwrapping_once = false
c.loop_label = ''
c.using_new_err_struct = false
c.inside_selector_expr = false
c.inside_interface_deref = false
c.inside_decl_rhs = false
c.inside_if_guard = false
c.error_details.clear()
}
pub fn (mut c Checker) check(mut ast_file ast.File) {
c.reset_checker_state_at_start_of_new_file()
c.change_current_file(ast_file)
for i, ast_import in ast_file.imports {
// Imports with the same path and name (self-imports and module name conflicts with builtin module imports)
if c.mod == ast_import.mod {
c.error('cannot import `${ast_import.mod}` into a module with the same name',
ast_import.mod_pos)
}
// Duplicates of regular imports with the default alias (modname) and `as` imports with a custom alias
if c.mod == ast_import.alias {
if c.mod == ast_import.mod.all_after_last('.') {
c.error('cannot import `${ast_import.mod}` into a module with the same name',
ast_import.mod_pos)
}
c.error('cannot import `${ast_import.mod}` as `${ast_import.alias}` into a module with the same name',
ast_import.alias_pos)
}
for sym in ast_import.syms {
full_name := ast_import.mod + '.' + sym.name
if full_name in c.const_names {
c.error('cannot selectively import constant `${sym.name}` from `${ast_import.mod}`, import `${ast_import.mod}` and use `${full_name}` instead',
sym.pos)
}
}
for j in 0 .. i {
if ast_import.mod == ast_file.imports[j].mod {
c.error('`${ast_import.mod}` was already imported on line ${
ast_file.imports[j].mod_pos.line_nr + 1}', ast_import.mod_pos)
} else if ast_import.mod == ast_file.imports[j].alias {
c.error('`${ast_file.imports[j].mod}` was already imported as `${ast_import.alias}` on line ${
ast_file.imports[j].mod_pos.line_nr + 1}', ast_import.mod_pos)
} else if ast_import.alias != '_' && ast_import.alias == ast_file.imports[j].alias {
c.error('`${ast_file.imports[j].mod}` was already imported on line ${
ast_file.imports[j].alias_pos.line_nr + 1}', ast_import.alias_pos)
}
}
}
c.stmt_level = 0
for mut stmt in ast_file.stmts {
if stmt in [ast.ConstDecl, ast.ExprStmt] {
c.expr_level = 0
c.stmt(mut stmt)
}
if c.should_abort {
return
}
}
c.stmt_level = 0
for mut stmt in ast_file.stmts {
if stmt is ast.GlobalDecl {
c.expr_level = 0
c.stmt(mut stmt)
}
if c.should_abort {
return
}
}
c.stmt_level = 0
for mut stmt in ast_file.stmts {
if stmt !in [ast.ConstDecl, ast.GlobalDecl, ast.ExprStmt] {
c.expr_level = 0
c.stmt(mut stmt)
}
if c.should_abort {
return
}
}
c.check_scope_vars(c.file.scope)
c.check_unused_labels()
}
pub fn (mut c Checker) check_scope_vars(sc &ast.Scope) {
if !c.pref.is_repl && !c.file.is_test {
for _, obj in sc.objects {
match obj {
ast.Var {
if !obj.is_used && obj.name[0] != `_` {
if !c.pref.translated && !c.file.is_translated {
c.warn('unused variable: `${obj.name}`', obj.pos)
}
}
if obj.is_mut && !obj.is_changed && !c.is_builtin_mod && obj.name != 'it' {
// if obj.is_mut && !obj.is_changed && !c.is_builtin { //TODO C error bad field not checked
// c.warn('`$obj.name` is declared as mutable, but it was never changed',
// obj.pos)
}
}
else {}
}
}
}
for child in sc.children {
c.check_scope_vars(child)
}
}
// not used right now
pub fn (mut c Checker) check2(mut ast_file ast.File) []errors.Error {
c.change_current_file(ast_file)
for mut stmt in ast_file.stmts {
c.stmt(mut stmt)
}
return c.errors
}
pub fn (mut c Checker) change_current_file(file &ast.File) {
c.file = unsafe { file }
c.vmod_file_content = ''
c.mod = file.mod.name
c.is_generated = file.is_generated
}
pub fn (mut c Checker) check_files(ast_files []&ast.File) {
// println('check_files')
// c.files = ast_files
mut has_main_mod_file := false
mut has_main_fn := false
unsafe {
mut files_from_main_module := []&ast.File{}
for i in 0 .. ast_files.len {
mut file := ast_files[i]
c.timers.start('checker_check ${file.path}')
c.check(mut file)
if file.mod.name == 'main' {
files_from_main_module << file
has_main_mod_file = true
if c.file_has_main_fn(file) {
has_main_fn = true
}
}
c.timers.show('checker_check ${file.path}')
}
if has_main_mod_file && !has_main_fn && files_from_main_module.len > 0 {
if c.pref.is_script && !c.pref.is_test {
// files_from_main_module contain preludes at the start
mut the_main_file := files_from_main_module.last()
the_main_file.stmts << ast.FnDecl{
name: 'main.main'
mod: 'main'
is_main: true
file: the_main_file.path
return_type: ast.void_type
scope: &ast.Scope{
parent: nil
}
}
has_main_fn = true
}
}
}
c.timers.start('checker_post_process_generic_fns')
last_file := c.file
// post process generic functions. must be done after all files have been
// checked, to ensure all generic calls are processed, as this information
// is needed when the generic type is auto inferred from the call argument.
// we may have to loop several times, if there were more concrete types found.
mut post_process_generic_fns_iterations := 0
post_process_iterations_loop: for post_process_generic_fns_iterations <= checker.generic_fn_postprocess_iterations_cutoff_limit {
$if trace_post_process_generic_fns_loop ? {
eprintln('>>>>>>>>> recheck_generic_fns loop iteration: ${post_process_generic_fns_iterations}')
}
for file in ast_files {
if file.generic_fns.len > 0 {
$if trace_post_process_generic_fns_loop ? {
eprintln('>> file.path: ${file.path:-40} | file.generic_fns:' +
file.generic_fns.map(it.name).str())
}
c.change_current_file(file)
c.post_process_generic_fns() or { break post_process_iterations_loop }
}
}
if !c.need_recheck_generic_fns {
break
}
c.need_recheck_generic_fns = false
post_process_generic_fns_iterations++
}
$if trace_post_process_generic_fns_loop ? {
eprintln('>>>>>>>>> recheck_generic_fns loop done, iteration: ${post_process_generic_fns_iterations}')
}
// restore the original c.file && c.mod after post processing
c.change_current_file(last_file)
c.timers.show('checker_post_process_generic_fns')
c.timers.start('checker_verify_all_vweb_routes')
c.verify_all_vweb_routes()
c.timers.show('checker_verify_all_vweb_routes')
if c.pref.is_test {
mut n_test_fns := 0
for _, f in c.table.fns {
if f.is_test {
n_test_fns++
}
}
if n_test_fns == 0 {
c.add_error_detail('The name of a test function in V, should start with `test_`.')
c.add_error_detail('The test function should take 0 parameters, and no return type. Example:')
c.add_error_detail('fn test_xyz(){ assert 2 + 2 == 4 }')
c.error('a _test.v file should have *at least* one `test_` function', token.Pos{})
}
}
// After the main checker run, run the line info check, print line info, and exit (if it's present)
if !c.pref.linfo.is_running && c.pref.line_info != '' { //'' && c.pref.linfo.line_nr == 0 {
// c.do_line_info(c.pref.line_info, ast_files)
println('setting is_running=true, pref.path=${c.pref.linfo.path} curdir' + os.getwd())
c.pref.linfo.is_running = true
for i, file in ast_files {
// println(file.path)
if file.path == c.pref.linfo.path {
println('running c.check_files')
c.check_files([ast_files[i]])
exit(0)
} else if file.path.starts_with('./') {
// Maybe it's a "./foo.v", linfo.path has an absolute path
abs_path := os.join_path(os.getwd(), file.path).replace('/./', '/') // TODO join_path shouldn't have /./
if abs_path == c.pref.linfo.path {
c.check_files([ast_files[i]])
exit(0)
}
}
}
println('failed to find file "${c.pref.linfo.path}"')
exit(0)
}
// Make sure fn main is defined in non lib builds
if c.pref.build_mode == .build_module || c.pref.is_test {
return
}
if c.pref.is_shared {
// shared libs do not need to have a main
return
}
if c.pref.no_builtin {
// `v -no-builtin module/` do not necessarily need to have a `main` function
// This is useful for compiling linux kernel modules for example.
return
}
if !has_main_mod_file {
c.error('project must include a `main` module or be a shared library (compile with `v -shared`)',
token.Pos{})
} else if !has_main_fn && !c.pref.is_o {
c.error('function `main` must be declared in the main module', token.Pos{})
}
}
// do checks specific to files in main module
// returns `true` if a main function is in the file
fn (mut c Checker) file_has_main_fn(file &ast.File) bool {
mut has_main_fn := false
for stmt in file.stmts {
if stmt is ast.FnDecl {
if stmt.name == 'main.main' {
if has_main_fn {
c.error('function `main` is already defined', stmt.pos)
}
has_main_fn = true
if stmt.params.len > 0 {
c.error('function `main` cannot have arguments', stmt.pos)
}
if stmt.return_type != ast.void_type {
c.error('function `main` cannot return values', stmt.pos)
}
if stmt.no_body {
c.error('function `main` must declare a body', stmt.pos)
}
} else if stmt.attrs.contains('console') {
c.error('only `main` can have the `[console]` attribute', stmt.pos)
}
}
}
return has_main_fn
}
fn (mut c Checker) check_valid_snake_case(name string, identifier string, pos token.Pos) {
if c.pref.translated || c.file.is_translated {
return
}
if !c.pref.is_vweb && name.len > 1 && (name[0] == `_` || name.contains('._')) {
c.error('${identifier} `${name}` cannot start with `_`', pos)
}
if !c.pref.experimental && util.contains_capital(name) {
c.error('${identifier} `${name}` cannot contain uppercase letters, use snake_case instead',
pos)
}
}
fn stripped_name(name string) string {
idx := name.last_index('.') or { -1 }
return name[(idx + 1)..]
}
fn (mut c Checker) check_valid_pascal_case(name string, identifier string, pos token.Pos) {
if c.pref.translated || c.file.is_translated {
return
}
sname := stripped_name(name)
if sname.len > 0 && !sname[0].is_capital() {
c.error('${identifier} `${name}` must begin with capital letter', pos)
}
}
fn (mut c Checker) type_decl(node ast.TypeDecl) {
match node {
ast.AliasTypeDecl { c.alias_type_decl(node) }
ast.FnTypeDecl { c.fn_type_decl(node) }
ast.SumTypeDecl { c.sum_type_decl(node) }
}
}
fn (mut c Checker) alias_type_decl(node ast.AliasTypeDecl) {
if c.file.mod.name != 'builtin' && !node.name.starts_with('C.') {
c.check_valid_pascal_case(node.name, 'type alias', node.pos)
}
if !c.ensure_type_exists(node.parent_type, node.type_pos) {
return
}
mut parent_typ_sym := c.table.sym(node.parent_type)
if node.parent_type.has_flag(.result) {
c.add_error_detail('Result types cannot be stored and have to be unwrapped immediately')
c.error('cannot make an alias of Result type', node.type_pos)
}
match parent_typ_sym.kind {
.placeholder, .int_literal, .float_literal {
c.error('unknown aliased type `${parent_typ_sym.name}`', node.type_pos)
}
.alias {
orig_sym := c.table.sym((parent_typ_sym.info as ast.Alias).parent_type)
c.error('type `${parent_typ_sym.str()}` is an alias, use the original alias type `${orig_sym.name}` instead',
node.type_pos)
}
.chan {
c.error('aliases of `chan` types are not allowed', node.type_pos)
}
.thread {
c.error('aliases of `thread` types are not allowed', node.type_pos)
}
.multi_return {
c.error('aliases of function multi return types are not allowed', node.type_pos)
}
.void {
c.error('aliases of the void type are not allowed', node.type_pos)
}
.function {
orig_sym := c.table.type_to_str(node.parent_type)
c.error('type `${parent_typ_sym.str()}` is an alias, use the original alias type `${orig_sym}` instead',
node.type_pos)
}
.struct_ {
if mut parent_typ_sym.info is ast.Struct {
// check if the generic param types have been defined
for ct in parent_typ_sym.info.concrete_types {
ct_sym := c.table.sym(ct)
if ct_sym.kind == .placeholder {
c.error('unknown type `${ct_sym.name}`', node.type_pos)
}
}
}
}
.array {
c.check_alias_vs_element_type_of_parent(node, (parent_typ_sym.info as ast.Array).elem_type,
'array')
}
.array_fixed {
c.check_alias_vs_element_type_of_parent(node, (parent_typ_sym.info as ast.ArrayFixed).elem_type,
'fixed array')
}
.map {
info := parent_typ_sym.info as ast.Map
c.check_alias_vs_element_type_of_parent(node, info.key_type, 'map key')
c.check_alias_vs_element_type_of_parent(node, info.value_type, 'map value')
}
.sum_type {
// TODO: decide whether the following should be allowed. Note that it currently works,
// while `type Sum = int | Sum` is explicitly disallowed:
// type Sum = int | Alias
// type Alias = Sum
}
.none_ {
c.error('cannot create a type alias of `none` as it is a value', node.type_pos)
}
// The rest of the parent symbol kinds are also allowed, since they are either primitive types,
// that in turn do not allow recursion, or are abstract enough so that they can not be checked at comptime:
else {}
/*
.voidptr, .byteptr, .charptr {}
.char, .rune, .bool {}
.string, .enum_, .none_, .any {}
.i8, .i16, .int, .i64, .isize {}
.u8, .u16, .u32, .u64, .usize {}
.f32, .f64 {}
.interface_ {}
.generic_inst {}
.aggregate {}
*/
}
}
fn (mut c Checker) check_alias_vs_element_type_of_parent(node ast.AliasTypeDecl, element_type_of_parent ast.Type, label string) {
if node.typ.idx() != element_type_of_parent.idx() {
return
}
c.error('recursive declarations of aliases are not allowed - the alias `${node.name}` is used in the ${label}',
node.type_pos)
}
fn (mut c Checker) fn_type_decl(node ast.FnTypeDecl) {
c.check_valid_pascal_case(node.name, 'fn type', node.pos)
typ_sym := c.table.sym(node.typ)
fn_typ_info := typ_sym.info as ast.FnType
fn_info := fn_typ_info.func
c.ensure_type_exists(fn_info.return_type, fn_info.return_type_pos)
ret_sym := c.table.sym(fn_info.return_type)
if ret_sym.kind == .placeholder {
c.error('unknown type `${ret_sym.name}`', fn_info.return_type_pos)
}
for arg in fn_info.params {
if !c.ensure_type_exists(arg.typ, arg.type_pos) {
return
}
arg_sym := c.table.sym(arg.typ)
if arg_sym.kind == .placeholder {
c.error('unknown type `${arg_sym.name}`', arg.type_pos)
}
}
}
fn (mut c Checker) sum_type_decl(node ast.SumTypeDecl) {
c.check_valid_pascal_case(node.name, 'sum type', node.pos)
mut names_used := []string{}
for variant in node.variants {
c.ensure_type_exists(variant.typ, variant.pos)
sym := c.table.sym(variant.typ)
if variant.typ.is_ptr() || (sym.info is ast.Alias && sym.info.parent_type.is_ptr()) {
variant_name := sym.name.all_after_last('.')
lb, rb := if sym.kind == .struct_ { '{', '}' } else { '(', ')' }
msg := if sym.info is ast.Alias && sym.info.parent_type.is_ptr() {
'alias as non-reference type'
} else {
'the sum type with non-reference types'
}
c.add_error_detail('declare ${msg}: `${node.name} = ${variant_name} | ...`
and use a reference to the sum type instead: `var := &${node.name}(${variant_name}${lb}val${rb})`')
c.error('sum type cannot hold a reference type', variant.pos)
}
if sym.name in names_used {
c.error('sum type ${node.name} cannot hold the type `${sym.name}` more than once',
variant.pos)
} else if sym.kind in [.placeholder, .int_literal, .float_literal] {
c.error('unknown type `${sym.name}`', variant.pos)
} else if sym.kind == .interface_ && sym.language != .js {
c.error('sum type cannot hold an interface', variant.pos)
} else if sym.kind == .struct_ && sym.language == .js {
c.error('sum type cannot hold a JS struct', variant.pos)
} else if sym.info is ast.Struct {
if sym.info.is_generic {
if !variant.typ.has_flag(.generic) {
c.error('generic struct `${sym.name}` must specify generic type names, e.g. ${sym.name}[T]',
variant.pos)
}
if node.generic_types.len == 0 {
c.error('generic sumtype `${node.name}` must specify generic type names, e.g. ${node.name}[T]',
node.name_pos)
} else {
for typ in sym.info.generic_types {
if typ !in node.generic_types {
sumtype_type_names := node.generic_types.map(c.table.type_to_str(it)).join(', ')
generic_sumtype_name := '${node.name}[${sumtype_type_names}]'
variant_type_names := sym.info.generic_types.map(c.table.type_to_str(it)).join(', ')
generic_variant_name := '${sym.name}[${variant_type_names}]'
c.error('generic type name `${c.table.sym(typ).name}` of generic struct `${generic_variant_name}` is not mentioned in sumtype `${generic_sumtype_name}`',
variant.pos)
}
}
}
}
} else if sym.info is ast.FnType {
if sym.info.func.generic_names.len > 0 {
if !variant.typ.has_flag(.generic) {
c.error('generic fntype `${sym.name}` must specify generic type names, e.g. ${sym.name}[T]',
variant.pos)
}
if node.generic_types.len == 0 {
c.error('generic sumtype `${node.name}` must specify generic type names, e.g. ${node.name}[T]',
node.name_pos)
}
}
if c.table.sym(sym.info.func.return_type).name.ends_with('.${node.name}') {
c.error('sum type `${node.name}` cannot be defined recursively', variant.pos)
}
for param in sym.info.func.params {
if c.table.sym(param.typ).name.ends_with('.${node.name}') {
c.error('sum type `${node.name}` cannot be defined recursively', variant.pos)
}
}
}
if sym.name.trim_string_left(sym.mod + '.') == node.name {
c.error('sum type cannot hold itself', variant.pos)
}
names_used << sym.name
}
}
fn (mut c Checker) expand_iface_embeds(idecl &ast.InterfaceDecl, level int, iface_embeds []ast.InterfaceEmbedding) []ast.InterfaceEmbedding {
// eprintln('> expand_iface_embeds: idecl.name: $idecl.name | level: $level | iface_embeds.len: $iface_embeds.len')
if level > checker.iface_level_cutoff_limit {
c.error('too many interface embedding levels: ${level}, for interface `${idecl.name}`',
idecl.pos)
return []
}
if iface_embeds.len == 0 {
return []
}
mut res := map[int]ast.InterfaceEmbedding{}
mut ares := []ast.InterfaceEmbedding{}
for ie in iface_embeds {
if iface_decl := c.table.interfaces[ie.typ] {
mut list := iface_decl.embeds.clone()
if !iface_decl.are_embeds_expanded {
list = c.expand_iface_embeds(idecl, level + 1, iface_decl.embeds)
unsafe {
c.table.interfaces[ie.typ].embeds = list
}
unsafe {
c.table.interfaces[ie.typ].are_embeds_expanded = true
}
}
for partial in list {
res[partial.typ] = partial
}
}
res[ie.typ] = ie
}
for _, v in res {
ares << v
}
return ares
}
// returns name and position of variable that needs write lock
// also sets `is_changed` to true (TODO update the name to reflect this?)
fn (mut c Checker) fail_if_immutable(mut expr ast.Expr) (string, token.Pos) {
mut to_lock := '' // name of variable that needs lock
mut pos := token.Pos{} // and its position
mut explicit_lock_needed := false
match mut expr {
ast.CastExpr {
// TODO
return '', expr.pos
}
ast.ComptimeSelector {
mut expr_left := expr.left
if mut expr.left is ast.Ident {
if mut expr.left.obj is ast.Var {
if expr.left.obj.ct_type_var != .generic_param {
c.fail_if_immutable(mut expr_left)
}
}
}
return '', expr.pos
}
ast.Ident {
if mut expr.obj is ast.Var {
if !expr.obj.is_mut && !c.pref.translated && !c.file.is_translated
&& !c.inside_unsafe {
if c.inside_anon_fn {
c.error('the closure copy of `${expr.name}` is immutable, declare it with `mut` to make it mutable',
expr.pos)
} else {
c.error('`${expr.name}` is immutable, declare it with `mut` to make it mutable',
expr.pos)
}
}
expr.obj.is_changed = true
if expr.obj.typ.share() == .shared_t {
if expr.name !in c.locked_names {
if c.locked_names.len > 0 || c.rlocked_names.len > 0 {
if expr.name in c.rlocked_names {
c.error('${expr.name} has an `rlock` but needs a `lock`',
expr.pos)
} else {
c.error('${expr.name} must be added to the `lock` list above',
expr.pos)
}
}
to_lock = expr.name
pos = expr.pos
}
}
} else if expr.obj is ast.ConstField && expr.name in c.const_names {
if !c.inside_unsafe && !c.pref.translated {
// TODO fix this in c2v, do not allow modification of all consts
// in translated code
c.error('cannot modify constant `${expr.name}`', expr.pos)
}
}
}
ast.IndexExpr {
if expr.left_type == 0 {
return to_lock, pos
}
left_sym := c.table.sym(expr.left_type)
mut elem_type := ast.Type(0)
mut kind := ''
match left_sym.info {
ast.Array {
elem_type, kind = left_sym.info.elem_type, 'array'
}
ast.ArrayFixed {
elem_type, kind = left_sym.info.elem_type, 'fixed array'
}
ast.Map {
elem_type, kind = left_sym.info.value_type, 'map'
}
else {}
}
if elem_type.has_flag(.shared_f) {
c.error('you have to create a handle and `lock` it to modify `shared` ${kind} element',
expr.left.pos().extend(expr.pos))
}
to_lock, pos = c.fail_if_immutable(mut expr.left)
}
ast.ParExpr {
to_lock, pos = c.fail_if_immutable(mut expr.expr)
}
ast.PrefixExpr {
if expr.op == .mul && expr.right is ast.Ident {
// Do not fail if dereference is immutable:
// `*x = foo()` doesn't modify `x`
} else {
to_lock, pos = c.fail_if_immutable(mut expr.right)
}
}
ast.PostfixExpr {
to_lock, pos = c.fail_if_immutable(mut expr.expr)
}
ast.SelectorExpr {
if expr.expr_type == 0 {
return '', expr.pos
}
// retrieve ast.Field
if !c.ensure_type_exists(expr.expr_type, expr.pos) {
return '', expr.pos
}
mut typ_sym := c.table.final_sym(c.unwrap_generic(expr.expr_type))
match typ_sym.kind {
.struct_ {
mut has_field := true
mut field_info := c.table.find_field_with_embeds(typ_sym, expr.field_name) or {
has_field = false
ast.StructField{}
}
if !has_field {
type_str := c.table.type_to_str(expr.expr_type)
c.error('unknown field `${type_str}.${expr.field_name}`', expr.pos)
return '', expr.pos
}
if field_info.typ.has_flag(.shared_f) {
expr_name := '${expr.expr}.${expr.field_name}'
if expr_name !in c.locked_names {
if c.locked_names.len > 0 || c.rlocked_names.len > 0 {
if expr_name in c.rlocked_names {
c.error('${expr_name} has an `rlock` but needs a `lock`',
expr.pos)
} else {
c.error('${expr_name} must be added to the `lock` list above',
expr.pos)
}
return '', expr.pos
}
to_lock = expr_name
pos = expr.pos
}
} else {
if !field_info.is_mut && !c.pref.translated && !c.file.is_translated {
type_str := c.table.type_to_str(expr.expr_type)
c.error('field `${expr.field_name}` of struct `${type_str}` is immutable',
expr.pos)
}
to_lock, pos = c.fail_if_immutable(mut expr.expr)
}
if to_lock != '' {
// No automatic lock for struct access
explicit_lock_needed = true
}
}
.interface_ {
interface_info := typ_sym.info as ast.Interface
mut field_info := interface_info.find_field(expr.field_name) or {
type_str := c.table.type_to_str(expr.expr_type)
c.error('unknown field `${type_str}.${expr.field_name}`', expr.pos)
return '', expr.pos
}
if !field_info.is_mut {
type_str := c.table.type_to_str(expr.expr_type)
c.error('field `${expr.field_name}` of interface `${type_str}` is immutable',
expr.pos)
return '', expr.pos
}
c.fail_if_immutable(mut expr.expr)
}
.sum_type {
sumtype_info := typ_sym.info as ast.SumType
mut field_info := sumtype_info.find_field(expr.field_name) or {
type_str := c.table.type_to_str(expr.expr_type)
c.error('unknown field `${type_str}.${expr.field_name}`', expr.pos)
return '', expr.pos
}
if !field_info.is_mut {
type_str := c.table.type_to_str(expr.expr_type)
c.error('field `${expr.field_name}` of sumtype `${type_str}` is immutable',
expr.pos)
return '', expr.pos
}
c.fail_if_immutable(mut expr.expr)
}
.array, .string {
// should only happen in `builtin` and unsafe blocks
inside_builtin := c.file.mod.name == 'builtin'
if !inside_builtin && !c.inside_unsafe {
c.error('`${typ_sym.kind}` can not be modified', expr.pos)
return '', expr.pos
}
}
.aggregate, .placeholder {
c.fail_if_immutable(mut expr.expr)
}
else {
c.error('unexpected symbol `${typ_sym.kind}`', expr.pos)
return '', expr.pos
}
}
}
ast.CallExpr {
// TODO: should only work for builtin method
if expr.name == 'slice' {
to_lock, pos = c.fail_if_immutable(mut expr.left)
if to_lock != '' {
// No automatic lock for array slicing (yet(?))
explicit_lock_needed = true
}
}
}
ast.ArrayInit {
c.error('array literal can not be modified', expr.pos)
return '', expr.pos
}
ast.StructInit {
return '', expr.pos
}
ast.InfixExpr {
return '', expr.pos
}
else {
if !expr.is_pure_literal() {
c.error('unexpected expression `${expr.type_name()}`', expr.pos())
return '', expr.pos()
}
}
}
if explicit_lock_needed {
c.error('`${to_lock}` is `shared` and needs explicit lock for `${expr.type_name()}`',
pos)
to_lock = ''
}
return to_lock, pos
}
fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos token.Pos) bool {
if typ == interface_type {
return true
}
$if debug_interface_type_implements ? {
eprintln('> type_implements typ: ${typ.debug()} (`${c.table.type_to_str(typ)}`) | inter_typ: ${interface_type.debug()} (`${c.table.type_to_str(interface_type)}`)')
}
utyp := c.unwrap_generic(typ)
styp := c.table.type_to_str(utyp)
typ_sym := c.table.sym(utyp)
mut inter_sym := c.table.sym(interface_type)
if !inter_sym.is_pub && inter_sym.mod !in [typ_sym.mod, c.mod] && typ_sym.mod != 'builtin' {
c.error('`${styp}` cannot implement private interface `${inter_sym.name}` of other module',
pos)
return false
}
// small hack for JS.Any type. Since `any` in regular V is getting deprecated we have our own JS.Any type for JS backend.
if typ_sym.name == 'JS.Any' {
return true
}
if mut inter_sym.info is ast.Interface {
mut generic_type := interface_type
mut generic_info := inter_sym.info
if inter_sym.info.parent_type.has_flag(.generic) {
parent_sym := c.table.sym(inter_sym.info.parent_type)
if parent_sym.info is ast.Interface {
generic_type = inter_sym.info.parent_type
generic_info = parent_sym.info
}
}
mut inferred_type := interface_type
if generic_info.is_generic {
inferred_type = c.resolve_generic_interface(typ, generic_type, pos)
if inferred_type == 0 {
return false
}
}
if inter_sym.info.is_generic {
if inferred_type == interface_type {
// terminate early, since otherwise we get an infinite recursion/segfault:
return false
}
return c.type_implements(typ, inferred_type, pos)
}
}
// do not check the same type more than once
if mut inter_sym.info is ast.Interface {
for t in inter_sym.info.types {
if t.idx() == utyp.idx() {
return true
}
}
}
if utyp.idx() == interface_type.idx() {
// same type -> already casted to the interface
return true
}
if interface_type.idx() == ast.error_type_idx && utyp.idx() == ast.none_type_idx {
// `none` "implements" the Error interface
return true
}
if typ_sym.kind == .interface_ && inter_sym.kind == .interface_ && !styp.starts_with('JS.')
&& !inter_sym.name.starts_with('JS.') {
c.error('cannot implement interface `${inter_sym.name}` with a different interface `${styp}`',
pos)
}
imethods := if inter_sym.kind == .interface_ {
(inter_sym.info as ast.Interface).methods
} else {
inter_sym.methods
}
// voidptr is an escape hatch, it should be allowed to be passed
if utyp != ast.voidptr_type && utyp != ast.nil_type {
mut are_methods_implemented := true
// Verify methods
for imethod in imethods {
method := c.table.find_method_with_embeds(typ_sym, imethod.name) or {
// >> Hack to allow old style custom error implementations
// TODO: remove once deprecation period for `IError` methods has ended
if inter_sym.idx == ast.error_type_idx
&& (imethod.name == 'msg' || imethod.name == 'code') {
c.note("`${styp}` doesn't implement method `${imethod.name}` of interface `${inter_sym.name}`. The usage of fields is being deprecated in favor of methods.",
pos)
return false
}
// <<
typ_sym.find_method_with_generic_parent(imethod.name) or {
c.error("`${styp}` doesn't implement method `${imethod.name}` of interface `${inter_sym.name}`",
pos)
are_methods_implemented = false
continue
}
}
msg := c.table.is_same_method(imethod, method)
if msg.len > 0 {
sig := c.table.fn_signature(imethod, skip_receiver: false)
typ_sig := c.table.fn_signature(method, skip_receiver: false)
c.add_error_detail('${inter_sym.name} has `${sig}`')
c.add_error_detail(' ${typ_sym.name} has `${typ_sig}`')
c.error('`${styp}` incorrectly implements method `${imethod.name}` of interface `${inter_sym.name}`: ${msg}',
pos)
return false
}
}
if !are_methods_implemented {
return false
}
}
// Verify fields
if mut inter_sym.info is ast.Interface {
for ifield in inter_sym.info.fields {
if field := c.table.find_field_with_embeds(typ_sym, ifield.name) {
if ifield.typ != field.typ {
exp := c.table.type_to_str(ifield.typ)
got := c.table.type_to_str(field.typ)
c.error('`${styp}` incorrectly implements field `${ifield.name}` of interface `${inter_sym.name}`, expected `${exp}`, got `${got}`',
pos)
return false
} else if ifield.is_mut && !(field.is_mut || field.is_global) {
c.error('`${styp}` incorrectly implements interface `${inter_sym.name}`, field `${ifield.name}` must be mutable',
pos)
return false
}
continue
}
// voidptr is an escape hatch, it should be allowed to be passed
if utyp != ast.voidptr_type && utyp != ast.nil_type {
// >> Hack to allow old style custom error implementations
// TODO: remove once deprecation period for `IError` methods has ended
if inter_sym.idx == ast.error_type_idx
&& (ifield.name == 'msg' || ifield.name == 'code') {
// do nothing, necessary warnings are already printed
} else {
// <<
c.error("`${styp}` doesn't implement field `${ifield.name}` of interface `${inter_sym.name}`",
pos)
}
}
}
if utyp != ast.voidptr_type && utyp != ast.nil_type && !inter_sym.info.types.contains(utyp) {
inter_sym.info.types << utyp
}
if !inter_sym.info.types.contains(ast.voidptr_type) {
inter_sym.info.types << ast.voidptr_type
}
}
return true
}
// helper for expr_or_block_err
fn is_field_to_description(expr_name string, is_field bool) string {
return if is_field {
'field `${expr_name}` is not'
} else {
'function `${expr_name}` does not return'
}
}
fn (mut c Checker) expr_or_block_err(kind ast.OrKind, expr_name string, pos token.Pos, is_field bool) {
match kind {
.absent {
// do nothing, most common case; do not be tempted to move the call to is_field_to_description above it, since that will slow it down
}
.block {
obj_does_not_return_or_is_not := is_field_to_description(expr_name, is_field)
c.error('unexpected `or` block, the ${obj_does_not_return_or_is_not} an Option or a Result',
pos)
}
.propagate_option {
obj_does_not_return_or_is_not := is_field_to_description(expr_name, is_field)
c.error('unexpected `?`, the ${obj_does_not_return_or_is_not} an Option',
pos)
}
.propagate_result {
obj_does_not_return_or_is_not := is_field_to_description(expr_name, is_field)
c.error('unexpected `!`, the ${obj_does_not_return_or_is_not} a Result', pos)
}
}
}
// return the actual type of the expression, once the result or option type is handled
fn (mut c Checker) check_expr_option_or_result_call(expr ast.Expr, ret_type ast.Type) ast.Type {
match expr {
ast.CallExpr {
mut expr_ret_type := expr.return_type
if expr_ret_type != 0 && c.table.sym(expr_ret_type).kind == .alias {
unaliased_ret_type := c.table.unaliased_type(expr_ret_type)
if unaliased_ret_type.has_option_or_result() {
expr_ret_type = unaliased_ret_type
}
}
if expr_ret_type.has_option_or_result() {
return_modifier_kind := if expr_ret_type.has_flag(.option) {
'an Option'
} else {
'a Result'
}
return_modifier := if expr_ret_type.has_flag(.option) { '?' } else { '!' }
if expr_ret_type.has_flag(.result) && expr.or_block.kind == .absent {
if c.inside_defer {
c.error('${expr.name}() returns ${return_modifier_kind}, so it should have an `or {}` block at the end',
expr.pos)
} else {
c.error('${expr.name}() returns ${return_modifier_kind}, so it should have either an `or {}` block, or `${return_modifier}` at the end',
expr.pos)
}
} else {
if expr.or_block.kind != .absent {
c.check_or_expr(expr.or_block, ret_type, expr_ret_type, expr)
}
}
return ret_type.clear_flag(.result)
} else {
c.expr_or_block_err(expr.or_block.kind, expr.name, expr.or_block.pos,
false)
}
}
ast.SelectorExpr {
if c.table.sym(ret_type).kind != .chan {
if expr.typ.has_option_or_result() {
with_modifier_kind := if expr.typ.has_flag(.option) {
'an Option'
} else {
'a Result'
}
with_modifier := if expr.typ.has_flag(.option) { '?' } else { '!' }
if expr.typ.has_flag(.result) && expr.or_block.kind == .absent {
if c.inside_defer {
c.error('field `${expr.field_name}` is ${with_modifier_kind}, so it should have an `or {}` block at the end',
expr.pos)
} else {
c.error('field `${expr.field_name}` is ${with_modifier_kind}, so it should have either an `or {}` block, or `${with_modifier}` at the end',
expr.pos)
}
} else {
if expr.or_block.kind != .absent {
c.check_or_expr(expr.or_block, ret_type, expr.typ, expr)
}
}
return ret_type.clear_flag(.result)
} else {
c.expr_or_block_err(expr.or_block.kind, expr.field_name, expr.or_block.pos,
true)
}
}
}
ast.IndexExpr {
if expr.or_expr.kind != .absent {
mut return_none_or_error := false
if expr.or_expr.stmts.len > 0 {
last_stmt := expr.or_expr.stmts.last()
if last_stmt is ast.ExprStmt {
if c.inside_return && last_stmt.typ in [ast.none_type, ast.error_type] {
return_none_or_error = true
}
}
}
if return_none_or_error {
c.check_expr_option_or_result_call(expr.or_expr, c.table.cur_fn.return_type)
} else {
c.check_or_expr(expr.or_expr, ret_type, ret_type.set_flag(.result),
expr)
}
}
}
ast.CastExpr {
c.check_expr_option_or_result_call(expr.expr, ret_type)
}
ast.AsCast {
c.check_expr_option_or_result_call(expr.expr, ret_type)
}
ast.ParExpr {
c.check_expr_option_or_result_call(expr.expr, ret_type)
}
else {}
}
return ret_type
}
fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_return_type ast.Type, expr ast.Expr) {
if node.kind == .propagate_option {
if c.table.cur_fn != unsafe { nil } && !c.table.cur_fn.return_type.has_flag(.option)
&& !c.table.cur_fn.is_main && !c.table.cur_fn.is_test && !c.inside_const {
c.add_instruction_for_option_type()
if expr is ast.Ident {
c.error('to propagate the Option, `${c.table.cur_fn.name}` must return an Option type',
expr.pos)
} else {
c.error('to propagate the call, `${c.table.cur_fn.name}` must return an Option type',
node.pos)
}
}
if expr !is ast.Ident && !expr_return_type.has_flag(.option) {
if expr_return_type.has_flag(.result) {
c.error('propagating a Result like an Option is deprecated, use `foo()!` instead of `foo()?`',
node.pos)
} else {
c.error('to propagate an Option, the call must also return an Option type',
node.pos)
}
}
return
}
if node.kind == .propagate_result {
if c.table.cur_fn != unsafe { nil } && !c.table.cur_fn.return_type.has_flag(.result)
&& !c.table.cur_fn.is_main && !c.table.cur_fn.is_test && !c.inside_const {
c.add_instruction_for_result_type()
c.error('to propagate the call, `${c.table.cur_fn.name}` must return a Result type',
node.pos)
}
if !expr_return_type.has_flag(.result) {
c.error('to propagate a Result, the call must also return a Result type',
node.pos)
}
return
}
if node.stmts.len == 0 {
if ret_type != ast.void_type {
// x := f() or {}
c.error('assignment requires a non empty `or {}` block', node.pos)
}
// allow `f() or {}`
return
}
mut last_stmt := node.stmts.last()
c.check_or_last_stmt(mut last_stmt, ret_type, expr_return_type.clear_option_and_result())
}
fn (mut c Checker) check_or_last_stmt(mut stmt ast.Stmt, ret_type ast.Type, expr_return_type ast.Type) {
if ret_type != ast.void_type {
match mut stmt {
ast.ExprStmt {
c.expected_type = ret_type
c.expected_or_type = ret_type.clear_option_and_result()
last_stmt_typ := c.expr(mut stmt.expr)
if last_stmt_typ.has_flag(.option) || last_stmt_typ == ast.none_type {
if stmt.expr in [ast.Ident, ast.SelectorExpr, ast.CallExpr, ast.None, ast.CastExpr] {
expected_type_name := c.table.type_to_str(ret_type.clear_option_and_result())
got_type_name := c.table.type_to_str(last_stmt_typ)
c.error('`or` block must provide a value of type `${expected_type_name}`, not `${got_type_name}`',
stmt.expr.pos())
return
}
}
c.expected_or_type = ast.void_type
type_fits := c.check_types(last_stmt_typ, ret_type)
&& last_stmt_typ.nr_muls() == ret_type.nr_muls()
is_noreturn := is_noreturn_callexpr(stmt.expr)
if type_fits || is_noreturn {
return
}
if stmt.typ == ast.void_type {
if mut stmt.expr is ast.IfExpr {
for mut branch in stmt.expr.branches {
if branch.stmts.len > 0 {
mut stmt_ := branch.stmts.last()
c.check_or_last_stmt(mut stmt_, ret_type, expr_return_type)
}
}
return
} else if mut stmt.expr is ast.MatchExpr {
for mut branch in stmt.expr.branches {
if branch.stmts.len > 0 {
mut stmt_ := branch.stmts.last()
c.check_or_last_stmt(mut stmt_, ret_type, expr_return_type)
}
}
return
}
expected_type_name := c.table.type_to_str(ret_type.clear_option_and_result())
c.error('`or` block must provide a default value of type `${expected_type_name}`, or return/continue/break or call a [noreturn] function like panic(err) or exit(1)',
stmt.expr.pos())
} else {
if ret_type.is_ptr() && last_stmt_typ.is_pointer()
&& c.table.sym(last_stmt_typ).kind == .voidptr {
return
}
if last_stmt_typ == ast.none_type_idx && ret_type.has_flag(.option) {
return
}
type_name := c.table.type_to_str(last_stmt_typ)
expected_type_name := c.table.type_to_str(ret_type.clear_option_and_result())
c.error('wrong return type `${type_name}` in the `or {}` block, expected `${expected_type_name}`',
stmt.expr.pos())
}
}
ast.BranchStmt {
if stmt.kind !in [.key_continue, .key_break] {
c.error('only break/continue is allowed as a branch statement in the end of an `or {}` block',
stmt.pos)
return
}
}
ast.Return {}
else {
expected_type_name := c.table.type_to_str(ret_type.clear_option_and_result())
c.error('last statement in the `or {}` block should be an expression of type `${expected_type_name}` or exit parent scope',
stmt.pos)
}
}
} else if mut stmt is ast.ExprStmt {
match mut stmt.expr {
ast.IfExpr {
for mut branch in stmt.expr.branches {
if branch.stmts.len > 0 {
mut stmt_ := branch.stmts.last()
c.check_or_last_stmt(mut stmt_, ret_type, expr_return_type)
}
}
}
ast.MatchExpr {
for mut branch in stmt.expr.branches {
if branch.stmts.len > 0 {
mut stmt_ := branch.stmts.last()
c.check_or_last_stmt(mut stmt_, ret_type, expr_return_type)
}
}
}
else {
if stmt.typ == ast.void_type || expr_return_type == ast.void_type {
return
}
if is_noreturn_callexpr(stmt.expr) {
return
}
if c.check_types(stmt.typ, expr_return_type) {
if stmt.typ.is_ptr() == expr_return_type.is_ptr()
|| (expr_return_type.is_ptr() && stmt.typ.is_pointer()
&& c.table.sym(stmt.typ).kind == .voidptr) {
return
}
}
// opt_returning_string() or { ... 123 }
type_name := c.table.type_to_str(stmt.typ)
expr_return_type_name := c.table.type_to_str(expr_return_type)
c.error('the default expression type in the `or` block should be `${expr_return_type_name}`, instead you gave a value of type `${type_name}`',
stmt.expr.pos())
}
}
}
}
fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type {
prevent_sum_type_unwrapping_once := c.prevent_sum_type_unwrapping_once
c.prevent_sum_type_unwrapping_once = false
using_new_err_struct_save := c.using_new_err_struct
// TODO remove; this avoids a breaking change in syntax
if '${node.expr}' == 'err' {
c.using_new_err_struct = true
}
// T.name, typeof(expr).name
mut name_type := 0
mut node_expr := node.expr
match mut node.expr {
ast.Ident {
name := node.expr.name
valid_generic := util.is_generic_type_name(name) && c.table.cur_fn != unsafe { nil }
&& name in c.table.cur_fn.generic_names
if valid_generic {
name_type = ast.Type(c.table.find_type_idx(name)).set_flag(.generic)
}
}
ast.TypeOf {
// TODO: fix this weird case, since just `typeof(x)` is `string`, but `|typeof(x).| propertyname` should be the actual type,
// so that we can get other metadata properties of the type, depending on `propertyname` (one of `name` or `idx` for now).
// A better alternative would be a new `meta(x).propertyname`, that does not have a `meta(x)` case (an error),
// or if it does, it should be a normal constant struct value, just filled at comptime.
c.expr(mut node_expr)
name_type = node.expr.typ
}
ast.AsCast {
c.add_error_detail('for example `(${node.expr.expr} as ${c.table.type_to_str(node.expr.typ)}).${node.field_name}`')
c.error('indeterminate `as` cast, use parenthesis to clarity', node.expr.pos)
}
else {}
}
if name_type > 0 {
node.name_type = name_type
match node.gkind_field {
.name {
return ast.string_type
}
.typ {
return ast.int_type
}
else {
if node.field_name == 'name' {
return ast.string_type
} else if node.field_name == 'idx' {
return ast.int_type
}
c.error('invalid field `.${node.field_name}` for type `${node.expr}`',
node.pos)
return ast.string_type
}
}
}
// evaluates comptime field. (from T.fields)
if c.comptime.check_comptime_is_field_selector(node) {
if c.comptime.check_comptime_is_field_selector_bool(node) {
node.expr_type = ast.bool_type
return node.expr_type
}
}
old_selector_expr := c.inside_selector_expr
c.inside_selector_expr = true
mut typ := c.expr(mut node.expr)
if node.expr.is_auto_deref_var() {
if mut node.expr is ast.Ident {
if mut node.expr.obj is ast.Var {
typ = node.expr.obj.typ
}
}
}
c.inside_selector_expr = old_selector_expr
c.using_new_err_struct = using_new_err_struct_save
if typ == ast.void_type_idx {
// This means that the field has an undefined type.
// This error was handled before.
c.error('`${node.expr}` does not return a value', node.pos)
node.expr_type = ast.void_type
return ast.void_type
} else if c.comptime.inside_comptime_for && typ == c.enum_data_type
&& node.field_name == 'value' {
// for comp-time enum.values
node.expr_type = c.comptime.type_map['${c.comptime.comptime_for_enum_var}.typ']
node.typ = typ
return node.expr_type
}
node.expr_type = typ
if !(node.expr is ast.Ident && node.expr.kind == .constant) {
if node.expr_type.has_flag(.option) {
c.error('cannot access fields of an Option, handle the error with `or {...}` or propagate it with `?`',
node.pos)
} else if node.expr_type.has_flag(.result) {
c.error('cannot access fields of a Result, handle the error with `or {...}` or propagate it with `!`',
node.pos)
}
}
field_name := node.field_name
sym := c.table.sym(typ)
if (typ.has_flag(.variadic) || sym.kind == .array_fixed) && field_name == 'len' {
node.typ = ast.int_type
return ast.int_type
}
if sym.kind == .chan {
if field_name == 'closed' {
node.typ = ast.bool_type
return ast.bool_type
} else if field_name in ['len', 'cap'] {
node.typ = ast.u32_type
return ast.u32_type
}
}
mut unknown_field_msg := 'type `${sym.name}` has no field named `${field_name}`'
mut has_field := false
mut field := ast.StructField{}
if field_name.len > 0 && field_name[0].is_capital() && sym.info is ast.Struct
&& sym.language == .v {
// x.Foo.y => access the embedded struct
for embed in sym.info.embeds {
embed_sym := c.table.sym(embed)
if embed_sym.embed_name() == field_name {
node.typ = embed
return embed
}
}
} else {
if f := c.table.find_field(sym, field_name) {
has_field = true
field = f
} else {
// look for embedded field
has_field = true
mut embed_types := []ast.Type{}
field, embed_types = c.table.find_field_from_embeds(sym, field_name) or {
if err.msg() != '' {
c.error(err.msg(), node.pos)
}
has_field = false
ast.StructField{}, []ast.Type{}
}
node.from_embed_types = embed_types
if sym.kind in [.aggregate, .sum_type] {
unknown_field_msg = err.msg()
}
}
if !c.inside_unsafe {
if sym.info is ast.Struct {
if sym.info.is_union && node.next_token !in token.assign_tokens {
if !c.pref.translated && !c.file.is_translated {
c.warn('reading a union field (or its address) requires `unsafe`',
node.pos)
}
}
}
}
if typ.has_flag(.generic) && !has_field {
gs := c.table.sym(c.unwrap_generic(typ))
if f := c.table.find_field(gs, field_name) {
has_field = true
field = f
} else {
// look for embedded field
has_field = true
mut embed_types := []ast.Type{}
field, embed_types = c.table.find_field_from_embeds(gs, field_name) or {
if err.msg() != '' {
c.error(err.msg(), node.pos)
}
has_field = false
ast.StructField{}, []ast.Type{}
}
node.from_embed_types = embed_types
node.generic_from_embed_types << embed_types
}
}
}
// >> Hack to allow old style custom error implementations
// TODO: remove once deprecation period for `IError` methods has ended
if sym.idx == ast.error_type_idx && !c.is_just_builtin_mod
&& (field_name == 'msg' || field_name == 'code') {
method := c.table.find_method(sym, field_name) or {
c.error('invalid `IError` interface implementation: ${err}', node.pos)
return ast.void_type
}
c.note('the `.${field_name}` field on `IError` is deprecated, and will be removed after 2022-06-01, use `.${field_name}()` instead.',
node.pos)
return method.return_type
}
// <<<
if has_field {
is_used_outside := sym.mod != c.mod
if is_used_outside && !field.is_pub && sym.language != .c {
unwrapped_sym := c.table.sym(c.unwrap_generic(typ))
c.error('field `${unwrapped_sym.name}.${field_name}` is not public', node.pos)
}
field_sym := c.table.sym(field.typ)
if field.is_deprecated && is_used_outside {
c.deprecate('field', field_name, field.attrs, node.pos)
}
if field_sym.kind in [.sum_type, .interface_] {
if !prevent_sum_type_unwrapping_once {
if scope_field := node.scope.find_struct_field(node.expr.str(), typ, field_name) {
return scope_field.smartcasts.last()
}
}
}
node.typ = field.typ
if node.or_block.kind == .block {
c.expected_or_type = node.typ.clear_option_and_result()
c.stmts_ending_with_expression(mut node.or_block.stmts)
c.check_or_expr(node.or_block, node.typ, c.expected_or_type, node)
c.expected_or_type = ast.void_type
}
return field.typ
}
if mut method := sym.find_method_with_generic_parent(field_name) {
if c.expected_type != 0 && c.expected_type != ast.none_type {
fn_type := ast.new_type(c.table.find_or_register_fn_type(method, false, true))
// if the expected type includes the receiver, don't hide it behind a closure
if c.check_types(fn_type, c.expected_type) {
return fn_type
}
}
receiver := method.params[0].typ
if receiver.nr_muls() > 0 {
if !c.inside_unsafe {
rec_sym := c.table.sym(receiver.set_nr_muls(0))
if !rec_sym.is_heap() {
suggestion := if rec_sym.kind == .struct_ {
'declaring `${rec_sym.name}` as `[heap]`'
} else {
'wrapping the `${rec_sym.name}` object in a `struct` declared as `[heap]`'
}
c.error('method `${c.table.type_to_str(receiver.idx())}.${method.name}` cannot be used as a variable outside `unsafe` blocks as its receiver might refer to an object stored on stack. Consider ${suggestion}.',
node.expr.pos().extend(node.pos))
}
}
}
method.params = method.params[1..]
node.has_hidden_receiver = true
method.name = ''
fn_type := ast.new_type(c.table.find_or_register_fn_type(method, false, true))
node.typ = fn_type
return fn_type
}
if sym.kind !in [.struct_, .aggregate, .interface_, .sum_type] {
if sym.kind != .placeholder {
unwrapped_sym := c.table.sym(c.unwrap_generic(typ))
if unwrapped_sym.kind == .array_fixed && node.field_name == 'len' {
node.typ = ast.int_type
return ast.int_type
}
c.error('`${unwrapped_sym.name}` has no property `${node.field_name}`', node.pos)
}
} else {
if sym.info is ast.Struct {
if c.smartcast_mut_pos != token.Pos{} {
c.note('smartcasting requires either an immutable value, or an explicit mut keyword before the value',
c.smartcast_mut_pos)
}
suggestion := util.new_suggestion(field_name, sym.info.fields.map(it.name))
c.error(suggestion.say(unknown_field_msg), node.pos)
return ast.void_type
}
if c.smartcast_mut_pos != token.Pos{} {
c.note('smartcasting requires either an immutable value, or an explicit mut keyword before the value',
c.smartcast_mut_pos)
}
if c.smartcast_cond_pos != token.Pos{} {
c.note('smartcast can only be used on the ident or selector, e.g. match foo, match foo.bar',
c.smartcast_cond_pos)
}
c.error(unknown_field_msg, node.pos)
}
return ast.void_type
}
fn (mut c Checker) const_decl(mut node ast.ConstDecl) {
if node.fields.len == 0 {
c.warn('const block must have at least 1 declaration', node.pos)
}
for mut field in node.fields {
if checker.reserved_type_names_chk.matches(util.no_cur_mod(field.name, c.mod)) {
c.error('invalid use of reserved type `${field.name}` as a const name', field.pos)
}
// TODO Check const name once the syntax is decided
if field.name in c.const_names {
name_pos := token.Pos{
...field.pos
len: util.no_cur_mod(field.name, c.mod).len
}
c.error('duplicate const `${field.name}`', name_pos)
}
if field.expr is ast.CallExpr {
sym := c.table.sym(c.check_expr_option_or_result_call(field.expr, c.expr(mut field.expr)))
if sym.kind == .multi_return {
c.error('const declarations do not support multiple return values yet',
field.expr.pos())
}
}
const_name := field.name.all_after_last('.')
if const_name == c.mod && const_name != 'main' {
name_pos := token.Pos{
...field.pos
len: util.no_cur_mod(field.name, c.mod).len
}
c.error('duplicate of a module name `${field.name}`', name_pos)
}
c.const_names << field.name
}
for i, mut field in node.fields {
c.const_deps << field.name
prev_const_var := c.const_var
c.const_var = unsafe { field }
mut typ := c.check_expr_option_or_result_call(field.expr, c.expr(mut field.expr))
if ct_value := c.eval_comptime_const_expr(field.expr, 0) {
field.comptime_expr_value = ct_value
if ct_value is u64 {
typ = ast.u64_type
}
}
node.fields[i].typ = ast.mktyp(typ)
if mut field.expr is ast.IfExpr {
for branch in field.expr.branches {
if branch.stmts.len > 0 && branch.stmts.last() is ast.ExprStmt
&& branch.stmts.last().typ != ast.void_type {
field.expr.is_expr = true
field.expr.typ = (branch.stmts.last() as ast.ExprStmt).typ
field.typ = field.expr.typ
// update ConstField object's type in table
if mut obj := c.file.global_scope.find(field.name) {
if mut obj is ast.ConstField {
obj.typ = field.typ
}
}
break
}
}
}
// Check for int overflow
if field.typ == ast.int_type {
if mut field.expr is ast.IntegerLiteral {
mut is_large := field.expr.val.len > 13
if !is_large && field.expr.val.len > 8 {
val := field.expr.val.i64()
is_large = val > checker.int_max || val < checker.int_min
}
if is_large {
c.error('overflow in implicit type `int`, use explicit type casting instead',
field.expr.pos)
}
}
}
c.const_deps = []
c.const_var = prev_const_var
}
}
fn (mut c Checker) enum_decl(mut node ast.EnumDecl) {
c.check_valid_pascal_case(node.name, 'enum name', node.pos)
mut useen := []u64{}
mut iseen := []i64{}
mut seen_enum_field_names := map[string]int{}
if node.fields.len == 0 {
c.error('enum cannot be empty', node.pos)
}
/*
if node.is_pub && c.mod == 'builtin' {
c.error('`builtin` module cannot have enums', node.pos)
}
*/
mut enum_imin := i64(0)
mut enum_imax := i64(0)
mut enum_umin := u64(0)
mut enum_umax := u64(0)
mut signed := true
senum_type := c.table.type_to_str(node.typ)
match node.typ {
ast.i8_type {
signed, enum_imin, enum_imax = true, min_i8, max_i8
}
ast.i16_type {
signed, enum_imin, enum_imax = true, min_i16, max_i16
}
ast.int_type {
signed, enum_imin, enum_imax = true, min_i32, max_i32
}
ast.i64_type {
signed, enum_imin, enum_imax = true, min_i64, max_i64
}
//
ast.u8_type {
signed, enum_umin, enum_umax = false, min_u8, max_u8
}
ast.u16_type {
signed, enum_umin, enum_umax = false, min_u16, max_u16
}
ast.u32_type {
signed, enum_umin, enum_umax = false, min_u32, max_u32
}
ast.u64_type {
signed, enum_umin, enum_umax = false, min_u64, max_u64
}
else {
if senum_type == 'i32' {
signed, enum_imin, enum_imax = true, min_i32, max_i32
} else {
c.error('`${senum_type}` is not one of `i8`,`i16`,`i32`,`int`,`i64`,`u8`,`u16`,`u32`,`u64`',
node.typ_pos)
}
}
}
if enum_imin > 0 {
// ensure that the minimum value is negative, even with msvc, which has a bug that makes -2147483648 positive ...
enum_imin *= -1
}
for i, mut field in node.fields {
if !c.pref.experimental && util.contains_capital(field.name) {
// TODO C2V uses hundreds of enums with capitals, remove -experimental check once it's handled
c.error('field name `${field.name}` cannot contain uppercase letters, use snake_case instead',
field.pos)
}
if _ := seen_enum_field_names[field.name] {
c.error('duplicate enum field name `${field.name}`', field.pos)
}
seen_enum_field_names[field.name] = i
if field.has_expr {
match mut field.expr {
ast.IntegerLiteral {
c.check_enum_field_integer_literal(field.expr, signed, node.is_multi_allowed,
senum_type, field.expr.pos, mut useen, enum_umin, enum_umax, mut
iseen, enum_imin, enum_imax)
}
ast.InfixExpr {
// Handle `enum Foo { x = 1 + 2 }`
c.infix_expr(mut field.expr)
mut t := transformer.new_transformer_with_table(c.table, c.pref)
folded_expr := t.infix_expr(mut field.expr)
if folded_expr is ast.IntegerLiteral {
c.check_enum_field_integer_literal(folded_expr, signed, node.is_multi_allowed,
senum_type, field.expr.pos, mut useen, enum_umin, enum_umax, mut
iseen, enum_imin, enum_imax)
}
}
ast.ParExpr {
c.expr(mut field.expr.expr)
mut t := transformer.new_transformer_with_table(c.table, c.pref)
folded_expr := t.expr(mut field.expr.expr)
if folded_expr is ast.IntegerLiteral {
c.check_enum_field_integer_literal(folded_expr, signed, node.is_multi_allowed,
senum_type, field.expr.pos, mut useen, enum_umin, enum_umax, mut
iseen, enum_imin, enum_imax)
}
}
ast.CastExpr {
fe_type := c.cast_expr(mut field.expr)
if node.typ != fe_type {
sfe_type := c.table.type_to_str(fe_type)
c.error('the type of the enum value `${sfe_type}` != the enum type itself `${senum_type}`',
field.expr.pos)
}
if !fe_type.is_pure_int() {
c.error('the type of an enum value must be an integer type, like i8, u8, int, u64 etc.',
field.expr.pos)
}
}
else {
if mut field.expr is ast.Ident {
if field.expr.language == .c {
continue
}
if field.expr.kind == .unresolved {
c.ident(mut field.expr)
}
if field.expr.kind == .constant && field.expr.obj.typ.is_int() {
// accepts int constants as enum value
continue
}
}
mut pos := field.expr.pos()
if pos.pos == 0 {
pos = field.pos
}
c.error('the default value for an enum has to be an integer', pos)
}
}
} else {
if signed {
if iseen.len > 0 {
ilast := iseen.last()
if ilast == enum_imax {
c.error('enum value overflows type `${senum_type}`, which has a maximum value of ${enum_imax}',
field.pos)
} else if !c.pref.translated && !c.file.is_translated && !node.is_multi_allowed
&& ilast + 1 in iseen {
c.error('enum value `${ilast + 1}` already exists', field.pos)
}
iseen << ilast + 1
} else {
iseen << 0
}
} else {
if useen.len > 0 {
ulast := useen.last()
if ulast == enum_umax {
c.error('enum value overflows type `${senum_type}`, which has a maximum value of ${enum_umax}',
field.pos)
} else if !c.pref.translated && !c.file.is_translated && !node.is_multi_allowed
&& ulast + 1 in useen {
c.error('enum value `${ulast + 1}` already exists', field.pos)
}
useen << ulast + 1
} else {
useen << 0
}
}
}
}
}
fn (mut c Checker) check_enum_field_integer_literal(expr ast.IntegerLiteral, is_signed bool, is_multi_allowed bool, styp string, pos token.Pos, mut useen []u64, umin u64, umax u64, mut iseen []i64, imin i64, imax i64) {
mut overflows := false
mut uval := u64(0)
mut ival := i64(0)
if is_signed {
val := expr.val.i64()
ival = val
if val < imin || val >= imax {
c.error('enum value `${expr.val}` overflows the enum type `${styp}`, values of which have to be in [${imin}, ${imax}]',
pos)
overflows = true
}
} else {
val := expr.val.u64()
uval = val
if val >= umax {
overflows = true
if val == umax {
is_bin := expr.val.starts_with('0b')
is_oct := expr.val.starts_with('0o')
is_hex := expr.val.starts_with('0x')
if is_hex {
overflows = val.hex() != umax.hex()
} else if !is_bin && !is_oct && !is_hex {
overflows = expr.val.str() != umax.str()
}
}
if overflows {
c.error('enum value `${expr.val}` overflows the enum type `${styp}`, values of which have to be in [${umin}, ${umax}]',
pos)
}
}
}
if !overflows && !c.pref.translated && !c.file.is_translated && !is_multi_allowed {
if (is_signed && ival in iseen) || (!is_signed && uval in useen) {
c.error('enum value `${expr.val}` already exists', pos)
}
}
if is_signed {
iseen << ival
} else {
useen << uval
}
}
@[inline]
fn (mut c Checker) check_loop_label(label string, pos token.Pos) {
if label.len == 0 {
// ignore
return
}
if c.loop_label.len != 0 {
c.error('nesting of labelled `for` loops is not supported', pos)
return
}
c.loop_label = label
}
fn (mut c Checker) stmt(mut node ast.Stmt) {
$if trace_checker ? {
ntype := typeof(*node).replace('v.ast.', '')
eprintln('checking: ${c.file.path:-30} | pos: ${node.pos.line_str():-39} | node: ${ntype} | ${node}')
}
c.expected_type = ast.void_type
match mut node {
ast.EmptyStmt {
if c.pref.is_verbose {
eprintln('Checker.stmt() EmptyStmt')
print_backtrace()
}
}
ast.NodeError {}
ast.DebuggerStmt {}
ast.AsmStmt {
c.asm_stmt(mut node)
}
ast.AssertStmt {
c.assert_stmt(mut node)
}
ast.AssignStmt {
c.assign_stmt(mut node)
}
ast.Block {
c.block(mut node)
}
ast.BranchStmt {
c.branch_stmt(node)
}
ast.ComptimeFor {
c.comptime_for(mut node)
}
ast.ConstDecl {
c.inside_const = true
c.const_decl(mut node)
c.inside_const = false
}
ast.DeferStmt {
if node.idx_in_fn < 0 && c.table.cur_fn != unsafe { nil } {
node.idx_in_fn = c.table.cur_fn.defer_stmts.len
c.table.cur_fn.defer_stmts << unsafe { &node }
}
if c.locked_names.len != 0 || c.rlocked_names.len != 0 {
c.error('defers are not allowed in lock statements', node.pos)
}
for i, ident in node.defer_vars {
mut id := ident
if mut id.info is ast.IdentVar {
if id.comptime && (id.tok_kind == .question
|| id.name in ast.valid_comptime_not_user_defined) {
node.defer_vars[i] = ast.Ident{
scope: unsafe { nil }
name: ''
}
continue
}
typ := c.ident(mut id)
if typ == ast.error_type_idx {
continue
}
id.info.typ = typ
node.defer_vars[i] = id
}
}
c.inside_defer = true
c.stmts(mut node.stmts)
c.inside_defer = false
}
ast.EnumDecl {
c.enum_decl(mut node)
}
ast.ExprStmt {
node.typ = c.expr(mut node.expr)
c.expected_type = ast.void_type
mut or_typ := ast.void_type
match mut node.expr {
ast.IndexExpr {
if node.expr.or_expr.kind != .absent {
node.is_expr = true
or_typ = node.typ
}
}
ast.PrefixExpr {
if node.expr.or_block.kind != .absent {
node.is_expr = true
or_typ = node.typ
}
}
else {}
}
if !c.pref.is_repl && (c.stmt_level == 1 || (c.stmt_level > 1 && !c.is_last_stmt)) {
if mut node.expr is ast.InfixExpr {
if node.expr.op == .left_shift {
left_sym := c.table.final_sym(node.expr.left_type)
if left_sym.kind != .array
&& c.table.final_sym(c.unwrap_generic(node.expr.left_type)).kind != .array {
c.error('unused expression', node.pos)
}
}
}
}
c.check_expr_option_or_result_call(node.expr, or_typ)
// TODO This should work, even if it's prolly useless .-.
// node.typ = c.check_expr_option_or_result_call(node.expr, ast.void_type)
}
ast.FnDecl {
c.fn_decl(mut node)
}
ast.ForCStmt {
c.for_c_stmt(mut node)
}
ast.ForInStmt {
c.for_in_stmt(mut node)
}
ast.ForStmt {
c.for_stmt(mut node)
}
ast.GlobalDecl {
c.global_decl(mut node)
}
ast.GotoLabel {
c.goto_label(node)
}
ast.GotoStmt {
c.goto_stmt(node)
}
ast.HashStmt {
c.hash_stmt(mut node)
}
ast.Import {
c.import_stmt(node)
}
ast.InterfaceDecl {
c.interface_decl(mut node)
}
ast.Module {
c.mod = node.name
c.is_just_builtin_mod = node.name == 'builtin'
c.is_builtin_mod = c.is_just_builtin_mod || node.name in ['os', 'strconv']
c.check_valid_snake_case(node.name, 'module name', node.pos)
}
ast.Return {
// c.returns = true
c.return_stmt(mut node)
c.scope_returns = true
}
ast.SemicolonStmt {}
ast.SqlStmt {
c.sql_stmt(mut node)
}
ast.StructDecl {
c.struct_decl(mut node)
}
ast.TypeDecl {
c.type_decl(node)
}
}
}
fn (mut c Checker) assert_stmt(mut node ast.AssertStmt) {
cur_exp_typ := c.expected_type
c.expected_type = ast.bool_type
assert_type := c.check_expr_option_or_result_call(node.expr, c.expr(mut node.expr))
if assert_type != ast.bool_type_idx {
atype_name := c.table.sym(assert_type).name
c.error('assert can be used only with `bool` expressions, but found `${atype_name}` instead',
node.pos)
}
if node.extra !is ast.EmptyExpr {
extra_type := c.expr(mut node.extra)
if extra_type != ast.string_type {
extra_type_name := c.table.sym(extra_type).name
c.error('assert allows only a single string as its second argument, but found `${extra_type_name}` instead',
node.extra_pos)
}
}
c.fail_if_unreadable(node.expr, ast.bool_type_idx, 'assertion')
c.expected_type = cur_exp_typ
}
fn (mut c Checker) block(mut node ast.Block) {
if node.is_unsafe {
prev_unsafe := c.inside_unsafe
c.inside_unsafe = true
c.stmts(mut node.stmts)
c.inside_unsafe = prev_unsafe
} else {
c.stmts(mut node.stmts)
}
}
fn (mut c Checker) branch_stmt(node ast.BranchStmt) {
if c.inside_defer {
c.error('`${node.kind.str()}` is not allowed in defer statements', node.pos)
}
if c.in_for_count == 0 {
if c.comptime.inside_comptime_for {
c.error('${node.kind.str()} is not allowed within a compile-time loop', node.pos)
} else {
c.error('${node.kind.str()} statement not within a loop', node.pos)
}
}
if node.label.len > 0 {
if node.label != c.loop_label {
c.error('invalid label name `${node.label}`', node.pos)
}
}
}
fn (mut c Checker) global_decl(mut node ast.GlobalDecl) {
for mut field in node.fields {
c.check_valid_snake_case(field.name, 'global name', field.pos)
if field.name in c.global_names {
c.error('duplicate global `${field.name}`', field.pos)
}
if '${c.mod}.${field.name}' in c.const_names {
c.error('duplicate global and const `${field.name}`', field.pos)
}
sym := c.table.sym(field.typ)
if sym.kind == .placeholder {
c.error('unknown type `${sym.name}`', field.typ_pos)
}
if field.has_expr {
if field.expr is ast.AnonFn && field.name == 'main' {
c.error('the `main` function is the program entry point, cannot redefine it',
field.pos)
}
field.typ = c.expr(mut field.expr)
mut v := c.file.global_scope.find_global(field.name) or {
panic('internal compiler error - could not find global in scope')
}
v.typ = ast.mktyp(field.typ)
}
c.global_names << field.name
}
}
fn (mut c Checker) asm_stmt(mut stmt ast.AsmStmt) {
if stmt.is_goto {
c.warn('inline assembly goto is not supported, it will most likely not work',
stmt.pos)
}
if c.pref.backend.is_js() {
c.error('inline assembly is not supported in the js backend', stmt.pos)
}
if c.pref.backend == .c && c.pref.ccompiler_type == .msvc {
c.error('msvc compiler does not support inline assembly', stmt.pos)
}
mut aliases := c.asm_ios(mut stmt.output, mut stmt.scope, true)
aliases2 := c.asm_ios(mut stmt.input, mut stmt.scope, false)
aliases << aliases2
for mut template in stmt.templates {
if template.is_directive {
/*
align n[,value]
.skip n[,value]
.space n[,value]
.byte value1[,...]
.word value1[,...]
.short value1[,...]
.int value1[,...]
.long value1[,...]
.quad immediate_value1[,...]
.globl symbol
.global symbol
.section section
.text
.data
.bss
.fill repeat[,size[,value]]
.org n
.previous
.string string[,...]
.asciz string[,...]
.ascii string[,...]
*/
if template.name !in ['skip', 'space', 'byte', 'word', 'short', 'int', 'long', 'quad',
'globl', 'global', 'section', 'text', 'data', 'bss', 'fill', 'org', 'previous',
'string', 'asciz', 'ascii'] { // all tcc-supported assembler directives
c.error('unknown assembler directive: `${template.name}`', template.pos)
}
}
for mut arg in template.args {
c.asm_arg(arg, stmt, aliases)
}
}
for mut clob in stmt.clobbered {
c.asm_arg(clob.reg, stmt, aliases)
}
}
fn (mut c Checker) asm_arg(arg ast.AsmArg, stmt ast.AsmStmt, aliases []string) {
match arg {
ast.AsmAlias {}
ast.AsmAddressing {
if arg.scale !in [-1, 1, 2, 4, 8] {
c.error('scale must be one of 1, 2, 4, or 8', arg.pos)
}
c.asm_arg(arg.displacement, stmt, aliases)
c.asm_arg(arg.base, stmt, aliases)
c.asm_arg(arg.index, stmt, aliases)
}
ast.BoolLiteral {} // all of these are guaranteed to be correct.
ast.FloatLiteral {}
ast.CharLiteral {}
ast.IntegerLiteral {}
ast.AsmRegister {} // if the register is not found, the parser will register it as an alias
ast.AsmDisp {}
string {}
}
}
fn (mut c Checker) asm_ios(mut ios []ast.AsmIO, mut scope ast.Scope, output bool) []string {
mut aliases := []string{}
for mut io in ios {
typ := c.expr(mut io.expr)
if output {
c.fail_if_immutable(mut io.expr)
}
if io.alias != '' {
aliases << io.alias
if io.alias in scope.objects {
scope.objects[io.alias] = ast.Var{
name: io.alias
expr: io.expr
is_arg: true
typ: typ
orig_type: typ
pos: io.pos
}
}
}
}
return aliases
}
fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
if c.skip_flags {
return
}
if c.ct_cond_stack.len > 0 {
node.ct_conds = c.ct_cond_stack.clone()
}
if c.pref.backend.is_js() || c.pref.backend == .golang {
// consider the best way to handle the .go.vv files
if !c.file.path.ends_with('.js.v') && !c.file.path.ends_with('.go.v')
&& !c.file.path.ends_with('.go.vv') {
c.error('hash statements are only allowed in backend specific files such "x.js.v" and "x.go.v"',
node.pos)
}
if c.pref.backend != .golang && c.mod == 'main' {
c.error('hash statements are not allowed in the main module. Place them in a separate module.',
node.pos)
}
return
}
match node.kind {
'include', 'insert', 'preinclude' {
original_flag := node.main
mut flag := node.main
if flag.contains('@VROOT') {
// c.note(checker.vroot_is_deprecated_message, node.pos)
vroot := util.resolve_vmodroot(flag.replace('@VROOT', '@VMODROOT'), c.file.path) or {
c.error(err.msg(), node.pos)
return
}
node.val = '${node.kind} ${vroot}'
node.main = vroot
flag = vroot
}
if flag.contains('@VEXEROOT') {
vroot := flag.replace('@VEXEROOT', os.dir(pref.vexe_path()))
node.val = '${node.kind} ${vroot}'
node.main = vroot
flag = vroot
}
if flag.contains('@VMODROOT') {
vroot := util.resolve_vmodroot(flag, c.file.path) or {
c.error(err.msg(), node.pos)
return
}
node.val = '${node.kind} ${vroot}'
node.main = vroot
flag = vroot
}
if flag.contains('\$env(') {
env := util.resolve_env_value(flag, true) or {
c.error(err.msg(), node.pos)
return
}
node.main = env
}
flag_no_comment := flag.all_before('//').trim_space()
if node.kind == 'include' || node.kind == 'preinclude' {
if !((flag_no_comment.starts_with('"') && flag_no_comment.ends_with('"'))
|| (flag_no_comment.starts_with('<') && flag_no_comment.ends_with('>'))) {
c.error('including C files should use either `"header_file.h"` or `` quoting',
node.pos)
}
}
if node.kind == 'insert' {
if !(flag_no_comment.starts_with('"') && flag_no_comment.ends_with('"')) {
c.error('inserting .c or .h files, should use `"header_file.h"` quoting',
node.pos)
}
node.main = node.main.trim('"')
if fcontent := os.read_file(node.main) {
node.val = fcontent
} else {
mut missing_message := 'The file ${original_flag}, needed for insertion by module `${node.mod}`,'
if os.is_file(node.main) {
missing_message += ' is not readable.'
} else {
missing_message += ' does not exist.'
}
if node.msg != '' {
missing_message += ' ${node.msg}.'
}
c.error(missing_message, node.pos)
}
}
}
'pkgconfig' {
args := if node.main.contains('--') {
node.main.split(' ')
} else {
'--cflags --libs ${node.main}'.split(' ')
}
mut m := pkgconfig.main(args) or {
c.error(err.msg(), node.pos)
return
}
cflags := m.run() or {
c.error(err.msg(), node.pos)
return
}
c.table.parse_cflag(cflags, c.mod, c.pref.compile_defines_all) or {
c.error(err.msg(), node.pos)
return
}
}
'flag' {
// #flag linux -lm
mut flag := node.main
if flag == 'flag' { // Checks for empty flag
c.error('no argument(s) provided for #flag', node.pos)
}
if flag.contains('@VROOT') {
// c.note(checker.vroot_is_deprecated_message, node.pos)
flag = util.resolve_vmodroot(flag.replace('@VROOT', '@VMODROOT'), c.file.path) or {
c.error(err.msg(), node.pos)
return
}
}
if flag.contains('@VEXEROOT') {
// expand `@VEXEROOT` to its absolute path
flag = flag.replace('@VEXEROOT', os.dir(pref.vexe_path()))
}
if flag.contains('@VMODROOT') {
flag = util.resolve_vmodroot(flag, c.file.path) or {
c.error(err.msg(), node.pos)
return
}
}
if flag.contains('\$env(') {
flag = util.resolve_env_value(flag, true) or {
c.error(err.msg(), node.pos)
return
}
}
for deprecated in ['@VMOD', '@VMODULE', '@VPATH', '@VLIB_PATH'] {
if flag.contains(deprecated) {
if !flag.contains('@VMODROOT') {
c.error('${deprecated} had been deprecated, use @VMODROOT instead.',
node.pos)
}
}
}
c.table.parse_cflag(flag, c.mod, c.pref.compile_defines_all) or {
c.error(err.msg(), node.pos)
}
}
else {
if node.kind == 'define' {
if !c.is_builtin_mod && !c.file.path.ends_with('.c.v')
&& !c.file.path.contains('vlib') {
if !c.pref.is_bare {
c.error("#define can only be used in vlib (V's standard library) and *.c.v files",
node.pos)
}
}
} else {
c.error('expected `#define`, `#flag`, `#include`, `#insert` or `#pkgconfig` not ${node.val}',
node.pos)
}
}
}
}
fn (mut c Checker) import_stmt(node ast.Import) {
c.check_valid_snake_case(node.alias, 'module alias', node.pos)
for sym in node.syms {
name := '${node.mod}.${sym.name}'
if sym.name[0].is_capital() {
if type_sym := c.table.find_sym(name) {
if type_sym.kind != .placeholder {
if !type_sym.is_pub {
c.error('module `${node.mod}` type `${sym.name}` is private',
sym.pos)
}
continue
}
}
c.error('module `${node.mod}` has no type `${sym.name}`', sym.pos)
continue
}
if sym.name in ast.builtin_type_names {
c.error('cannot import or override builtin type', sym.pos)
}
if func := c.table.find_fn(name) {
if !func.is_pub {
c.error('module `${node.mod}` function `${sym.name}()` is private', sym.pos)
}
continue
}
if _ := c.file.global_scope.find_const(name) {
continue
}
c.error('module `${node.mod}` has no constant or function `${sym.name}`', sym.pos)
}
if c.table.module_deprecated[node.mod] {
c.deprecate('module', node.mod, c.table.module_attrs[node.mod], node.pos)
}
}
// stmts should be used for processing normal statement lists (fn bodies, for loop bodies etc).
fn (mut c Checker) stmts(mut stmts []ast.Stmt) {
old_stmt_level := c.stmt_level
c.stmt_level = 0
c.stmts_ending_with_expression(mut stmts)
c.stmt_level = old_stmt_level
}
// stmts_ending_with_expression, should be used for processing list of statements, that can end with an expression.
// Examples for such lists are the bodies of `or` blocks, `if` expressions and `match` expressions:
// `x := opt() or { stmt1 stmt2 ExprStmt }`,
// `x := if cond { stmt1 stmt2 ExprStmt } else { stmt2 stmt3 ExprStmt }`,
// `x := match expr { Type1 { stmt1 stmt2 ExprStmt } else { stmt2 stmt3 ExprStmt }`.
fn (mut c Checker) stmts_ending_with_expression(mut stmts []ast.Stmt) {
if stmts.len == 0 {
c.scope_returns = false
return
}
if c.stmt_level > checker.stmt_level_cutoff_limit {
c.scope_returns = false
c.error('checker: too many stmt levels: ${c.stmt_level} ', stmts[0].pos)
return
}
mut unreachable := token.Pos{
line_nr: -1
}
c.stmt_level++
for i, mut stmt in stmts {
c.is_last_stmt = i == stmts.len - 1
if c.scope_returns {
if unreachable.line_nr == -1 {
unreachable = stmt.pos
}
}
c.stmt(mut stmt)
if stmt is ast.GotoLabel {
unreachable = token.Pos{
line_nr: -1
}
c.scope_returns = false
}
if c.should_abort {
return
}
}
c.stmt_level--
if unreachable.line_nr >= 0 {
c.error('unreachable code', unreachable)
}
c.find_unreachable_statements_after_noreturn_calls(stmts)
c.scope_returns = false
}
fn (mut c Checker) unwrap_generic(typ ast.Type) ast.Type {
if typ.has_flag(.generic) {
if c.inside_generic_struct_init {
generic_names := c.cur_struct_generic_types.map(c.table.sym(it).name)
if t_typ := c.table.resolve_generic_to_concrete(typ, generic_names, c.cur_struct_concrete_types) {
return t_typ
}
}
if c.table.cur_fn != unsafe { nil } {
if t_typ := c.table.resolve_generic_to_concrete(typ, c.table.cur_fn.generic_names,
c.table.cur_concrete_types)
{
return t_typ
}
}
}
return typ
}
pub fn (mut c Checker) expr(mut node ast.Expr) ast.Type {
c.expr_level++
defer {
c.expr_level--
}
if c.expr_level > checker.expr_level_cutoff_limit {
c.error('checker: too many expr levels: ${c.expr_level} ', node.pos())
return ast.void_type
}
match mut node {
ast.NodeError {}
ast.ComptimeType {
c.error('incorrect use of compile-time type', node.pos)
}
ast.EmptyExpr {
if c.pref.is_verbose {
print_backtrace()
}
c.error('checker.expr(): unhandled EmptyExpr', token.Pos{})
return ast.void_type
}
ast.CTempVar {
return node.typ
}
ast.AnonFn {
return c.anon_fn(mut node)
}
ast.ArrayDecompose {
typ := c.expr(mut node.expr)
type_sym := c.table.sym(typ)
if type_sym.kind == .array_fixed {
c.error('direct decomposition of fixed array is not allowed, convert the fixed array to normal array via ${node.expr}[..]',
node.expr.pos())
return ast.void_type
} else if type_sym.kind != .array {
c.error('decomposition can only be used on arrays', node.expr.pos())
return ast.void_type
}
array_info := type_sym.info as ast.Array
elem_type := array_info.elem_type.set_flag(.variadic)
node.expr_type = typ
node.arg_type = elem_type
return elem_type
}
ast.ArrayInit {
return c.array_init(mut node)
}
ast.AsCast {
node.expr_type = c.expr(mut node.expr)
expr_type_sym := c.table.sym(node.expr_type)
type_sym := c.table.sym(c.unwrap_generic(node.typ))
if expr_type_sym.kind == .sum_type {
c.ensure_type_exists(node.typ, node.pos)
if !c.table.sumtype_has_variant(c.unwrap_generic(node.expr_type), c.unwrap_generic(node.typ),
true) {
addr := '&'.repeat(node.typ.nr_muls())
c.error('cannot cast `${expr_type_sym.name}` to `${addr}${type_sym.name}`',
node.pos)
}
} else if expr_type_sym.kind == .interface_ {
c.ensure_type_exists(node.typ, node.pos)
if type_sym.kind != .interface_ {
c.type_implements(node.typ, node.expr_type, node.pos)
}
} else if node.expr_type.clear_flag(.option) != node.typ.clear_flag(.option) {
mut s := 'cannot cast non-sum type `${expr_type_sym.name}` using `as`'
if type_sym.kind == .sum_type {
s += ' - use e.g. `${type_sym.name}(some_expr)` instead.'
}
c.error(s, node.pos)
}
return node.typ
}
ast.Assoc {
v := node.scope.find_var(node.var_name) or { panic(err) }
for i, _ in node.fields {
mut expr := node.exprs[i]
c.expr(mut expr)
}
node.typ = v.typ
return v.typ
}
ast.BoolLiteral {
return ast.bool_type
}
ast.CastExpr {
return c.cast_expr(mut node)
}
ast.CallExpr {
mut ret_type := c.call_expr(mut node)
if ret_type != 0 && c.table.sym(ret_type).kind == .alias {
unaliased_type := c.table.unaliased_type(ret_type)
if unaliased_type.has_option_or_result() {
ret_type = unaliased_type
}
}
if !ret_type.has_option_or_result() {
c.expr_or_block_err(node.or_block.kind, node.name, node.or_block.pos,
false)
}
if node.or_block.kind != .absent {
if ret_type.has_flag(.option) {
ret_type = ret_type.clear_flag(.option)
}
if ret_type.has_flag(.result) {
ret_type = ret_type.clear_flag(.result)
}
}
return ret_type
}
ast.ChanInit {
return c.chan_init(mut node)
}
ast.CharLiteral {
return ast.rune_type
}
ast.Comment {
return ast.void_type
}
ast.AtExpr {
return c.at_expr(mut node)
}
ast.ComptimeCall {
return c.comptime_call(mut node)
}
ast.ComptimeSelector {
return c.comptime_selector(mut node)
}
ast.ConcatExpr {
return c.concat_expr(mut node)
}
ast.DumpExpr {
c.expected_type = ast.string_type
node.expr_type = c.expr(mut node.expr)
if c.comptime.inside_comptime_for && node.expr is ast.Ident {
if c.comptime.is_comptime_var(node.expr) {
node.expr_type = c.comptime.get_comptime_var_type(node.expr as ast.Ident)
} else if (node.expr as ast.Ident).name in c.comptime.type_map {
node.expr_type = c.comptime.type_map[(node.expr as ast.Ident).name]
}
}
c.check_expr_option_or_result_call(node.expr, node.expr_type)
etidx := node.expr_type.idx()
if etidx == ast.void_type_idx {
c.error('dump expression can not be void', node.expr.pos())
return ast.void_type
} else if etidx == ast.char_type_idx && node.expr_type.nr_muls() == 0 {
c.error('`char` values cannot be dumped directly, use dump(u8(x)) or dump(int(x)) instead',
node.expr.pos())
return ast.void_type
}
unwrapped_expr_type := c.unwrap_generic(node.expr_type)
tsym := c.table.sym(unwrapped_expr_type)
if tsym.kind == .array_fixed {
info := tsym.info as ast.ArrayFixed
if !info.is_fn_ret {
// for dumping fixed array we must register the fixed array struct to return from function
c.table.find_or_register_array_fixed(info.elem_type, info.size, info.size_expr,
true)
}
}
type_cname := if node.expr_type.has_flag(.option) {
'_option_${tsym.cname}'
} else {
tsym.cname
}
c.table.dumps[int(unwrapped_expr_type.clear_flag(.result).clear_flag(.atomic_f))] = type_cname
node.cname = type_cname
return node.expr_type
}
ast.EnumVal {
return c.enum_val(mut node)
}
ast.FloatLiteral {
return ast.float_literal_type
}
ast.GoExpr {
return c.go_expr(mut node)
}
ast.SpawnExpr {
return c.spawn_expr(mut node)
}
ast.Ident {
return c.ident(mut node)
}
ast.IfExpr {
return c.if_expr(mut node)
}
ast.IfGuardExpr {
old_inside_if_guard := c.inside_if_guard
c.inside_if_guard = true
node.expr_type = c.expr(mut node.expr)
c.inside_if_guard = old_inside_if_guard
if !node.expr_type.has_flag(.option) && !node.expr_type.has_flag(.result) {
mut no_opt_or_res := true
match mut node.expr {
ast.IndexExpr {
no_opt_or_res = false
node.expr_type = node.expr_type.set_flag(.option)
node.expr.is_option = true
}
ast.PrefixExpr {
if node.expr.op == .arrow {
no_opt_or_res = false
node.expr_type = node.expr_type.set_flag(.option)
node.expr.is_option = true
}
}
else {}
}
if no_opt_or_res {
c.error('expression should either return an Option or a Result', node.expr.pos())
}
}
return ast.bool_type
}
ast.IndexExpr {
return c.index_expr(mut node)
}
ast.InfixExpr {
return c.infix_expr(mut node)
}
ast.IntegerLiteral {
return c.int_lit(mut node)
}
ast.LambdaExpr {
c.inside_lambda = true
defer {
c.inside_lambda = false
}
return c.lambda_expr(mut node, c.expected_type)
}
ast.LockExpr {
return c.lock_expr(mut node)
}
ast.MapInit {
return c.map_init(mut node)
}
ast.MatchExpr {
return c.match_expr(mut node)
}
ast.Nil {
if !c.inside_unsafe {
c.error('`nil` is only allowed in `unsafe` code', node.pos)
}
return ast.nil_type
}
ast.PostfixExpr {
return c.postfix_expr(mut node)
}
ast.PrefixExpr {
return c.prefix_expr(mut node)
}
ast.None {
return ast.none_type
}
ast.OrExpr {
// never happens
return ast.void_type
}
// ast.OrExpr2 {
// return node.typ
// }
ast.ParExpr {
if node.expr is ast.ParExpr {
c.warn('redundant parentheses are used', node.pos)
}
return c.expr(mut node.expr)
}
ast.RangeExpr {
// branch range expression of `match x { a...b {} }`, or: `a#[x..y]`:
ltyp := c.expr(mut node.low)
htyp := c.expr(mut node.high)
if !c.check_types(ltyp, htyp) {
lstype := c.table.type_to_str(ltyp)
hstype := c.table.type_to_str(htyp)
c.add_error_detail('')
c.add_error_detail(' low part type: ${lstype}')
c.add_error_detail('high part type: ${hstype}')
c.error('the low and high parts of a range expression, should have matching types',
node.pos)
}
node.typ = c.promote(ltyp, htyp)
return ltyp
}
ast.SelectExpr {
return c.select_expr(mut node)
}
ast.SelectorExpr {
mut ret_type := c.selector_expr(mut node)
if c.table.sym(ret_type).kind == .chan {
return ret_type
}
if !ret_type.has_flag(.option) && !ret_type.has_flag(.result) {
c.expr_or_block_err(node.or_block.kind, node.field_name, node.or_block.pos,
true)
}
if node.or_block.kind != .absent {
if ret_type.has_flag(.option) {
ret_type = ret_type.clear_flag(.option)
}
if ret_type.has_flag(.result) {
ret_type = ret_type.clear_flag(.result)
}
}
return ret_type
}
ast.SizeOf {
if !node.is_type {
node.typ = c.expr(mut node.expr)
}
sym := c.table.final_sym(node.typ)
if sym.kind == .placeholder && sym.language != .c {
// Allow `sizeof(C.MYSQL_TIME)` etc
c.error('unknown type `${sym.name}`', node.pos)
}
// c.deprecate_old_isreftype_and_sizeof_of_a_guessed_type(node.guessed_type,
// node.typ, node.pos, 'sizeof')
return ast.u32_type
}
ast.IsRefType {
if !node.is_type {
node.typ = c.expr(mut node.expr)
}
// c.deprecate_old_isreftype_and_sizeof_of_a_guessed_type(node.guessed_type,
// node.typ, node.pos, 'isreftype')
return ast.bool_type
}
ast.OffsetOf {
return c.offset_of(node)
}
ast.SqlExpr {
return c.sql_expr(mut node)
}
ast.StringLiteral {
if node.language == .c {
// string literal starts with "c": `C.printf(c'hello')`
return ast.u8_type.set_nr_muls(1)
}
return c.string_lit(mut node)
}
ast.StringInterLiteral {
return c.string_inter_lit(mut node)
}
ast.StructInit {
if node.unresolved {
mut expr_ := c.table.resolve_init(node, c.unwrap_generic(node.typ))
return c.expr(mut expr_)
}
mut inited_fields := []string{}
return c.struct_init(mut node, false, mut inited_fields)
}
ast.TypeNode {
if !c.inside_x_is_type && node.typ.has_flag(.generic) && unsafe { c.table.cur_fn != 0 }
&& c.table.cur_fn.generic_names.len == 0 {
c.error('unexpected generic variable in non-generic function `${c.table.cur_fn.name}`',
node.pos)
}
return node.typ
}
ast.TypeOf {
if !node.is_type {
node.typ = c.expr(mut node.expr)
}
return ast.string_type
}
ast.UnsafeExpr {
return c.unsafe_expr(mut node)
}
ast.Likely {
ltype := c.expr(mut node.expr)
if !c.check_types(ltype, ast.bool_type) {
ltype_sym := c.table.sym(ltype)
lname := if node.is_likely { '_likely_' } else { '_unlikely_' }
c.error('`${lname}()` expects a boolean expression, instead it got `${ltype_sym.name}`',
node.pos)
}
return ast.bool_type
}
}
return ast.void_type
}
// pub fn (mut c Checker) asm_reg(mut node ast.AsmRegister) ast.Type {
// name := node.name
// for bit_size, array in ast.x86_no_number_register_list {
// if name in array {
// return c.table.bitsize_to_type(bit_size)
// }
// }
// for bit_size, array in ast.x86_with_number_register_list {
// if name in array {
// return c.table.bitsize_to_type(bit_size)
// }
// }
// c.error('invalid register name: `$name`', node.pos)
// return ast.void_type
// }
fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type {
// Given: `Outside( Inside(xyz) )`,
// node.expr_type: `Inside`
// node.typ: `Outside`
node.expr_type = c.expr(mut node.expr) // type to be casted
if mut node.expr is ast.ComptimeSelector {
node.expr_type = c.comptime.get_comptime_selector_type(node.expr, node.expr_type)
}
mut from_type := c.unwrap_generic(node.expr_type)
from_sym := c.table.sym(from_type)
final_from_sym := c.table.final_sym(from_type)
mut to_type := c.unwrap_generic(node.typ)
mut to_sym := c.table.sym(to_type) // type to be used as cast
mut final_to_sym := c.table.final_sym(to_type)
final_to_type := if mut to_sym.info is ast.Alias {
to_sym.info.parent_type
} else {
to_type
}
final_to_is_ptr := to_type.is_ptr() || final_to_type.is_ptr()
if to_type.has_flag(.result) {
c.error('casting to Result type is forbidden', node.pos)
}
if (to_sym.is_number() && from_sym.name == 'JS.Number')
|| (to_sym.is_number() && from_sym.name == 'JS.BigInt')
|| (to_sym.is_string() && from_sym.name == 'JS.String')
|| (to_type.is_bool() && from_sym.name == 'JS.Boolean')
|| (from_type.is_bool() && to_sym.name == 'JS.Boolean')
|| (from_sym.is_number() && to_sym.name == 'JS.Number')
|| (from_sym.is_number() && to_sym.name == 'JS.BigInt')
|| (from_sym.is_string() && to_sym.name == 'JS.String') {
return to_type
}
if to_sym.language != .c {
c.ensure_type_exists(to_type, node.pos)
if to_sym.info is ast.Alias && to_sym.info.parent_type.has_flag(.option)
&& !to_type.has_flag(.option) {
c.error('alias to Option type requires to be used as Option type (?${to_sym.name}(...))',
node.pos)
}
}
if from_sym.kind == .u8 && from_type.is_ptr() && to_sym.kind == .string && !to_type.is_ptr() {
c.error('to convert a C string buffer pointer to a V string, use x.vstring() instead of string(x)',
node.pos)
}
if from_type == ast.void_type {
c.error('expression does not return a value so it cannot be cast', node.expr.pos())
}
if to_type.has_flag(.option) && from_type == ast.none_type {
// allow conversion from none to every option type
} else if to_sym.kind == .sum_type {
to_sym_info := to_sym.info as ast.SumType
if to_sym_info.generic_types.len > 0 && to_sym_info.concrete_types.len == 0 {
c.error('generic sumtype `${to_sym.name}` must specify type parameter, e.g. ${to_sym.name}[int]',
node.pos)
}
if from_type in [ast.int_literal_type, ast.float_literal_type] {
xx := if from_type == ast.int_literal_type { ast.int_type } else { ast.f64_type }
node.expr_type = c.promote_num(node.expr_type, xx)
from_type = node.expr_type
}
if !c.table.sumtype_has_variant(to_type, from_type, false) && !to_type.has_flag(.option)
&& !to_type.has_flag(.result) {
ft := c.table.type_to_str(from_type)
tt := c.table.type_to_str(to_type)
c.error('cannot cast `${ft}` to `${tt}`', node.pos)
}
} else if mut to_sym.info is ast.Alias && !(final_to_sym.kind == .struct_ && final_to_is_ptr) {
if (!c.check_types(from_type, to_sym.info.parent_type) && !(final_to_sym.is_int()
&& final_from_sym.kind in [.enum_, .bool, .i8, .u8, .char]))
|| (final_to_sym.kind == .struct_
&& from_type.idx() in [ast.voidptr_type_idx, ast.nil_type_idx]) {
ft := c.table.type_to_str(from_type)
tt := c.table.type_to_str(to_type)
c.error('cannot cast `${ft}` to `${tt}` (alias to `${final_to_sym.name}`)',
node.pos)
}
} else if to_sym.kind == .struct_ && mut to_sym.info is ast.Struct
&& (!to_sym.info.is_typedef || from_type.idx() in [ast.voidptr_type_idx, ast.nil_type_idx])
&& !final_to_is_ptr {
// For now we ignore C typedef because of `C.Window(C.None)` in vlib/clipboard (except for `from_type` is voidptr/nil)
if from_sym.kind == .struct_ && from_sym.info is ast.Struct && !from_type.is_ptr() {
if !to_type.has_flag(.option) {
c.warn('casting to struct is deprecated, use e.g. `Struct{...expr}` instead',
node.pos)
}
if !c.check_struct_signature(from_sym.info, to_sym.info) {
c.error('cannot convert struct `${from_sym.name}` to struct `${to_sym.name}`',
node.pos)
}
} else {
ft := c.table.type_to_str(from_type)
c.error('cannot cast `${ft}` to struct', node.pos)
}
} else if to_sym.kind == .struct_ && final_to_is_ptr {
if from_sym.info is ast.Alias {
from_type = from_sym.info.parent_type.derive_add_muls(from_type)
}
if mut node.expr is ast.IntegerLiteral {
if node.expr.val.int() == 0 && !c.pref.translated && !c.file.is_translated {
c.error('cannot null cast a struct pointer, use &${to_sym.name}(unsafe { nil })',
node.pos)
} else if !c.inside_unsafe && !c.pref.translated && !c.file.is_translated {
c.error('cannot cast int to a struct pointer outside `unsafe`', node.pos)
}
} else if mut node.expr is ast.Ident {
match mut node.expr.obj {
ast.GlobalField, ast.ConstField, ast.Var {
if mut node.expr.obj.expr is ast.IntegerLiteral {
if node.expr.obj.expr.val.int() == 0 && !c.pref.translated
&& !c.file.is_translated {
c.error('cannot null cast a struct pointer, use &${to_sym.name}(unsafe { nil })',
node.pos)
} else if !c.inside_unsafe && !c.pref.translated && !c.file.is_translated {
c.error('cannot cast int to a struct pointer outside `unsafe`',
node.pos)
}
}
}
else {}
}
}
if from_type == ast.voidptr_type_idx && !c.inside_unsafe && !c.pref.translated
&& !c.file.is_translated {
c.error('cannot cast voidptr to a struct outside `unsafe`', node.pos)
}
if !from_type.is_int() && final_from_sym.kind != .enum_
&& !from_type.is_any_kind_of_pointer() {
ft := c.table.type_to_str(from_type)
tt := c.table.type_to_str(to_type)
c.error('cannot cast `${ft}` to `${tt}`', node.pos)
}
} else if !from_type.has_option_or_result() && mut to_sym.info is ast.Interface {
if c.type_implements(from_type, to_type, node.pos) {
if !from_type.is_any_kind_of_pointer() && from_sym.kind != .interface_
&& !c.inside_unsafe {
c.mark_as_referenced(mut &node.expr, true)
}
if to_sym.info.is_generic {
inferred_type := c.resolve_generic_interface(from_type, to_type, node.pos)
if inferred_type != 0 {
to_type = inferred_type
to_sym = c.table.sym(to_type)
final_to_sym = c.table.final_sym(to_type)
}
}
} else {
ft := c.table.type_to_str(from_type)
tt := c.table.type_to_str(to_type)
c.error('`${ft}` does not implement interface `${tt}`, cannot cast `${ft}` to interface `${tt}`',
node.pos)
}
} else if to_type == ast.bool_type && from_type != ast.bool_type && !c.inside_unsafe
&& !c.pref.translated && !c.file.is_translated {
c.error('cannot cast to bool - use e.g. `some_int != 0` instead', node.pos)
} else if from_type == ast.none_type && !to_type.has_flag(.option) && !to_type.has_flag(.result) {
type_name := c.table.type_to_str(to_type)
c.error('cannot cast `none` to `${type_name}`', node.pos)
} else if !from_type.has_option_or_result() && from_sym.kind == .struct_ && !from_type.is_ptr() {
if (final_to_is_ptr || to_sym.kind !in [.sum_type, .interface_]) && !c.is_builtin_mod {
from_type_name := c.table.type_to_str(from_type)
type_name := c.table.type_to_str(to_type)
c.error('cannot cast struct `${from_type_name}` to `${type_name}`', node.pos)
}
} else if to_sym.kind == .u8 && !final_from_sym.is_number()
&& !from_type.is_any_kind_of_pointer() && final_from_sym.kind !in [.char, .enum_, .bool] {
ft := c.table.type_to_str(from_type)
tt := c.table.type_to_str(to_type)
c.error('cannot cast type `${ft}` to `${tt}`', node.pos)
} else if (from_type.has_flag(.option) && !to_type.has_flag(.option))
|| from_type.has_flag(.result) || from_type.has_flag(.variadic) {
// variadic case can happen when arrays are converted into variadic
msg := if from_type.has_flag(.option) {
'an Option'
} else if from_type.has_flag(.result) {
'a Result'
} else {
'a variadic'
}
c.error('cannot type cast ${msg}', node.pos)
} else if !c.inside_unsafe && to_type.is_ptr() && from_type.is_ptr() && to_type != from_type
&& to_type.deref() != ast.char_type && from_type.deref() != ast.char_type {
ft := c.table.type_to_str(from_type)
tt := c.table.type_to_str(to_type)
c.warn('casting `${ft}` to `${tt}` is only allowed in `unsafe` code', node.pos)
} else if from_sym.kind == .array_fixed && !from_type.is_ptr() {
if !c.pref.translated && !c.file.is_translated {
c.warn('cannot cast a fixed array (use e.g. `&arr[0]` instead)', node.pos)
}
} else if final_from_sym.kind == .string && final_to_sym.is_number()
&& final_to_sym.kind != .rune {
snexpr := node.expr.str()
tt := c.table.type_to_str(to_type)
c.error('cannot cast string to `${tt}`, use `${snexpr}.${final_to_sym.name}()` instead.',
node.pos)
} else if final_from_sym.kind == .string && final_to_is_ptr && to_sym.kind != .string {
snexpr := node.expr.str()
tt := c.table.type_to_str(to_type)
c.error('cannot cast string to `${tt}`, use `${snexpr}.str` instead.', node.pos)
} else if final_from_sym.kind == .string && to_sym.kind == .char {
snexpr := node.expr.str()
tt := c.table.type_to_str(to_type)
c.error('cannot cast string to `${tt}`, use `${snexpr}[index]` instead.', node.pos)
} else if final_from_sym.kind == .string && to_type.is_voidptr()
&& !node.expr_type.has_flag(.generic) && !from_type.is_ptr() {
c.error('cannot cast string to `voidptr`, use voidptr(s.str) instead', node.pos)
} else if final_from_sym.kind == .string && to_type.is_pointer() && !c.inside_unsafe {
tt := c.table.type_to_str(to_type)
c.error('cannot cast string to `${tt}` outside `unsafe`, use ${tt}(s.str) instead',
node.pos)
} else if final_from_sym.kind == .array && !from_type.is_ptr() && to_type != ast.string_type
&& !(to_type.has_flag(.option) && from_type.idx() == to_type.idx()) {
ft := c.table.type_to_str(from_type)
tt := c.table.type_to_str(to_type)
c.error('cannot cast array `${ft}` to `${tt}`', node.pos)
} else if from_type.has_flag(.option) && to_type.has_flag(.option)
&& to_sym.kind != final_from_sym.kind {
ft := c.table.type_to_str(from_type)
tt := c.table.type_to_str(to_type)
c.error('cannot cast incompatible option ${final_to_sym.name} `${ft}` to `${tt}`',
node.pos)
}
if to_sym.kind == .rune && from_sym.is_string() {
snexpr := node.expr.str()
ft := c.table.type_to_str(from_type)
c.error('cannot cast `${ft}` to rune, use `${snexpr}.runes()` instead.', node.pos)
}
if to_sym.kind == .enum_ && !(c.inside_unsafe || c.file.is_translated) && from_sym.is_int() {
c.error('casting numbers to enums, should be done inside `unsafe{}` blocks', node.pos)
}
if final_to_sym.kind == .function && final_from_sym.kind == .function && !(c.inside_unsafe
|| c.file.is_translated) && !c.check_matching_function_symbols(final_from_sym, final_to_sym) {
c.error('casting a function value from one function signature, to another function signature, should be done inside `unsafe{}` blocks',
node.pos)
}
if to_type.is_ptr() && to_sym.kind == .alias && from_sym.kind == .map {
c.error('cannot cast to alias pointer `${c.table.type_to_str(to_type)}` because `${c.table.type_to_str(from_type)}` is a value',
node.pos)
}
if to_type == ast.string_type {
if from_type in [ast.u8_type, ast.bool_type] {
snexpr := node.expr.str()
ft := c.table.type_to_str(from_type)
c.error('cannot cast type `${ft}` to string, use `${snexpr}.str()` instead.',
node.pos)
} else if from_type.is_any_kind_of_pointer() {
snexpr := node.expr.str()
ft := c.table.type_to_str(from_type)
c.error('cannot cast pointer type `${ft}` to string, use `&u8(${snexpr}).vstring()` or `cstring_to_vstring(${snexpr})` instead.',
node.pos)
} else if from_type.is_number() {
snexpr := node.expr.str()
c.error('cannot cast number to string, use `${snexpr}.str()` instead.', node.pos)
} else if from_sym.kind == .alias && final_from_sym.name != 'string' {
ft := c.table.type_to_str(from_type)
c.error('cannot cast type `${ft}` to string, use `x.str()` instead.', node.pos)
} else if final_from_sym.kind == .array {
snexpr := node.expr.str()
if final_from_sym.name == '[]u8' {
c.error('cannot cast []u8 to string, use `${snexpr}.bytestr()` or `${snexpr}.str()` instead.',
node.pos)
} else {
first_elem_idx := '[0]'
c.error('cannot cast array to string, use `${snexpr}${first_elem_idx}.str()` instead.',
node.pos)
}
} else if final_from_sym.kind == .enum_ {
snexpr := node.expr.str()
c.error('cannot cast enum to string, use ${snexpr}.str() instead.', node.pos)
} else if final_from_sym.kind == .map {
c.error('cannot cast map to string.', node.pos)
} else if final_from_sym.kind == .sum_type {
snexpr := node.expr.str()
ft := c.table.type_to_str(from_type)
c.error('cannot cast sumtype `${ft}` to string, use `${snexpr}.str()` instead.',
node.pos)
} else if final_from_sym.kind == .function {
fnexpr := node.expr.str()
c.error('cannot cast function `${fnexpr}` to string', node.pos)
} else if to_type != ast.string_type && from_type == ast.string_type
&& (!(to_sym.kind == .alias && final_to_sym.name == 'string')) {
mut error_msg := 'cannot cast a string to a type `${final_to_sym.name}`, that is not an alias of string'
if mut node.expr is ast.StringLiteral {
if node.expr.val.len == 1 {
error_msg += ", for denoting characters use `${node.expr.val}` instead of '${node.expr.val}'"
}
}
c.error(error_msg, node.pos)
}
} else if to_type.is_int() && mut node.expr is ast.IntegerLiteral {
tt := c.table.type_to_str(to_type)
tsize, _ := c.table.type_size(to_type.idx())
bit_size := tsize * 8
value_string := match node.expr.val[0] {
`-`, `+` {
node.expr.val[1..]
}
else {
node.expr.val
}
}
_, e := strconv.common_parse_uint2(value_string, 0, bit_size)
match e {
0 {}
-3 {
c.error('value `${node.expr.val}` overflows `${tt}`', node.pos)
}
else {
c.error('cannot cast value `${node.expr.val}` to `${tt}`', node.pos)
}
}
} else if to_type.is_float() && mut node.expr is ast.FloatLiteral {
tt := c.table.type_to_str(to_type)
strconv.atof64(node.expr.val) or {
c.error('cannot cast value `${node.expr.val}` to `${tt}`', node.pos)
}
}
if from_sym.language == .v && !from_type.is_ptr()
&& final_from_sym.kind in [.sum_type, .interface_]
&& final_to_sym.kind !in [.sum_type, .interface_] {
ft := c.table.type_to_str(from_type)
tt := c.table.type_to_str(to_type)
kind_name := if from_sym.kind == .sum_type { 'sum type' } else { 'interface' }
c.error('cannot cast `${ft}` ${kind_name} value to `${tt}`, use `${node.expr} as ${tt}` instead',
node.pos)
}
if node.has_arg {
c.expr(mut node.arg)
}
// checks on int literal to enum cast if the value represents a value on the enum
if to_sym.kind == .enum_ {
if mut node.expr is ast.IntegerLiteral {
enum_typ_name := c.table.get_type_name(to_type)
node_val := node.expr.val.i64()
if enum_decl := c.table.enum_decls[to_sym.name] {
mut in_range := false
if enum_decl.is_flag {
// if a flag enum has 4 variants, the maximum possible value would have all 4 flags set (0b1111)
max_val := (u64(1) << enum_decl.fields.len) - 1
in_range = node_val >= 0 && u64(node_val) <= max_val
} else {
mut enum_val := i64(0)
for enum_field in enum_decl.fields {
// check if the field of the enum value is an integer literal
if enum_field.expr is ast.IntegerLiteral {
enum_val = enum_field.expr.val.i64()
} else if comptime_value := c.eval_comptime_const_expr(enum_field.expr,
0)
{
enum_val = comptime_value.i64() or { -1 }
}
if node_val == enum_val {
in_range = true
break
}
enum_val += 1
}
}
if !in_range {
c.warn('${node_val} does not represent a value of enum ${enum_typ_name}',
node.pos)
}
}
}
if node.expr_type == ast.string_type_idx {
c.add_error_detail('use ${c.table.type_to_str(node.typ)}.from_string(${node.expr}) instead')
c.error('cannot cast `string` to `enum`', node.pos)
}
}
node.typname = c.table.sym(node.typ).name
return node.typ
}
fn (mut c Checker) at_expr(mut node ast.AtExpr) ast.Type {
match node.kind {
.fn_name {
if c.table.cur_fn == unsafe { nil } {
return ast.void_type
}
node.val = c.table.cur_fn.name.all_after_last('.')
}
.method_name {
if c.table.cur_fn == unsafe { nil } {
return ast.void_type
}
fname := c.table.cur_fn.name.all_after_last('.')
if c.table.cur_fn.is_method {
node.val = c.table.type_to_str(c.table.cur_fn.receiver.typ).all_after_last('.') +
'.' + fname
} else {
node.val = fname
}
}
.mod_name {
if c.table.cur_fn == unsafe { nil } {
return ast.void_type
}
node.val = c.table.cur_fn.mod
}
.struct_name {
if c.table.cur_fn.is_method || c.table.cur_fn.is_static_type_method {
node.val = c.table.type_to_str(c.table.cur_fn.receiver.typ).all_after_last('.')
} else {
node.val = ''
}
}
.vexe_path {
node.val = pref.vexe_path()
}
.file_path {
node.val = os.real_path(c.file.path)
}
.line_nr {
node.val = (node.pos.line_nr + 1).str()
}
.file_path_line_nr {
node.val = os.file_name(c.file.path) + ':' + (node.pos.line_nr + 1).str()
}
.column_nr {
node.val = (node.pos.col + 1).str()
}
.location {
mut mname := 'unknown'
if c.table.cur_fn != unsafe { nil } {
if c.table.cur_fn.is_method {
mname = c.table.type_to_str(c.table.cur_fn.receiver.typ) + '{}.' +
c.table.cur_fn.name.all_after_last('.')
} else {
mname = c.table.cur_fn.name
}
if c.table.cur_fn.is_static_type_method {
mname = mname.replace('__static__', '.') + ' (static)'
}
}
node.val = c.file.path + ':' + (node.pos.line_nr + 1).str() + ', ${mname}'
}
.vhash {
node.val = version.vhash()
}
.v_current_hash {
node.val = c.v_current_commit_hash
}
.vmod_file {
// cache the vmod content, do not read it many times
if c.vmod_file_content.len == 0 {
mut mcache := vmod.get_cache()
vmod_file_location := mcache.get_by_file(c.file.path)
if vmod_file_location.vmod_file.len == 0 {
c.error('@VMOD_FILE can be used only in projects, that have v.mod file',
node.pos)
}
vmod_content := os.read_file(vmod_file_location.vmod_file) or { '' }
c.vmod_file_content = vmod_content.replace('\r\n', '\n') // normalise EOLs just in case
}
node.val = c.vmod_file_content
}
.vroot_path {
node.val = os.dir(pref.vexe_path())
}
.vexeroot_path {
node.val = os.dir(pref.vexe_path())
}
.vmodroot_path {
mut mcache := vmod.get_cache()
vmod_file_location := mcache.get_by_file(c.file.path)
node.val = os.dir(vmod_file_location.vmod_file)
}
.unknown {
c.error('unknown @ identifier: ${node.name}. Available identifiers: ${token.valid_at_tokens}',
node.pos)
}
}
return ast.string_type
}
struct ACFieldMethod {
name string
typ string
}
fn (mut c Checker) ident(mut node ast.Ident) ast.Type {
if c.pref.linfo.is_running {
// Mini LS hack (v -line-info "a.v:16")
c.ident_autocomplete(node)
}
// TODO: move this
if c.const_deps.len > 0 {
mut name := node.name
if !name.contains('.') && node.mod != 'builtin' {
name = '${node.mod}.${node.name}'
}
// detect cycles, while allowing for references to the same constant,
// used inside its initialisation like: `struct Abc { x &Abc } ... const a = [ Abc{0}, Abc{unsafe{&a[0]}} ]!`
// see vlib/v/tests/const_fixed_array_containing_references_to_itself_test.v
if unsafe { c.const_var != 0 } && name == c.const_var.name {
if mut c.const_var.expr is ast.ArrayInit {
if c.const_var.expr.is_fixed && c.expected_type.nr_muls() > 0 {
elem_typ := c.expected_type.deref()
node.kind = .constant
node.name = c.const_var.name
node.info = ast.IdentVar{
typ: elem_typ
}
// c.const_var.typ = elem_typ
node.obj = c.const_var
return c.expected_type
}
}
c.error('cycle in constant `${c.const_var.name}`', node.pos)
return ast.void_type
}
c.const_deps << name
}
if node.kind == .blank_ident {
if node.tok_kind !in [.assign, .decl_assign] {
c.error('undefined ident: `_` (may only be used in assignments)', node.pos)
}
return ast.void_type
}
// second use
if node.kind in [.constant, .global, .variable] {
info := node.info as ast.IdentVar
typ := if c.comptime.is_comptime_var(node) {
ctype := c.comptime.get_comptime_var_type(node)
if ctype != ast.void_type {
ctype
} else {
info.typ
}
} else {
info.typ
}
// Got a var with type T, return current generic type
if node.or_expr.kind != .absent {
if !typ.has_flag(.option) {
if node.or_expr.kind == .propagate_option {
c.error('cannot use `?` on non-option variable', node.pos)
} else if node.or_expr.kind == .block {
c.error('cannot use `or {}` block on non-option variable', node.pos)
}
}
unwrapped_typ := typ.clear_option_and_result()
c.expected_or_type = unwrapped_typ
c.stmts_ending_with_expression(mut node.or_expr.stmts)
c.check_or_expr(node.or_expr, typ, c.expected_or_type, node)
return unwrapped_typ
}
return typ
} else if node.kind == .function {
info := node.info as ast.IdentFn
if func := c.table.find_fn(node.name) {
if func.generic_names.len > 0 {
concrete_types := node.concrete_types.map(c.unwrap_generic(it))
if concrete_types.all(!it.has_flag(.generic)) {
c.table.register_fn_concrete_types(func.fkey(), concrete_types)
}
}
}
return info.typ
} else if node.kind == .unresolved {
// first use
if node.tok_kind == .assign && node.is_mut {
c.error('`mut` not allowed with `=` (use `:=` to declare a variable)', node.pos)
}
if mut obj := node.scope.find(node.name) {
match mut obj {
ast.GlobalField {
if node.mod == '' {
node.kind = .global
node.info = ast.IdentVar{
typ: obj.typ
}
node.obj = obj
return obj.typ
}
}
ast.Var {
// inside vweb tmpl ident positions are meaningless, use the position of the comptime call.
// if the variable is declared before the comptime call then we can assume all is well.
// `node.name !in node.scope.objects && node.scope.start_pos < c.comptime_call_pos` (inherited)
node_pos := if c.pref.is_vweb && node.name !in node.scope.objects
&& node.scope.start_pos < c.comptime_call_pos {
c.comptime_call_pos
} else {
node.pos.pos
}
if node_pos < obj.pos.pos {
c.error('undefined variable `${node.name}` (used before declaration)',
node.pos)
}
is_sum_type_cast := obj.smartcasts.len != 0
&& !c.prevent_sum_type_unwrapping_once
c.prevent_sum_type_unwrapping_once = false
mut typ := if is_sum_type_cast { obj.smartcasts.last() } else { obj.typ }
if typ == 0 {
if mut obj.expr is ast.Ident {
if obj.expr.kind == .unresolved {
c.error('unresolved variable: `${node.name}`', node.pos)
return ast.void_type
}
}
if mut obj.expr is ast.IfGuardExpr {
// new variable from if guard shouldn't have the option flag for further use
// a temp variable will be generated which unwraps it
sym := c.table.sym(obj.expr.expr_type)
if sym.kind == .multi_return {
mr_info := sym.info as ast.MultiReturn
if mr_info.types.len == obj.expr.vars.len {
for vi, var in obj.expr.vars {
if var.name == node.name {
typ = mr_info.types[vi]
}
}
}
} else {
typ = obj.expr.expr_type.clear_option_and_result()
}
} else if obj.expr is ast.EmptyExpr {
c.error('invalid variable `${node.name}`', node.pos)
typ = ast.void_type
} else {
typ = c.expr(mut obj.expr)
}
}
if c.inside_interface_deref && c.table.is_interface_var(obj) {
typ = typ.deref()
}
is_option := typ.has_option_or_result() || node.or_expr.kind != .absent
node.kind = .variable
node.info = ast.IdentVar{
typ: typ
is_option: is_option
}
if !is_sum_type_cast {
obj.typ = typ
}
node.obj = obj
// unwrap option (`println(x)`)
if is_option {
if node.or_expr.kind == .absent {
return typ.clear_flag(.result)
}
if !typ.has_flag(.option) {
if node.or_expr.kind == .propagate_option {
c.error('cannot use `?` on non-option variable', node.pos)
} else if node.or_expr.kind == .block {
c.error('cannot use `or {}` block on non-option variable',
node.pos)
}
}
unwrapped_typ := typ.clear_option_and_result()
c.expected_or_type = unwrapped_typ
c.stmts_ending_with_expression(mut node.or_expr.stmts)
c.check_or_expr(node.or_expr, typ, c.expected_or_type, node)
return unwrapped_typ
}
return typ
}
else {}
}
}
mut name := node.name
// check for imported symbol
if name in c.file.imported_symbols {
name = c.file.imported_symbols[name]
}
// prepend mod to look for fn call or const
else if !name.contains('.') && node.mod != 'builtin' {
name = '${node.mod}.${node.name}'
}
if mut obj := c.file.global_scope.find(name) {
match mut obj {
ast.GlobalField {
node.kind = .global
node.info = ast.IdentVar{
typ: obj.typ
}
node.obj = obj
return obj.typ
}
ast.ConstField {
if !(obj.is_pub || obj.mod == c.mod || c.pref.is_test) {
c.error('constant `${obj.name}` is private', node.pos)
}
mut typ := obj.typ
if typ == 0 {
old_c_mod := c.mod
c.mod = obj.mod
c.inside_const = true
typ = c.expr(mut obj.expr)
c.inside_const = false
c.mod = old_c_mod
if mut obj.expr is ast.CallExpr {
if obj.expr.or_block.kind != .absent {
typ = typ.clear_option_and_result()
}
}
}
node.name = name
node.kind = .constant
node.info = ast.IdentVar{
typ: typ
}
obj.typ = typ
node.obj = obj
if obj.attrs.contains('deprecated') && obj.mod != c.mod {
c.deprecate('const', obj.name, obj.attrs, node.pos)
}
if node.or_expr.kind != .absent {
unwrapped_typ := typ.clear_option_and_result()
c.expected_or_type = unwrapped_typ
c.stmts_ending_with_expression(mut node.or_expr.stmts)
c.check_or_expr(node.or_expr, typ, c.expected_or_type, node)
}
return typ
}
else {}
}
}
// Non-anon-function object (not a call), e.g. `onclick(my_click)`
if func := c.table.find_fn(name) {
mut fn_type := ast.new_type(c.table.find_or_register_fn_type(func, false,
true))
if func.generic_names.len > 0 {
concrete_types := node.concrete_types.map(c.unwrap_generic(it))
if typ_ := c.table.resolve_generic_to_concrete(fn_type, func.generic_names,
concrete_types)
{
fn_type = typ_
if concrete_types.all(!it.has_flag(.generic)) {
c.table.register_fn_concrete_types(func.fkey(), concrete_types)
}
}
}
node.name = name
node.kind = .function
node.info = ast.IdentFn{
typ: fn_type
}
return fn_type
}
}
if node.language == .c {
if node.name == 'C.NULL' {
return ast.voidptr_type
}
return ast.int_type
}
if c.inside_sql {
if field := c.table.find_field(c.cur_orm_ts, node.name) {
return field.typ
}
}
if node.kind == .unresolved && node.mod != 'builtin' {
// search in the `builtin` idents, for example
// main.compare_f32 may actually be builtin.compare_f32
saved_mod := node.mod
node.mod = 'builtin'
builtin_type := c.ident(mut node)
if node.obj is ast.ConstField {
field := node.obj as ast.ConstField
if field.attrs.contains('deprecated') && field.mod != c.mod {
c.deprecate('const', field.name, field.attrs, node.pos)
}
}
if builtin_type != ast.void_type {
return builtin_type
}
node.mod = saved_mod
}
if node.tok_kind == .assign {
c.error('undefined ident: `${node.name}` (use `:=` to declare a variable)', node.pos)
} else if node.name == 'errcode' {
c.error('undefined ident: `errcode`; did you mean `err.code`?', node.pos)
} else {
if c.inside_ct_attr {
c.note('`[if ${node.name}]` is deprecated. Use `[if ${node.name}?]` instead',
node.pos)
} else {
cname_mod := node.name.all_before('.')
if cname_mod.len != node.name.len {
mut const_names_in_mod := []string{}
for _, so in c.table.global_scope.objects {
if so is ast.ConstField {
if so.mod == cname_mod {
const_names_in_mod << so.name
}
}
}
c.error(util.new_suggestion(node.name, const_names_in_mod).say('undefined ident: `${node.name}`'),
node.pos)
} else {
// If a variable is not found in the scope of an anonymous function
// but is in an external scope, then we can suggest the user add it to the capturing list.
if c.inside_anon_fn {
found_var := c.fn_scope.find_var(node.name)
if found_var != none {
if c.inside_lambda {
// Lambdas don't support capturing variables yet, so that's the only hint.
c.error('undefined variable `${node.name}`', node.pos)
} else {
c.error('`${node.name}` must be added to the capture list for the closure to be used inside',
node.pos)
}
return ast.void_type
}
}
c.error('undefined ident: `${node.name}`', node.pos)
}
}
}
if c.table.known_type(node.name) {
// e.g. `User` in `json.decode(User, '...')`
return ast.void_type
}
return ast.void_type
}
fn (mut c Checker) concat_expr(mut node ast.ConcatExpr) ast.Type {
mut mr_types := []ast.Type{}
for mut expr in node.vals {
mr_types << c.expr(mut expr)
}
if node.vals.len == 1 {
typ := mr_types[0]
node.return_type = typ
return typ
} else {
for i := 0; i < mr_types.len; i++ {
if mr_types[i] == ast.void_type {
c.error('type `void` cannot be used in multi-return', node.vals[i].pos())
return ast.void_type
}
}
typ := c.table.find_or_register_multi_return(mr_types)
ast.new_type(typ)
node.return_type = typ
return typ
}
}
// smartcast takes the expression with the current type which should be smartcasted to the target type in the given scope
fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast.Type, mut scope ast.Scope, is_comptime bool) {
sym := c.table.sym(cur_type)
to_type := if sym.kind == .interface_ && c.table.sym(to_type_).kind != .interface_ {
to_type_.ref()
} else {
to_type_
}
match mut expr {
ast.SelectorExpr {
mut is_mut := false
mut smartcasts := []ast.Type{}
expr_sym := c.table.sym(expr.expr_type)
mut orig_type := 0
if field := c.table.find_field(expr_sym, expr.field_name) {
if field.is_mut {
if root_ident := expr.root_ident() {
if v := scope.find_var(root_ident.name) {
is_mut = v.is_mut
}
}
}
if orig_type == 0 {
orig_type = field.typ
}
}
if field := scope.find_struct_field(expr.expr.str(), expr.expr_type, expr.field_name) {
smartcasts << field.smartcasts
}
// smartcast either if the value is immutable or if the mut argument is explicitly given
if !is_mut || expr.is_mut {
smartcasts << to_type
scope.register_struct_field(expr.expr.str(), ast.ScopeStructField{
struct_type: expr.expr_type
name: expr.field_name
typ: cur_type
smartcasts: smartcasts
pos: expr.pos
orig_type: orig_type
})
} else {
c.smartcast_mut_pos = expr.pos
}
}
ast.Ident {
mut is_mut := false
mut smartcasts := []ast.Type{}
mut is_already_casted := false
mut orig_type := 0
mut is_inherited := false
mut ct_type_var := ast.ComptimeVarKind.no_comptime
if mut expr.obj is ast.Var {
is_mut = expr.obj.is_mut
smartcasts << expr.obj.smartcasts
is_already_casted = expr.obj.pos.pos == expr.pos.pos
if orig_type == 0 {
orig_type = expr.obj.typ
}
is_inherited = expr.obj.is_inherited
ct_type_var = if is_comptime {
.smartcast
} else {
.no_comptime
}
}
// smartcast either if the value is immutable or if the mut argument is explicitly given
if (!is_mut || expr.is_mut) && !is_already_casted {
smartcasts << to_type
if var := scope.find_var(expr.name) {
if is_comptime && var.ct_type_var == .smartcast {
scope.update_smartcasts(expr.name, to_type)
return
}
}
scope.register(ast.Var{
name: expr.name
typ: cur_type
pos: expr.pos
is_used: true
is_mut: expr.is_mut
is_inherited: is_inherited
smartcasts: smartcasts
orig_type: orig_type
ct_type_var: ct_type_var
})
} else if is_mut && !expr.is_mut {
c.smartcast_mut_pos = expr.pos
}
}
else {
c.smartcast_cond_pos = expr.pos()
}
}
}
fn (mut c Checker) select_expr(mut node ast.SelectExpr) ast.Type {
node.is_expr = c.expected_type != ast.void_type
node.expected_type = c.expected_type
for mut branch in node.branches {
c.stmt(mut branch.stmt)
match mut branch.stmt {
ast.ExprStmt {
if branch.is_timeout {
if !branch.stmt.typ.is_int() {
tsym := c.table.sym(branch.stmt.typ)
c.error('invalid type `${tsym.name}` for timeout - expected integer number of nanoseconds aka `time.Duration`',
branch.stmt.pos)
}
} else {
if mut branch.stmt.expr is ast.InfixExpr {
if branch.stmt.expr.left !in [ast.Ident, ast.SelectorExpr, ast.IndexExpr] {
c.error('channel in `select` key must be predefined', branch.stmt.expr.left.pos())
}
} else {
c.error('invalid expression for `select` key', branch.stmt.expr.pos())
}
}
}
ast.AssignStmt {
expr := branch.stmt.right[0]
match expr {
ast.PrefixExpr {
if expr.right !in [ast.Ident, ast.SelectorExpr, ast.IndexExpr] {
c.error('channel in `select` key must be predefined', expr.right.pos())
}
if expr.or_block.kind != .absent {
err_prefix := if expr.or_block.kind == .block {
'or block'
} else {
'error propagation'
}
c.error('${err_prefix} not allowed in `select` key', expr.or_block.pos)
}
}
else {
c.error('`<-` receive expression expected', branch.stmt.right[0].pos())
}
}
if mut branch.stmt.left[0] is ast.Ident {
ident := branch.stmt.left[0] as ast.Ident
if ident.kind == .blank_ident && branch.stmt.op != .decl_assign {
c.error('cannot send on `_`, use `_ := <- quit` instead', branch.stmt.left[0].pos())
}
}
}
else {
if !branch.is_else {
c.error('receive or send statement expected as `select` key', branch.stmt.pos)
}
}
}
c.stmts(mut branch.stmts)
}
return ast.bool_type
}
fn (mut c Checker) lock_expr(mut node ast.LockExpr) ast.Type {
expected_type := c.expected_type
if c.rlocked_names.len > 0 || c.locked_names.len > 0 {
c.error('nested `lock`/`rlock` not allowed', node.pos)
}
for i in 0 .. node.lockeds.len {
mut expr_ := node.lockeds[i]
e_typ := c.expr(mut expr_)
id_name := node.lockeds[i].str()
if !e_typ.has_flag(.shared_f) {
obj_type := if node.lockeds[i] is ast.Ident { 'variable' } else { 'struct element' }
c.error('`${id_name}` must be declared as `shared` ${obj_type} to be locked',
node.lockeds[i].pos())
}
if id_name in c.locked_names {
c.error('`${id_name}` is already locked', node.lockeds[i].pos())
} else if id_name in c.rlocked_names {
c.error('`${id_name}` is already read-locked', node.lockeds[i].pos())
}
if node.is_rlock[i] {
c.rlocked_names << id_name
} else {
c.locked_names << id_name
}
}
c.stmts(mut node.stmts)
// handle `x := rlock a { a.getval() }`
mut ret_type := ast.void_type
if node.stmts.len > 0 {
mut last_stmt := node.stmts.last()
if mut last_stmt is ast.ExprStmt {
c.expected_type = expected_type
ret_type = c.expr(mut last_stmt.expr)
}
}
c.rlocked_names = []
c.locked_names = []
if ret_type != ast.void_type {
node.is_expr = true
}
node.typ = ret_type
return ret_type
}
fn (mut c Checker) unsafe_expr(mut node ast.UnsafeExpr) ast.Type {
prev_unsafe := c.inside_unsafe
c.inside_unsafe = true
t := c.expr(mut node.expr)
c.inside_unsafe = prev_unsafe
return t
}
fn (mut c Checker) find_definition(ident ast.Ident) !ast.Expr {
match ident.kind {
.unresolved, .blank_ident { return error('none') }
.variable, .constant { return c.find_obj_definition(ident.obj) }
.global { return error('${ident.name} is a global variable') }
.function { return error('${ident.name} is a function') }
}
}
fn (mut c Checker) find_obj_definition(obj ast.ScopeObject) !ast.Expr {
// TODO: remove once we have better type inference
mut name := ''
match obj {
ast.Var, ast.ConstField, ast.GlobalField, ast.AsmRegister { name = obj.name }
}
mut expr := ast.empty_expr
if obj is ast.Var {
if obj.is_mut {
return error('`${name}` is mut and may have changed since its definition')
}
expr = obj.expr
} else if obj is ast.ConstField {
expr = obj.expr
} else {
return error('`${name}` is a global variable and is unknown at compile time')
}
if mut expr is ast.Ident {
return c.find_definition(expr)
}
if !expr.is_pure_literal() {
return error('definition of `${name}` is unknown at compile time')
}
return expr
}
fn (c &Checker) has_return(stmts []ast.Stmt) ?bool {
// complexity means either more match or ifs
mut has_complexity := false
for s in stmts {
if s is ast.ExprStmt {
if s.expr in [ast.IfExpr, ast.MatchExpr] {
has_complexity = true
break
}
}
}
// if the inner complexity covers all paths with returns there is no need for further checks
if !has_complexity || !c.returns {
return has_top_return(stmts)
}
return none
}
fn (mut c Checker) mark_as_referenced(mut node ast.Expr, as_interface bool) {
match mut node {
ast.Ident {
if mut node.obj is ast.Var {
mut obj := unsafe { &node.obj }
if c.fn_scope != unsafe { nil } {
obj = c.fn_scope.find_var(node.obj.name) or { obj }
}
if obj.typ == 0 {
return
}
type_sym := c.table.sym(obj.typ.set_nr_muls(0))
if obj.is_stack_obj && !type_sym.is_heap() && !c.pref.translated
&& !c.file.is_translated {
suggestion := if type_sym.kind == .struct_ {
'declaring `${type_sym.name}` as `[heap]`'
} else {
'wrapping the `${type_sym.name}` object in a `struct` declared as `[heap]`'
}
mischief := if as_interface { 'used as interface object' } else { 'referenced' }
c.error('`${node.name}` cannot be ${mischief} outside `unsafe` blocks as it might be stored on stack. Consider ${suggestion}.',
node.pos)
} else if type_sym.kind == .array_fixed {
c.error('cannot reference fixed array `${node.name}` outside `unsafe` blocks as it is supposed to be stored on stack',
node.pos)
} else {
match type_sym.kind {
.struct_ {
info := type_sym.info as ast.Struct
if !info.is_heap {
node.obj.is_auto_heap = true
}
}
.sum_type, .interface_ {}
else {
node.obj.is_auto_heap = true
}
}
}
}
}
ast.SelectorExpr {
if !node.expr_type.is_ptr() {
c.mark_as_referenced(mut &node.expr, as_interface)
}
}
ast.IndexExpr {
c.mark_as_referenced(mut &node.left, as_interface)
}
else {}
}
}
fn (mut c Checker) get_base_name(node &ast.Expr) string {
match node {
ast.Ident {
return node.name
}
ast.SelectorExpr {
return c.get_base_name(&node.expr)
}
ast.IndexExpr {
return c.get_base_name(&node.left)
}
else {
return ''
}
}
}
fn (mut c Checker) prefix_expr(mut node ast.PrefixExpr) ast.Type {
old_inside_ref_lit := c.inside_ref_lit
c.inside_ref_lit = c.inside_ref_lit || node.op == .amp
right_type := c.expr(mut node.right)
c.inside_ref_lit = old_inside_ref_lit
node.right_type = right_type
if node.op == .amp {
if node.right is ast.Nil {
c.error('invalid operation: cannot take address of nil', node.right.pos())
}
if mut node.right is ast.PrefixExpr {
if node.right.op == .amp {
c.error('unexpected `&`, expecting expression', node.right.pos)
}
} else if mut node.right is ast.SelectorExpr {
if node.right.expr.is_literal() {
c.error('cannot take the address of a literal value', node.pos.extend(node.right.pos))
}
right_sym := c.table.sym(right_type)
expr_sym := c.table.sym(node.right.expr_type)
if expr_sym.kind == .struct_ && (expr_sym.info as ast.Struct).is_minify
&& (node.right.typ == ast.bool_type_idx || (right_sym.kind == .enum_
&& !(right_sym.info as ast.Enum).is_flag
&& !(right_sym.info as ast.Enum).uses_exprs)) {
c.error('cannot take the address of field in struct `${c.table.type_to_str(node.right.expr_type)}`, which is tagged as `[minify]`',
node.pos.extend(node.right.pos))
}
if node.right.typ.has_flag(.option) {
c.error('cannot take the address of an Option field', node.pos.extend(node.right.pos))
}
}
}
// TODO: testing ref/deref strategy
if node.op == .amp && !right_type.is_ptr() {
mut expr := node.right
// if ParExpr get the innermost expr
for mut expr is ast.ParExpr {
expr = expr.expr
}
if expr in [ast.BoolLiteral, ast.CallExpr, ast.CharLiteral, ast.FloatLiteral, ast.IntegerLiteral,
ast.InfixExpr, ast.StringLiteral, ast.StringInterLiteral] {
c.error('cannot take the address of ${expr}', node.pos)
}
if mut node.right is ast.Ident {
if node.right.kind == .constant && !c.inside_unsafe && c.pref.experimental {
c.warn('cannot take the address of const outside `unsafe`', node.right.pos)
}
}
if node.right is ast.SelectorExpr {
typ_sym := c.table.sym(right_type)
if typ_sym.kind == .map && !c.inside_unsafe {
c.error('cannot take the address of map values outside `unsafe`', node.pos)
}
}
if mut node.right is ast.IndexExpr {
typ_sym := c.table.sym(node.right.left_type)
mut is_mut := false
if mut node.right.left is ast.Ident {
ident := node.right.left
// TODO: temporary, remove this
ident_obj := ident.obj
if ident_obj is ast.Var {
is_mut = ident_obj.is_mut
}
}
if typ_sym.kind == .map {
c.error('cannot take the address of map values', node.right.pos)
}
if !c.inside_unsafe {
if typ_sym.kind == .array && is_mut {
c.error('cannot take the address of mutable array elements outside unsafe blocks',
node.right.pos)
}
}
}
if !c.inside_fn_arg && !c.inside_unsafe {
c.mark_as_referenced(mut &node.right, false)
}
return right_type.ref()
} else if node.op == .amp && node.right !is ast.CastExpr {
if !c.inside_fn_arg && !c.inside_unsafe {
c.mark_as_referenced(mut &node.right, false)
}
if node.right.is_auto_deref_var() {
return right_type
} else {
return right_type.ref()
}
}
right_sym := c.table.final_sym(c.unwrap_generic(right_type))
if node.op == .mul {
if right_type.has_flag(.option) {
c.error('type `?${right_sym.name}` is an Option, it must be unwrapped first; use `*var?` to do it',
node.right.pos())
}
if right_type.is_ptr() {
return right_type.deref()
}
if !right_type.is_pointer() && !c.pref.translated && !c.file.is_translated {
s := c.table.type_to_str(right_type)
c.error('invalid indirect of `${s}`, the type `${right_sym.name}` is not a pointer',
node.pos)
}
if right_type.is_voidptr() {
c.error('cannot dereference to void', node.pos)
}
if mut node.right is ast.Ident {
if var := node.right.scope.find_var('${node.right.name}') {
if var.expr is ast.UnsafeExpr {
if var.expr.expr is ast.Nil {
c.error('cannot deference a `nil` pointer', node.right.pos)
}
}
}
}
}
if node.op == .bit_not && !c.pref.translated && !c.file.is_translated {
if right_sym.info is ast.Enum && !right_sym.info.is_flag {
c.error('operator `~` can only be used with `@[flag]` tagged enums', node.pos)
}
// Only check for int not enum as it is done above
if !right_sym.is_int() && right_sym.info !is ast.Enum {
c.type_error_for_operator('~', 'integer', right_sym.name, node.pos)
}
}
if node.op == .not && right_sym.kind != .bool && !c.pref.translated && !c.file.is_translated {
c.type_error_for_operator('!', 'bool', right_sym.name, node.pos)
}
// FIXME
// there are currently other issues to investigate if right_type
// is unwrapped directly as initialization, so do it here
if node.op == .minus && !right_sym.is_number() {
c.type_error_for_operator('-', 'numeric', right_sym.name, node.pos)
}
if node.op == .arrow {
raw_right_sym := c.table.final_sym(right_type)
if raw_right_sym.kind == .chan {
c.stmts_ending_with_expression(mut node.or_block.stmts)
return raw_right_sym.chan_info().elem_type
}
c.type_error_for_operator('<-', '`chan`', raw_right_sym.name, node.pos)
}
return right_type
}
fn (mut c Checker) type_error_for_operator(op_label string, types_label string, found_type_label string, pos token.Pos) {
c.error('operator `${op_label}` can only be used with ${types_label} types, but the value after `${op_label}` is of type `${found_type_label}` instead',
pos)
}
fn (mut c Checker) check_index(typ_sym &ast.TypeSymbol, index ast.Expr, index_type ast.Type, pos token.Pos, range_index bool, is_gated bool) {
if typ_sym.kind in [.array, .array_fixed, .string] {
index_type_sym := c.table.sym(index_type)
if !(index_type.is_int() || index_type_sym.kind == .enum_
|| (index_type_sym.kind == .alias
&& (index_type_sym.info as ast.Alias).parent_type.is_int())
|| (c.pref.translated && index_type.is_any_kind_of_pointer())) {
type_str := if typ_sym.kind == .string {
'non-integer string index `${c.table.type_to_str(index_type)}`'
} else {
'non-integer index `${c.table.type_to_str(index_type)}` (array type `${typ_sym.name}`)'
}
c.error('${type_str}', pos)
}
if index is ast.IntegerLiteral && !is_gated {
if index.val[0] == `-` {
c.error('negative index `${index.val}`', index.pos)
} else if typ_sym.kind == .array_fixed {
i := index.val.int()
info := typ_sym.info as ast.ArrayFixed
if (!range_index && i >= info.size) || (range_index && i > info.size) {
c.error('index out of range (index: ${i}, len: ${info.size})', index.pos)
}
}
}
if index_type.has_option_or_result() {
type_str := if typ_sym.kind == .string {
'(type `${typ_sym.name}`)'
} else {
'(array type `${typ_sym.name}`)'
}
c.error('cannot use Option or Result as index ${type_str}', pos)
}
}
}
fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type {
mut typ := c.expr(mut node.left)
if typ == 0 {
c.error('unknown type for expression `${node.left}`', node.pos)
return typ
}
mut typ_sym := c.table.final_sym(typ)
node.left_type = typ
match typ_sym.kind {
.map {
node.is_map = true
}
.array {
node.is_array = true
if node.or_expr.kind != .absent && node.index is ast.RangeExpr {
c.error('custom error handling on range expressions for arrays is not supported yet.',
node.or_expr.pos)
}
}
.array_fixed {
node.is_farray = true
}
.any {
unwrapped_typ := c.unwrap_generic(typ)
unwrapped_sym := c.table.final_sym(unwrapped_typ)
if unwrapped_sym.kind in [.map, .array, .array_fixed] {
typ = unwrapped_typ
typ_sym = unwrapped_sym
}
}
else {}
}
is_aggregate_arr := typ_sym.kind == .aggregate
&& (typ_sym.info as ast.Aggregate).types.filter(c.table.type_kind(it) !in [.array, .array_fixed, .string, .map]).len == 0
if typ_sym.kind !in [.array, .array_fixed, .string, .map]
&& (!typ.is_ptr() || typ_sym.kind in [.sum_type, .interface_])
&& typ !in [ast.byteptr_type, ast.charptr_type] && !typ.has_flag(.variadic)
&& !is_aggregate_arr {
c.error('type `${typ_sym.name}` does not support indexing', node.pos)
}
if is_aggregate_arr {
// treating indexexpr of sumtype of array types
typ = (typ_sym.info as ast.Aggregate).types[0]
}
if typ.has_flag(.option) {
if node.left is ast.Ident && node.left.or_expr.kind == .absent {
c.error('type `?${typ_sym.name}` is an Option, it must be unwrapped first; use `var?[]` to do it',
node.left.pos())
} else if node.left is ast.CallExpr {
c.error('type `?${typ_sym.name}` is an Option, it must be unwrapped with `func()?`, or use `func() or {default}`',
node.left.pos())
}
} else if typ.has_flag(.result) {
c.error('type `!${typ_sym.name}` is a Result, it does not support indexing', node.left.pos())
}
if typ_sym.kind == .string && !typ.is_ptr() && node.is_setter {
c.error('cannot assign to s[i] since V strings are immutable\n' +
'(note, that variables may be mutable but string values are always immutable, like in Go and Java)',
node.pos)
}
if !c.inside_unsafe && !c.is_builtin_mod && !c.inside_if_guard && !c.is_index_assign
&& typ_sym.kind == .map && node.or_expr.stmts.len == 0 {
elem_type := c.table.value_type(typ)
if elem_type.is_any_kind_of_pointer() {
c.note('accessing a pointer map value requires an `or {}` block outside `unsafe`',
node.pos)
}
mut checked_types := []ast.Type{}
if c.is_contains_any_kind_of_pointer(elem_type, mut checked_types) {
c.note('accessing map value that contain pointers requires an `or {}` block outside `unsafe`',
node.pos)
}
}
if (typ.is_ptr() && !typ.has_flag(.shared_f) && (!node.left.is_auto_deref_var()
|| (typ_sym.kind == .struct_ && typ_sym.name != 'array')))
|| typ.is_pointer() {
mut is_ok := false
mut is_mut_struct := false
if mut node.left is ast.Ident {
if mut node.left.obj is ast.Var {
// `mut param []T` function parameter
is_ok = node.left.obj.is_mut && node.left.obj.is_arg && !typ.deref().is_ptr()
&& typ_sym.kind != .struct_
// `mut param Struct`
is_mut_struct = node.left.obj.is_mut && node.left.obj.is_arg
&& typ_sym.kind == .struct_
}
}
if !is_ok && node.index is ast.RangeExpr {
s := c.table.type_to_str(typ)
c.error('type `${s}` does not support slicing', node.pos)
} else if is_mut_struct {
c.error('type `mut ${typ_sym.name}` does not support slicing', node.pos)
} else if !c.inside_unsafe && !is_ok && !c.pref.translated && !c.file.is_translated {
c.warn('pointer indexing is only allowed in `unsafe` blocks', node.pos)
}
}
if mut node.index is ast.RangeExpr { // [1..2]
if node.index.has_low {
index_type := c.expr(mut node.index.low)
c.check_index(typ_sym, node.index.low, index_type, node.pos, true, node.is_gated)
}
if node.index.has_high {
index_type := c.expr(mut node.index.high)
c.check_index(typ_sym, node.index.high, index_type, node.pos, true, node.is_gated)
}
// array[1..2] => array
// fixed_array[1..2] => array
if typ_sym.kind == .array_fixed {
elem_type := c.table.value_type(typ)
idx := c.table.find_or_register_array(elem_type)
typ = ast.new_type(idx)
} else {
typ = typ.set_nr_muls(0)
}
} else { // [1]
if typ_sym.kind == .map {
info := typ_sym.info as ast.Map
c.expected_type = info.key_type
index_type := c.expr(mut node.index)
if !c.check_types(index_type, info.key_type) {
err := c.expected_msg(index_type, info.key_type)
c.error('invalid key: ${err}', node.pos)
}
value_sym := c.table.sym(info.value_type)
if !node.is_setter && value_sym.kind == .sum_type && node.or_expr.kind == .absent
&& !c.inside_unsafe && !c.inside_if_guard {
c.warn('`or {}` block required when indexing a map with sum type value',
node.pos)
}
} else {
index_type := c.expr(mut node.index)
// for [1] case #[1] is not allowed!
if node.is_gated == true {
c.error('`#[]` allowed only for ranges', node.pos)
}
c.check_index(typ_sym, node.index, index_type, node.pos, false, false)
}
value_type := c.table.value_type(typ)
if value_type != ast.void_type {
typ = value_type
}
}
if node.or_expr.stmts.len > 0 && node.or_expr.stmts.last() is ast.ExprStmt {
c.expected_or_type = typ
}
c.stmts_ending_with_expression(mut node.or_expr.stmts)
c.check_expr_option_or_result_call(node, typ)
return typ
}
// `.green` or `Color.green`
// If a short form is used, `expected_type` needs to be an enum
// with this value.
fn (mut c Checker) enum_val(mut node ast.EnumVal) ast.Type {
mut typ_idx := if node.enum_name == '' {
// Get the type of the enum without enum name by looking at the expected type.
// e.g. `set_color(.green)`, V knows that `Green` is the expected type.
if c.expected_type == ast.void_type && c.expected_expr_type != ast.void_type {
c.expected_expr_type.idx()
} else {
c.expected_type.idx()
}
} else {
c.table.find_type_idx(node.enum_name)
}
if typ_idx == 0 {
// Handle `builtin` enums like `ChanState`, so that `x := ChanState.closed` works.
// In the checker the name for such enums was set to `main.ChanState` instead of
// just `ChanState`.
if node.enum_name.starts_with('${c.mod}.') {
typ_idx = c.table.find_type_idx(node.enum_name['${c.mod}.'.len..])
if typ_idx == 0 {
c.error('unknown enum `${node.enum_name}` (type_idx=0)', node.pos)
return ast.void_type
}
}
if typ_idx == 0 {
// the actual type is still unknown, produce an error, instead of panic:
c.error('unknown enum `${node.enum_name}` (type_idx=0)', node.pos)
return ast.void_type
}
}
mut typ := ast.new_type(typ_idx)
if c.pref.translated || c.file.is_translated {
// TODO make more strict
node.typ = typ
return typ
}
if typ == ast.void_type {
c.error('not an enum', node.pos)
return ast.void_type
}
mut typ_sym := c.table.sym(typ)
if typ_sym.kind == .array && node.enum_name.len == 0 {
array_info := typ_sym.info as ast.Array
typ = array_info.elem_type
typ_sym = c.table.sym(typ)
}
fsym := c.table.final_sym(typ)
if fsym.kind != .enum_ && !c.pref.translated && !c.file.is_translated {
// TODO in C int fields can be compared to enums, need to handle that in C2V
if typ_sym.kind == .placeholder {
// If it's a placeholder, the type doesn't exist, print
// an error that makes sense here.
c.error('unknown type `${typ_sym.name}`', node.pos)
} else {
c.error('expected type is not an enum (`${typ_sym.name}`)', node.pos)
}
return ast.void_type
}
if fsym.info !is ast.Enum {
c.error('not an enum', node.pos)
return ast.void_type
}
if !(typ_sym.is_pub || typ_sym.mod == c.mod) {
c.error('enum `${typ_sym.name}` is private', node.pos)
}
info := typ_sym.enum_info()
if node.val !in info.vals {
suggestion := util.new_suggestion(node.val, info.vals)
c.error(suggestion.say('enum `${typ_sym.name}` does not have a value `${node.val}`'),
node.pos)
}
node.typ = typ
return typ
}
fn (mut c Checker) chan_init(mut node ast.ChanInit) ast.Type {
if node.typ != 0 {
info := c.table.sym(node.typ).chan_info()
node.elem_type = info.elem_type
if node.elem_type != 0 {
elem_sym := c.table.sym(node.elem_type)
if elem_sym.kind == .placeholder {
c.error('unknown type `${elem_sym.name}`', node.elem_type_pos)
}
}
if node.has_cap {
c.check_array_init_para_type('cap', mut node.cap_expr, node.pos)
}
return node.typ
} else {
c.error('`chan` of unknown type', node.pos)
return node.typ
}
}
fn (mut c Checker) offset_of(node ast.OffsetOf) ast.Type {
sym := c.table.final_sym(node.struct_type)
if sym.kind != .struct_ {
c.error('first argument of __offsetof must be struct', node.pos)
return ast.u32_type
}
if !c.table.struct_has_field(sym, node.field) {
c.error('struct `${sym.name}` has no field called `${node.field}`', node.pos)
}
return ast.u32_type
}
fn (mut c Checker) check_dup_keys(node &ast.MapInit, i int) {
key_i := node.keys[i]
if key_i is ast.StringLiteral {
for j in 0 .. i {
key_j := node.keys[j]
if key_j is ast.StringLiteral {
if key_i.val == key_j.val {
c.error('duplicate key "${key_i.val}" in map literal', key_i.pos)
}
}
}
} else if key_i is ast.IntegerLiteral {
for j in 0 .. i {
key_j := node.keys[j]
if key_j is ast.IntegerLiteral {
if key_i.val == key_j.val {
c.error('duplicate key "${key_i.val}" in map literal', key_i.pos)
}
}
}
}
}
fn (c &Checker) check_struct_signature_init_fields(from ast.Struct, to ast.Struct, node ast.StructInit) bool {
if node.init_fields.len == 0 {
return from.fields.len == to.fields.len
}
mut count_not_in_from := 0
for field in node.init_fields {
filtered := from.fields.filter(it.name == field.name)
if filtered.len != 1 {
count_not_in_from++
}
}
return (from.fields.len + count_not_in_from) == to.fields.len
}
// check `to` has all fields of `from`
fn (c &Checker) check_struct_signature(from ast.Struct, to ast.Struct) bool {
// Note: `to` can have extra fields
if from.fields.len == 0 {
return false
}
for field in from.fields {
filtered := to.fields.filter(it.name == field.name)
if filtered.len != 1 {
// field doesn't exist
return false
}
counterpart := filtered[0]
if field.typ != counterpart.typ {
// field has different type
return false
}
if field.is_pub != counterpart.is_pub {
// field is not public while the other one is
return false
}
if field.is_mut != counterpart.is_mut {
// field is not mutable while the other one is
return false
}
}
return true
}
fn (mut c Checker) fetch_field_name(field ast.StructField) string {
mut name := field.name
for attr in field.attrs {
if attr.kind == .string && attr.arg != '' && attr.name == 'sql' {
name = attr.arg
break
}
}
sym := c.table.sym(field.typ)
if sym.kind == .struct_ && sym.name != 'time.Time' {
name = '${name}_id'
}
return name
}
fn (mut c Checker) ensure_generic_type_specify_type_names(typ ast.Type, pos token.Pos) bool {
if typ == 0 {
c.error('unknown type', pos)
return false
}
c.ensure_generic_type_level++
defer {
c.ensure_generic_type_level--
}
if c.ensure_generic_type_level > checker.expr_level_cutoff_limit {
c.error('checker: too many levels of Checker.ensure_generic_type_specify_type_names calls: ${c.ensure_generic_type_level} ',
pos)
return false
}
sym := c.table.final_sym(typ)
if c.ensure_generic_type_level > 38 {
dump(typ)
dump(sym.kind)
dump(pos)
dump(c.ensure_generic_type_level)
}
match sym.kind {
.function {
fn_info := sym.info as ast.FnType
if !c.ensure_generic_type_specify_type_names(fn_info.func.return_type, fn_info.func.return_type_pos) {
return false
}
for param in fn_info.func.params {
if !c.ensure_generic_type_specify_type_names(param.typ, param.type_pos) {
return false
}
}
}
.array {
if !c.ensure_generic_type_specify_type_names((sym.info as ast.Array).elem_type,
pos) {
return false
}
}
.array_fixed {
if !c.ensure_generic_type_specify_type_names((sym.info as ast.ArrayFixed).elem_type,
pos) {
return false
}
}
.map {
info := sym.info as ast.Map
if !c.ensure_generic_type_specify_type_names(info.key_type, pos) {
return false
}
if !c.ensure_generic_type_specify_type_names(info.value_type, pos) {
return false
}
}
.sum_type {
info := sym.info as ast.SumType
if info.generic_types.len > 0 && !typ.has_flag(.generic) && info.concrete_types.len == 0 {
c.error('`${sym.name}` type is generic sumtype, must specify the generic type names, e.g. ${sym.name}[T], ${sym.name}[int]',
pos)
return false
}
}
.struct_ {
info := sym.info as ast.Struct
if info.generic_types.len > 0 && !typ.has_flag(.generic) && info.concrete_types.len == 0 {
c.error('`${sym.name}` type is generic struct, must specify the generic type names, e.g. ${sym.name}[T], ${sym.name}[int]',
pos)
return false
}
}
.interface_ {
info := sym.info as ast.Interface
if info.generic_types.len > 0 && !typ.has_flag(.generic) && info.concrete_types.len == 0 {
c.error('`${sym.name}` type is generic interface, must specify the generic type names, e.g. ${sym.name}[T], ${sym.name}[int]',
pos)
return false
}
}
else {}
}
return true
}
fn (mut c Checker) ensure_type_exists(typ ast.Type, pos token.Pos) bool {
if typ == 0 {
c.error('unknown type', pos)
return false
}
sym := c.table.sym(typ)
if !c.is_builtin_mod && sym.kind == .struct_ && !sym.is_pub && sym.mod != c.mod {
c.error('struct `${sym.name}` was declared as private to module `${sym.mod}`, so it can not be used inside module `${c.mod}`',
pos)
return false
}
match sym.kind {
.placeholder {
// if sym.language == .c && sym.name == 'C.time_t' {
// TODO temporary hack until we can define C aliases
// return true
//}
// if sym.language == .v && !sym.name.starts_with('C.') {
// if sym.language in [.v, .c] {
if sym.language == .v {
c.error(util.new_suggestion(sym.name, c.table.known_type_names()).say('unknown type `${sym.name}`'),
pos)
return false
} else if sym.language == .c {
if !c.pref.translated && !c.file.is_translated {
c.warn(util.new_suggestion(sym.name, c.table.known_type_names()).say('unknown type `${sym.name}` (all virtual C types must be defined, this will be an error soon)'),
pos)
}
// dump(sym)
// for _, t in c.table.type_symbols {
// println(t.name)
//}
}
}
.int_literal, .float_literal {
// Separate error condition for `int_literal` and `float_literal` because `util.suggestion` may give different
// suggestions due to f32 comparison issue.
if !c.is_builtin_mod {
msg := if sym.kind == .int_literal {
'unknown type `${sym.name}`.\nDid you mean `int`?'
} else {
'unknown type `${sym.name}`.\nDid you mean `f64`?'
}
c.error(msg, pos)
return false
}
}
.function {
fn_info := sym.info as ast.FnType
if !c.ensure_type_exists(fn_info.func.return_type, fn_info.func.return_type_pos) {
return false
}
for param in fn_info.func.params {
if !c.ensure_type_exists(param.typ, param.type_pos) {
return false
}
}
}
.array {
if !c.ensure_type_exists((sym.info as ast.Array).elem_type, pos) {
return false
}
}
.array_fixed {
if !c.ensure_type_exists((sym.info as ast.ArrayFixed).elem_type, pos) {
return false
}
}
.map {
info := sym.info as ast.Map
if !c.ensure_type_exists(info.key_type, pos) {
return false
}
if !c.ensure_type_exists(info.value_type, pos) {
return false
}
}
.sum_type {
info := sym.info as ast.SumType
for concrete_typ in info.concrete_types {
if !c.ensure_type_exists(concrete_typ, pos) {
return false
}
}
}
else {}
}
return true
}
// return true if a violation of a shared variable access rule is detected
fn (mut c Checker) fail_if_unreadable(expr ast.Expr, typ ast.Type, what string) bool {
mut pos := token.Pos{}
match expr {
ast.Ident {
if typ.has_flag(.shared_f) {
if expr.name !in c.rlocked_names && expr.name !in c.locked_names {
action := if what == 'argument' { 'passed' } else { 'used' }
c.error('`${expr.name}` is `shared` and must be `rlock`ed or `lock`ed to be ${action} as non-mut ${what}',
expr.pos)
return true
}
}
return false
}
ast.SelectorExpr {
pos = expr.pos
if typ.has_flag(.shared_f) {
expr_name := '${expr.expr}.${expr.field_name}'
if expr_name !in c.rlocked_names && expr_name !in c.locked_names {
action := if what == 'argument' { 'passed' } else { 'used' }
c.error('`${expr_name}` is `shared` and must be `rlock`ed or `lock`ed to be ${action} as non-mut ${what}',
expr.pos)
return true
}
return false
} else {
if c.fail_if_unreadable(expr.expr, expr.expr_type, what) {
return true
}
}
}
ast.CallExpr {
pos = expr.pos
if expr.is_method {
if c.fail_if_unreadable(expr.left, expr.left_type, what) {
return true
}
}
return false
}
ast.LockExpr {
// TODO: check expressions inside the lock by appending to c.(r)locked_names
return false
}
ast.IndexExpr {
pos = expr.left.pos().extend(expr.pos)
if c.fail_if_unreadable(expr.left, expr.left_type, what) {
return true
}
}
ast.InfixExpr {
pos = expr.left.pos().extend(expr.pos)
if c.fail_if_unreadable(expr.left, expr.left_type, what) {
return true
}
if c.fail_if_unreadable(expr.right, expr.right_type, what) {
return true
}
}
else {
pos = expr.pos()
}
}
if typ.has_flag(.shared_f) {
c.error('you have to create a handle and `rlock` it to use a `shared` element as non-mut ${what}',
pos)
return true
}
return false
}
fn (mut c Checker) fail_if_stack_struct_action_outside_unsafe(mut ident ast.Ident, failed_action string) {
if mut ident.obj is ast.Var {
mut obj := unsafe { &ident.obj }
if c.fn_scope != unsafe { nil } {
obj = c.fn_scope.find_var(ident.obj.name) or { obj }
}
if obj.is_stack_obj && !c.inside_unsafe {
sym := c.table.sym(obj.typ.set_nr_muls(0))
if !sym.is_heap() && !c.pref.translated && !c.file.is_translated {
suggestion := if sym.kind == .struct_ {
'declaring `${sym.name}` as `[heap]`'
} else {
'wrapping the `${sym.name}` object in a `struct` declared as `[heap]`'
}
c.error('`${ident.name}` cannot be ${failed_action} outside `unsafe` blocks as it might refer to an object stored on stack. Consider ${suggestion}.',
ident.pos)
}
}
}
}
fn (mut c Checker) goto_label(node ast.GotoLabel) {
// Register a goto label
if node.name !in c.goto_labels {
c.goto_labels[node.name] = node
c.goto_labels[node.name].is_used = false
}
}
fn (mut c Checker) goto_stmt(node ast.GotoStmt) {
if c.inside_defer {
c.error('goto is not allowed in defer statements', node.pos)
}
if !c.inside_unsafe {
if !c.pref.translated && !c.file.is_translated {
c.warn('`goto` requires `unsafe` (consider using labelled break/continue)',
node.pos)
}
}
if c.table.cur_fn != unsafe { nil } && node.name !in c.table.cur_fn.label_names {
c.error('unknown label `${node.name}`', node.pos)
}
c.goto_labels[node.name].is_used = true // Register a label use
// TODO: check label doesn't bypass variable declarations
}
fn (mut c Checker) check_unused_labels() {
for name, label in c.goto_labels {
if !label.is_used {
// TODO show label's location
c.warn('label `${name}` defined and not used', label.pos)
c.goto_labels[name].is_used = true // so that this warning is not shown again
}
}
}
fn (mut c Checker) deprecate_old_isreftype_and_sizeof_of_a_guessed_type(is_guessed_type bool, typ ast.Type, pos token.Pos, label string) {
if is_guessed_type {
styp := c.table.type_to_str(typ)
c.note('`${label}(${styp})` is deprecated. Use `v fmt -w .` to convert it to `${label}[${styp}]()` instead.',
pos)
}
}
fn (c &Checker) check_import_sym_conflict(ident string) bool {
for import_sym in c.file.imports {
// Check if alias exists or not
if !import_sym.alias.is_blank() {
if import_sym.alias == ident {
return true
}
} else if import_sym.mod == ident {
return true
}
}
return false
}
================================================
FILE: src/tests/testdata/benchmarks/inlay_hints.vv
================================================
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module builtin
import strconv
/*
Note: A V string should be/is immutable from the point of view of
V user programs after it is first created. A V string is
also slightly larger than the equivalent C string because
the V string also has an integer length attached.
This tradeoff is made, since V strings are created just *once*,
but potentially used *many times* over their lifetime.
The V string implementation uses a struct, that has a .str field,
which points to a C style 0 terminated memory block. Although not
strictly necessary from the V point of view, that additional 0
is *very useful for C interoperability*.
The V string implementation also has an integer .len field,
containing the length of the .str field, excluding the
terminating 0 (just like the C's strlen(s) would do).
The 0 ending of .str, and the .len field, mean that in practice:
a) a V string s can be used very easily, wherever a
C string is needed, just by passing s.str,
without a need for further conversion/copying.
b) where strlen(s) is needed, you can just pass s.len,
without having to constantly recompute the length of s
*over and over again* like some C programs do. This is because
V strings are immutable and so their length does not change.
Ordinary V code *does not need* to be concerned with the
additional 0 in the .str field. The 0 *must* be put there by the
low level string creating functions inside this module.
Failing to do this will lead to programs that work most of the
time, when used with pure V functions, but fail in strange ways,
when used with modules using C functions (for example os and so on).
*/
pub struct string {
pub:
str &u8 = 0 // points to a C style 0 terminated string of bytes.
len int // the length of the .str field, excluding the ending 0 byte. It is always equal to strlen(.str).
// NB string.is_lit is an enumeration of the following:
// .is_lit == 0 => a fresh string, should be freed by autofree
// .is_lit == 1 => a literal string from .rodata, should NOT be freed
// .is_lit == -98761234 => already freed string, protects against double frees.
// ---------> ^^^^^^^^^ calling free on these is a bug.
// Any other value means that the string has been corrupted.
mut:
is_lit int
}
// runes returns an array of all the utf runes in the string `s`
// which is useful if you want random access to them
@[direct_array_access]
pub fn (s string) runes() []rune {
mut runes := []rune{cap: s.len}
for i := 0; i < s.len; i++ {
char_len := utf8_char_len(unsafe { s.str[i] })
if char_len > 1 {
end := if s.len - 1 >= i + char_len { i + char_len } else { s.len }
mut r := unsafe { s[i..end] }
runes << r.utf32_code()
i += char_len - 1
} else {
runes << unsafe { s.str[i] }
}
}
return runes
}
// cstring_to_vstring creates a new V string copy of the C style string,
// pointed by `s`. This function is most likely what you want to use when
// working with C style pointers to 0 terminated strings (i.e. `char*`).
// It is recommended to use it, unless you *do* understand the implications of
// tos/tos2/tos3/tos4/tos5 in terms of memory management and interactions with
// -autofree and `[manualfree]`.
// It will panic, if the pointer `s` is 0.
@[unsafe]
pub fn cstring_to_vstring(s &char) string {
return unsafe { tos2(&u8(s)) }.clone()
}
// tos_clone creates a new V string copy of the C style string, pointed by `s`.
// See also cstring_to_vstring (it is the same as it, the only difference is,
// that tos_clone expects `&byte`, while cstring_to_vstring expects &char).
// It will panic, if the pointer `s` is 0.
@[unsafe]
pub fn tos_clone(s &u8) string {
return unsafe { tos2(s) }.clone()
}
// tos creates a V string, given a C style pointer to a 0 terminated block.
// Note: the memory block pointed by s is *reused, not copied*!
// It will panic, when the pointer `s` is 0.
// See also `tos_clone`.
@[unsafe]
pub fn tos(s &u8, len int) string {
if s == 0 {
panic('tos(): nil string')
}
return string{
str: unsafe { s }
len: len
}
}
// tos2 creates a V string, given a C style pointer to a 0 terminated block.
// Note: the memory block pointed by s is *reused, not copied*!
// It will calculate the length first, thus it is more costly than `tos`.
// It will panic, when the pointer `s` is 0.
// It is the same as `tos3`, but for &byte pointers, avoiding callsite casts.
// See also `tos_clone`.
@[unsafe]
pub fn tos2(s &u8) string {
if s == 0 {
panic('tos2: nil string')
}
return string{
str: unsafe { s }
len: unsafe { vstrlen(s) }
}
}
// tos3 creates a V string, given a C style pointer to a 0 terminated block.
// Note: the memory block pointed by s is *reused, not copied*!
// It will calculate the length first, so it is more costly than tos.
// It will panic, when the pointer `s` is 0.
// It is the same as `tos2`, but for &char pointers, avoiding callsite casts.
// See also `tos_clone`.
@[unsafe]
pub fn tos3(s &char) string {
if s == 0 {
panic('tos3: nil string')
}
return string{
str: unsafe { &u8(s) }
len: unsafe { vstrlen_char(s) }
}
}
// tos4 creates a V string, given a C style pointer to a 0 terminated block.
// Note: the memory block pointed by s is *reused, not copied*!
// It will calculate the length first, so it is more costly than tos.
// It returns '', when given a 0 pointer `s`, it does NOT panic.
// It is the same as `tos5`, but for &byte pointers, avoiding callsite casts.
// See also `tos_clone`.
@[unsafe]
pub fn tos4(s &u8) string {
if s == 0 {
return ''
}
return string{
str: unsafe { s }
len: unsafe { vstrlen(s) }
}
}
// tos5 creates a V string, given a C style pointer to a 0 terminated block.
// Note: the memory block pointed by s is *reused, not copied*!
// It will calculate the length first, so it is more costly than tos.
// It returns '', when given a 0 pointer `s`, it does NOT panic.
// It is the same as `tos4`, but for &char pointers, avoiding callsite casts.
// See also `tos_clone`.
@[unsafe]
pub fn tos5(s &char) string {
if s == 0 {
return ''
}
return string{
str: unsafe { &u8(s) }
len: unsafe { vstrlen_char(s) }
}
}
// vstring converts a C style string to a V string.
// Note: the memory block pointed by `bp` is *reused, not copied*!
// Note: instead of `&u8(arr.data).vstring()`, do use `tos_clone(&u8(arr.data))`.
// Strings returned from this function will be normal V strings beside that,
// (i.e. they would be freed by V's -autofree mechanism, when they are no longer used).
// See also `tos_clone`.
@[unsafe]
pub fn (bp &u8) vstring() string {
return string{
str: unsafe { bp }
len: unsafe { vstrlen(bp) }
}
}
// vstring_with_len converts a C style 0 terminated string to a V string.
// Note: the memory block pointed by `bp` is *reused, not copied*!
// This method has lower overhead compared to .vstring(), since it
// does not need to calculate the length of the 0 terminated string.
// See also `tos_clone`.
@[unsafe]
pub fn (bp &u8) vstring_with_len(len int) string {
return string{
str: unsafe { bp }
len: len
is_lit: 0
}
}
// vstring converts a C style string to a V string.
// Note: the memory block pointed by `bp` is *reused, not copied*!
// Strings returned from this function will be normal V strings beside that,
// (i.e. they would be freed by V's -autofree mechanism, when they are
// no longer used).
// Note: instead of `&u8(a.data).vstring()`, use `tos_clone(&u8(a.data))`.
// See also `tos_clone`.
@[unsafe]
pub fn (cp &char) vstring() string {
return string{
str: &u8(cp)
len: unsafe { vstrlen_char(cp) }
is_lit: 0
}
}
// vstring_with_len converts a C style 0 terminated string to a V string.
// Note: the memory block pointed by `bp` is *reused, not copied*!
// This method has lower overhead compared to .vstring(), since it
// does not calculate the length of the 0 terminated string.
// See also `tos_clone`.
@[unsafe]
pub fn (cp &char) vstring_with_len(len int) string {
return string{
str: &u8(cp)
len: len
is_lit: 0
}
}
// vstring_literal converts a C style string to a V string.
// Note: the memory block pointed by `bp` is *reused, not copied*!
// NB2: unlike vstring, vstring_literal will mark the string
// as a literal, so it will not be freed by -autofree.
// This is suitable for readonly strings, C string literals etc,
// that can be read by the V program, but that should not be
// managed/freed by it, for example `os.args` is implemented using it.
// See also `tos_clone`.
@[unsafe]
pub fn (bp &u8) vstring_literal() string {
return string{
str: unsafe { bp }
len: unsafe { vstrlen(bp) }
is_lit: 1
}
}
// vstring_with_len converts a C style string to a V string.
// Note: the memory block pointed by `bp` is *reused, not copied*!
// This method has lower overhead compared to .vstring_literal(), since it
// does not need to calculate the length of the 0 terminated string.
// See also `tos_clone`.
@[unsafe]
pub fn (bp &u8) vstring_literal_with_len(len int) string {
return string{
str: unsafe { bp }
len: len
is_lit: 1
}
}
// vstring_literal converts a C style string char* pointer to a V string.
// Note: the memory block pointed by `bp` is *reused, not copied*!
// See also `byteptr.vstring_literal` for more details.
// See also `tos_clone`.
@[unsafe]
pub fn (cp &char) vstring_literal() string {
return string{
str: &u8(cp)
len: unsafe { vstrlen_char(cp) }
is_lit: 1
}
}
// vstring_literal_with_len converts a C style string char* pointer,
// to a V string.
// Note: the memory block pointed by `bp` is *reused, not copied*!
// This method has lower overhead compared to .vstring_literal(), since it
// does not need to calculate the length of the 0 terminated string.
// See also `tos_clone`.
@[unsafe]
pub fn (cp &char) vstring_literal_with_len(len int) string {
return string{
str: &u8(cp)
len: len
is_lit: 1
}
}
// len_utf8 returns the number of runes contained in the string `s`.
pub fn (s string) len_utf8() int {
mut l := 0
mut i := 0
for i < s.len {
l++
i += ((0xe5000000 >> ((unsafe { s.str[i] } >> 3) & 0x1e)) & 3) + 1
}
return l
}
// clone_static returns an independent copy of a given array.
// It should be used only in -autofree generated code.
@[inline]
fn (a string) clone_static() string {
return a.clone()
}
// option_clone_static returns an independent copy of a given array when lhs is an option type.
// It should be used only in -autofree generated code.
@[inline; markused]
fn (a string) option_clone_static() ?string {
return ?string(a.clone())
}
// clone returns a copy of the V string `a`.
pub fn (a string) clone() string {
if a.len <= 0 {
return ''
}
mut b := string{
str: unsafe { malloc_noscan(a.len + 1) }
len: a.len
}
unsafe {
vmemcpy(b.str, a.str, a.len)
b.str[a.len] = 0
}
return b
}
// replace_once replaces the first occurrence of `rep` with the string passed in `with`.
pub fn (s string) replace_once(rep string, with string) string {
idx := s.index_(rep)
if idx == -1 {
return s.clone()
}
return s.substr(0, idx) + with + s.substr(idx + rep.len, s.len)
}
// replace replaces all occurrences of `rep` with the string passed in `with`.
@[direct_array_access]
pub fn (s string) replace(rep string, with string) string {
if s.len == 0 || rep.len == 0 || rep.len > s.len {
return s.clone()
}
if !s.contains(rep) {
return s.clone()
}
// TODO PERF Allocating ints is expensive. Should be a stack array
// Get locations of all reps within this string
mut idxs := []int{cap: s.len / rep.len}
defer {
unsafe { idxs.free() }
}
mut idx := 0
for {
idx = s.index_after(rep, idx) or { break }
idxs << idx
idx += rep.len
}
// Dont change the string if there's nothing to replace
if idxs.len == 0 {
return s.clone()
}
// Now we know the number of replacements we need to do and we can calc the len of the new string
new_len := s.len + idxs.len * (with.len - rep.len)
mut b := unsafe { malloc_noscan(new_len + 1) } // add space for the null byte at the end
// Fill the new string
mut b_i := 0
mut s_idx := 0
for _, rep_pos in idxs {
for i in s_idx .. rep_pos { // copy everything up to piece being replaced
unsafe {
b[b_i] = s[i]
}
b_i++
}
s_idx = rep_pos + rep.len // move string index past replacement
for i in 0 .. with.len { // copy replacement piece
unsafe {
b[b_i] = with[i]
}
b_i++
}
}
if s_idx < s.len { // if any original after last replacement, copy it
for i in s_idx .. s.len {
unsafe {
b[b_i] = s[i]
}
b_i++
}
}
unsafe {
b[new_len] = 0
return tos(b, new_len)
}
}
struct RepIndex {
idx int
val_idx int
}
// replace_each replaces all occurrences of the string pairs given in `vals`.
// Example: assert 'ABCD'.replace_each(['B','C/','C','D','D','C']) == 'AC/DC'
@[direct_array_access]
pub fn (s string) replace_each(vals []string) string {
if s.len == 0 || vals.len == 0 {
return s.clone()
}
if vals.len % 2 != 0 {
eprintln('string.replace_each(): odd number of strings')
return s.clone()
}
// `rep` - string to replace
// `with` - string to replace with
// Remember positions of all rep strings, and calculate the length
// of the new string to do just one allocation.
mut new_len := s.len
mut idxs := []RepIndex{cap: 6}
mut idx := 0
s_ := s.clone()
for rep_i := 0; rep_i < vals.len; rep_i += 2 {
// vals: ['rep1, 'with1', 'rep2', 'with2']
rep := vals[rep_i]
with := vals[rep_i + 1]
for {
idx = s_.index_after(rep, idx) or { break }
// The string already found is set to `/del`, to avoid duplicate searches.
for i in 0 .. rep.len {
unsafe {
s_.str[idx + i] = 127
}
}
// We need to remember both the position in the string,
// and which rep/with pair it refers to.
idxs << RepIndex{
idx: idx
val_idx: rep_i
}
idx += rep.len
new_len += with.len - rep.len
}
}
// Dont change the string if there's nothing to replace
if idxs.len == 0 {
return s.clone()
}
idxs.sort(a.idx < b.idx)
mut b := unsafe { malloc_noscan(new_len + 1) } // add space for 0 terminator
// Fill the new string
mut idx_pos := 0
mut cur_idx := idxs[idx_pos]
mut b_i := 0
for i := 0; i < s.len; i++ {
if i == cur_idx.idx {
// Reached the location of rep, replace it with "with"
rep := vals[cur_idx.val_idx]
with := vals[cur_idx.val_idx + 1]
for j in 0 .. with.len {
unsafe {
b[b_i] = with[j]
}
b_i++
}
// Skip the length of rep, since we just replaced it with "with"
i += rep.len - 1
// Go to the next index
idx_pos++
if idx_pos < idxs.len {
cur_idx = idxs[idx_pos]
}
} else {
// Rep doesnt start here, just copy
unsafe {
b[b_i] = s.str[i]
}
b_i++
}
}
unsafe {
b[new_len] = 0
return tos(b, new_len)
}
}
// replace_char replaces all occurrences of the character `rep` multiple occurrences of the character passed in `with` with respect to `repeat`.
// Example: assert '\tHello!'.replace_char(`\t`,` `,8) == ' Hello!'
@[direct_array_access]
pub fn (s string) replace_char(rep u8, with u8, repeat int) string {
$if !no_bounds_checking {
if repeat <= 0 {
panic('string.replace_char(): tab length too short')
}
}
if s.len == 0 {
return s.clone()
}
// TODO Allocating ints is expensive. Should be a stack array
// - string.replace()
mut idxs := []int{cap: s.len}
defer {
unsafe { idxs.free() }
}
// No need to do a contains(), it already traverses the entire string
for i, ch in s {
if ch == rep { // Found char? Mark its location
idxs << i
}
}
if idxs.len == 0 {
return s.clone()
}
// Now we know the number of replacements we need to do and we can calc the len of the new string
new_len := s.len + idxs.len * (repeat - 1)
mut b := unsafe { malloc_noscan(new_len + 1) } // add space for the null byte at the end
// Fill the new string
mut b_i := 0
mut s_idx := 0
for rep_pos in idxs {
for i in s_idx .. rep_pos { // copy everything up to piece being replaced
unsafe {
b[b_i] = s[i]
}
b_i++
}
s_idx = rep_pos + 1 // move string index past replacement
for _ in 0 .. repeat { // copy replacement piece
unsafe {
b[b_i] = with
}
b_i++
}
}
if s_idx < s.len { // if any original after last replacement, copy it
for i in s_idx .. s.len {
unsafe {
b[b_i] = s[i]
}
b_i++
}
}
unsafe {
b[new_len] = 0
return tos(b, new_len)
}
}
// normalize_tabs replaces all tab characters with `tab_len` amount of spaces
// Example: assert '\t\tpop rax\t; pop rax'.normalize_tabs(2) == ' pop rax ; pop rax'
@[inline]
pub fn (s string) normalize_tabs(tab_len int) string {
return s.replace_char(`\t`, ` `, tab_len)
}
// bool returns `true` if the string equals the word "true" it will return `false` otherwise.
@[inline]
pub fn (s string) bool() bool {
return s == 'true' || s == 't' // TODO t for pg, remove
}
// int returns the value of the string as an integer `'1'.int() == 1`.
@[inline]
pub fn (s string) int() int {
return int(strconv.common_parse_int(s, 0, 32, false, false) or { 0 })
}
// i64 returns the value of the string as i64 `'1'.i64() == i64(1)`.
@[inline]
pub fn (s string) i64() i64 {
return strconv.common_parse_int(s, 0, 64, false, false) or { 0 }
}
// i8 returns the value of the string as i8 `'1'.i8() == i8(1)`.
@[inline]
pub fn (s string) i8() i8 {
return i8(strconv.common_parse_int(s, 0, 8, false, false) or { 0 })
}
// i16 returns the value of the string as i16 `'1'.i16() == i16(1)`.
@[inline]
pub fn (s string) i16() i16 {
return i16(strconv.common_parse_int(s, 0, 16, false, false) or { 0 })
}
// f32 returns the value of the string as f32 `'1.0'.f32() == f32(1)`.
@[inline]
pub fn (s string) f32() f32 {
return f32(strconv.atof64(s) or { 0 })
}
// f64 returns the value of the string as f64 `'1.0'.f64() == f64(1)`.
@[inline]
pub fn (s string) f64() f64 {
return strconv.atof64(s) or { 0 }
}
// u8 returns the value of the string as u8 `'1'.u8() == u8(1)`.
@[inline]
pub fn (s string) u8() u8 {
return u8(strconv.common_parse_uint(s, 0, 8, false, false) or { 0 })
}
// u8_array returns the value of the hex/bin string as u8 array.
// hex string example: `'0x11223344ee'.u8_array() == [u8(0x11),0x22,0x33,0x44,0xee]`.
// bin string example: `'0b1101_1101'.u8_array() == [u8(0xdd)]`.
// underscore in the string will be stripped.
pub fn (s string) u8_array() []u8 {
// strip underscore in the string
mut tmps := s.replace('_', '')
if tmps.len == 0 {
return []u8{}
}
tmps = tmps.to_lower()
if tmps.starts_with('0x') {
tmps = tmps[2..]
if tmps.len == 0 {
return []u8{}
}
// make sure every digit is valid hex digit
if !tmps.contains_only('0123456789abcdef') {
return []u8{}
}
// make sure tmps has even hex digits
if tmps.len % 2 == 1 {
tmps = '0' + tmps
}
mut ret := []u8{len: tmps.len / 2}
for i in 0 .. ret.len {
ret[i] = u8(tmps[2 * i..2 * i + 2].parse_uint(16, 8) or { 0 })
}
return ret
} else if tmps.starts_with('0b') {
tmps = tmps[2..]
if tmps.len == 0 {
return []u8{}
}
// make sure every digit is valid binary digit
if !tmps.contains_only('01') {
return []u8{}
}
// make sure tmps has multiple of 8 binary digits
if tmps.len % 8 != 0 {
tmps = '0'.repeat(8 - tmps.len % 8) + tmps
}
mut ret := []u8{len: tmps.len / 8}
for i in 0 .. ret.len {
ret[i] = u8(tmps[8 * i..8 * i + 8].parse_uint(2, 8) or { 0 })
}
return ret
}
return []u8{}
}
// u16 returns the value of the string as u16 `'1'.u16() == u16(1)`.
@[inline]
pub fn (s string) u16() u16 {
return u16(strconv.common_parse_uint(s, 0, 16, false, false) or { 0 })
}
// u32 returns the value of the string as u32 `'1'.u32() == u32(1)`.
@[inline]
pub fn (s string) u32() u32 {
return u32(strconv.common_parse_uint(s, 0, 32, false, false) or { 0 })
}
// u64 returns the value of the string as u64 `'1'.u64() == u64(1)`.
@[inline]
pub fn (s string) u64() u64 {
return strconv.common_parse_uint(s, 0, 64, false, false) or { 0 }
}
// parse_uint is like `parse_int` but for unsigned numbers
//
// This method directly exposes the `parse_uint` function from `strconv`
// as a method on `string`. For more advanced features,
// consider calling `strconv.common_parse_uint` directly.
@[inline]
pub fn (s string) parse_uint(_base int, _bit_size int) !u64 {
return strconv.parse_uint(s, _base, _bit_size)
}
// parse_int interprets a string s in the given base (0, 2 to 36) and
// bit size (0 to 64) and returns the corresponding value i.
//
// If the base argument is 0, the true base is implied by the string's
// prefix: 2 for "0b", 8 for "0" or "0o", 16 for "0x", and 10 otherwise.
// Also, for argument base 0 only, underscore characters are permitted
// as defined by the Go syntax for integer literals.
//
// The bitSize argument specifies the integer type
// that the result must fit into. Bit sizes 0, 8, 16, 32, and 64
// correspond to int, int8, int16, int32, and int64.
// If bitSize is below 0 or above 64, an error is returned.
//
// This method directly exposes the `parse_int` function from `strconv`
// as a method on `string`. For more advanced features,
// consider calling `strconv.common_parse_int` directly.
@[inline]
pub fn (s string) parse_int(_base int, _bit_size int) !i64 {
return strconv.parse_int(s, _base, _bit_size)
}
@[direct_array_access]
fn (s string) == (a string) bool {
if s.str == 0 {
// should never happen
panic('string.eq(): nil string')
}
if s.len != a.len {
return false
}
if s.len > 0 {
last_idx := s.len - 1
if s[last_idx] != a[last_idx] {
return false
}
}
unsafe {
return vmemcmp(s.str, a.str, a.len) == 0
}
}
// compare returns -1 if `s` < `a`, 0 if `s` == `a`, and 1 if `s` > `a`
@[direct_array_access]
pub fn (s string) compare(a string) int {
min_len := if s.len < a.len { s.len } else { a.len }
for i in 0 .. min_len {
if s[i] < a[i] {
return -1
}
if s[i] > a[i] {
return 1
}
}
if s.len < a.len {
return -1
}
if s.len > a.len {
return 1
}
return 0
}
@[direct_array_access]
fn (s string) < (a string) bool {
for i in 0 .. s.len {
if i >= a.len || s[i] > a[i] {
return false
} else if s[i] < a[i] {
return true
}
}
if s.len < a.len {
return true
}
return false
}
@[direct_array_access]
fn (s string) + (a string) string {
new_len := a.len + s.len
mut res := string{
str: unsafe { malloc_noscan(new_len + 1) }
len: new_len
}
unsafe {
vmemcpy(res.str, s.str, s.len)
vmemcpy(res.str + s.len, a.str, a.len)
}
unsafe {
res.str[new_len] = 0 // V strings are not null terminated, but just in case
}
return res
}
// split_any splits the string to an array by any of the `delim` chars.
// Example: "first row\nsecond row".split_any(" \n") == ['first', 'row', 'second', 'row']
// Split a string using the chars in the delimiter string as delimiters chars.
// If the delimiter string is empty then `.split()` is used.
@[direct_array_access]
pub fn (s string) split_any(delim string) []string {
mut res := []string{}
mut i := 0
// check empty source string
if s.len > 0 {
// if empty delimiter string using default split
if delim.len <= 0 {
return s.split('')
}
for index, ch in s {
for delim_ch in delim {
if ch == delim_ch {
res << s[i..index]
i = index + 1
break
}
}
}
if i < s.len {
res << s[i..]
}
}
return res
}
// rsplit_any splits the string to an array by any of the `delim` chars in reverse order.
// Example: "first row\nsecond row".rsplit_any(" \n") == ['row', 'second', 'row', 'first']
// Split a string using the chars in the delimiter string as delimiters chars.
// If the delimiter string is empty then `.rsplit()` is used.
@[direct_array_access]
pub fn (s string) rsplit_any(delim string) []string {
mut res := []string{}
mut i := s.len - 1
if s.len > 0 {
if delim.len <= 0 {
return s.rsplit('')
}
mut rbound := s.len
for i >= 0 {
for delim_ch in delim {
if s[i] == delim_ch {
res << s[i + 1..rbound]
rbound = i
break
}
}
i--
}
if rbound > 0 {
res << s[..rbound]
}
}
return res
}
// split splits the string into an array of strings at the given delimiter.
// Example: assert 'A B C'.split(' ') == ['A','B','C']
// If `delim` is empty the string is split by it's characters.
// Example: assert 'DEF'.split('') == ['D','E','F']
@[inline]
pub fn (s string) split(delim string) []string {
return s.split_nth(delim, 0)
}
// rsplit splits the string into an array of strings at the given delimiter, starting from the right.
// Example: assert 'A B C'.rsplit(' ') == ['C','B','A']
// If `delim` is empty the string is split by it's characters.
// Example: assert 'DEF'.rsplit('') == ['F','E','D']
@[inline]
pub fn (s string) rsplit(delim string) []string {
return s.rsplit_nth(delim, 0)
}
// split_once splits the string into a pair of strings at the given delimiter.
// Example:
// ```v
// path, ext := 'file.ts.dts'.split_once('.')?
// assert path == 'file'
// assert ext == 'ts.dts'
pub fn (s string) split_once(delim string) ?(string, string) {
result := s.split_nth(delim, 2)
if result.len != 2 {
return none
}
return result[0], result[1]
}
// rsplit_once splits the string into a pair of strings at the given delimiter, starting from the right.
// Example:
// ```v
// path, ext := 'file.ts.dts'.rsplit_once('.')?
// assert path == 'file.ts'
// assert ext == 'dts'
// ```
// NOTE: rsplit_once returns the string at the left side of the delimiter as first part of the pair.
pub fn (s string) rsplit_once(delim string) ?(string, string) {
result := s.rsplit_nth(delim, 2)
if result.len != 2 {
return none
}
return result[1], result[0]
}
// split_nth splits the string based on the passed `delim` substring.
// It returns the first Nth parts. When N=0, return all the splits.
// The last returned element has the remainder of the string, even if
// the remainder contains more `delim` substrings.
@[direct_array_access]
pub fn (s string) split_nth(delim string, nth int) []string {
mut res := []string{}
mut i := 0
match delim.len {
0 {
i = 1
for ch in s {
if nth > 0 && i >= nth {
res << s[i - 1..]
break
}
res << ch.ascii_str()
i++
}
return res
}
1 {
mut start := 0
delim_byte := delim[0]
for i < s.len {
if s[i] == delim_byte {
was_last := nth > 0 && res.len == nth - 1
if was_last {
break
}
val := s.substr(start, i)
res << val
start = i + delim.len
i = start
} else {
i++
}
}
// Then the remaining right part of the string
if nth < 1 || res.len < nth {
res << s[start..]
}
return res
}
else {
mut start := 0
// Take the left part for each delimiter occurrence
for i <= s.len {
is_delim := i + delim.len <= s.len && s.substr(i, i + delim.len) == delim
if is_delim {
was_last := nth > 0 && res.len == nth - 1
if was_last {
break
}
val := s.substr(start, i)
res << val
start = i + delim.len
i = start
} else {
i++
}
}
// Then the remaining right part of the string
if nth < 1 || res.len < nth {
res << s[start..]
}
return res
}
}
}
// rsplit_nth splits the string based on the passed `delim` substring in revese order.
// It returns the first Nth parts. When N=0, return all the splits.
// The last returned element has the remainder of the string, even if
// the remainder contains more `delim` substrings.
@[direct_array_access]
pub fn (s string) rsplit_nth(delim string, nth int) []string {
mut res := []string{}
mut i := s.len - 1
match delim.len {
0 {
for i >= 0 {
if nth > 0 && res.len == nth - 1 {
res << s[..i + 1]
break
}
res << s[i].ascii_str()
i--
}
return res
}
1 {
mut rbound := s.len
delim_byte := delim[0]
for i >= 0 {
if s[i] == delim_byte {
if nth > 0 && res.len == nth - 1 {
break
}
res << s[i + 1..rbound]
rbound = i
i--
} else {
i--
}
}
if nth < 1 || res.len < nth {
res << s[..rbound]
}
return res
}
else {
mut rbound := s.len
for i >= 0 {
is_delim := i - delim.len >= 0 && s[i - delim.len..i] == delim
if is_delim {
if nth > 0 && res.len == nth - 1 {
break
}
res << s[i..rbound]
rbound = i - delim.len
i -= delim.len
} else {
i--
}
}
if nth < 1 || res.len < nth {
res << s[..rbound]
}
return res
}
}
}
// split_into_lines splits the string by newline characters.
// newlines are stripped.
// `\r` (MacOS), `\n` (POSIX), and `\r\n` (WinOS) line endings are all supported (including mixed line endings).
// NOTE: algorithm is "greedy", consuming '\r\n' as a single line ending with higher priority than '\r' and '\n' as multiple endings
@[direct_array_access]
pub fn (s string) split_into_lines() []string {
mut res := []string{}
if s.len == 0 {
return res
}
cr := `\r`
lf := `\n`
mut line_start := 0
for i := 0; i < s.len; i++ {
if line_start <= i {
if s[i] == lf {
res << if line_start == i { '' } else { s[line_start..i] }
line_start = i + 1
} else if s[i] == cr {
res << if line_start == i { '' } else { s[line_start..i] }
if (i + 1) < s.len && s[i + 1] == lf {
line_start = i + 2
} else {
line_start = i + 1
}
}
}
}
if line_start < s.len {
res << s[line_start..]
}
return res
}
// substr returns the string between index positions `start` and `end`.
// Example: assert 'ABCD'.substr(1,3) == 'BC'
@[direct_array_access]
pub fn (s string) substr(start int, _end int) string {
end := if _end == max_int { s.len } else { _end } // max_int
$if !no_bounds_checking {
if start > end || start > s.len || end > s.len || start < 0 || end < 0 {
panic('substr(${start}, ${end}) out of bounds (len=${s.len})')
}
}
len := end - start
if len == s.len {
return s.clone()
}
mut res := string{
str: unsafe { malloc_noscan(len + 1) }
len: len
}
unsafe {
vmemcpy(res.str, s.str + start, len)
res.str[len] = 0
}
return res
}
// substr_unsafe works like substr(), but doesn't copy (allocate) the substring
@[direct_array_access]
pub fn (s string) substr_unsafe(start int, _end int) string {
end := if _end == 2147483647 { s.len } else { _end } // max_int
len := end - start
if len == s.len {
return s
}
return string{
str: unsafe { s.str + start }
len: len
}
}
// version of `substr()` that is used in `a[start..end] or {`
// return an error when the index is out of range
@[direct_array_access]
pub fn (s string) substr_with_check(start int, _end int) !string {
end := if _end == max_int { s.len } else { _end } // max_int
if start > end || start > s.len || end > s.len || start < 0 || end < 0 {
return error('substr(${start}, ${end}) out of bounds (len=${s.len})')
}
len := end - start
if len == s.len {
return s.clone()
}
mut res := string{
str: unsafe { malloc_noscan(len + 1) }
len: len
}
unsafe {
vmemcpy(res.str, s.str + start, len)
res.str[len] = 0
}
return res
}
// substr_ni returns the string between index positions `start` and `end` allowing negative indexes
// This function always return a valid string.
@[direct_array_access]
pub fn (s string) substr_ni(_start int, _end int) string {
mut start := _start
mut end := if _end == max_int { s.len } else { _end } // max_int
// borders math
if start < 0 {
start = s.len + start
if start < 0 {
start = 0
}
}
if end < 0 {
end = s.len + end
if end < 0 {
end = 0
}
}
if end >= s.len {
end = s.len
}
if start > s.len || end < start {
return ''
}
len := end - start
// string copy
mut res := string{
str: unsafe { malloc_noscan(len + 1) }
len: len
}
unsafe {
vmemcpy(res.str, s.str + start, len)
res.str[len] = 0
}
return res
}
// index returns the position of the first character of the input string.
// It will return `-1` if the input string can't be found.
@[direct_array_access]
fn (s string) index_(p string) int {
if p.len > s.len || p.len == 0 {
return -1
}
if p.len > 2 {
return s.index_kmp(p)
}
mut i := 0
for i < s.len {
mut j := 0
for j < p.len && unsafe { s.str[i + j] == p.str[j] } {
j++
}
if j == p.len {
return i
}
i++
}
return -1
}
// index returns the position of the first character of the first occurrence of the `needle` string in `s`.
// It will return `none` if the `needle` string can't be found in `s`.
pub fn (s string) index(p string) ?int {
idx := s.index_(p)
if idx == -1 {
return none
}
return idx
}
// index_last returns the position of the first character of the *last* occurrence of the `needle` string in `s`.
pub fn (s string) index_last(needle string) ?int {
idx := s.index_last_(needle)
if idx == -1 {
return none
}
return idx
}
// last_index returns the position of the first character of the *last* occurrence of the `needle` string in `s`.
@[deprecated: 'use `.index_last(needle string)` instead']
@[deprecated_after: '2023-12-18']
@[inline]
pub fn (s string) last_index(needle string) ?int {
return s.index_last(needle)
}
// index_kmp does KMP search.
@[direct_array_access; manualfree]
fn (s string) index_kmp(p string) int {
if p.len > s.len {
return -1
}
mut prefix := []int{len: p.len}
defer {
unsafe { prefix.free() }
}
mut j := 0
for i := 1; i < p.len; i++ {
for unsafe { p.str[j] != p.str[i] } && j > 0 {
j = prefix[j - 1]
}
if unsafe { p.str[j] == p.str[i] } {
j++
}
prefix[i] = j
}
j = 0
for i in 0 .. s.len {
for unsafe { p.str[j] != s.str[i] } && j > 0 {
j = prefix[j - 1]
}
if unsafe { p.str[j] == s.str[i] } {
j++
}
if j == p.len {
return i - p.len + 1
}
}
return -1
}
// index_any returns the position of any of the characters in the input string - if found.
pub fn (s string) index_any(chars string) int {
for i, ss in s {
for c in chars {
if c == ss {
return i
}
}
}
return -1
}
// index_last_ returns the position of the last occurrence of the given string `p` in `s`.
@[direct_array_access]
fn (s string) index_last_(p string) int {
if p.len > s.len || p.len == 0 {
return -1
}
mut i := s.len - p.len
for i >= 0 {
mut j := 0
for j < p.len && unsafe { s.str[i + j] == p.str[j] } {
j++
}
if j == p.len {
return i
}
i--
}
return -1
}
// index_after returns the position of the input string, starting search from `start` position.
@[direct_array_access]
pub fn (s string) index_after(p string, start int) int {
if p.len > s.len {
return -1
}
mut strt := start
if start < 0 {
strt = 0
}
if start >= s.len {
return -1
}
mut i := strt
for i < s.len {
mut j := 0
mut ii := i
for j < p.len && unsafe { s.str[ii] == p.str[j] } {
j++
ii++
}
if j == p.len {
return i
}
i++
}
return -1
}
// index_u8 returns the index of byte `c` if found in the string.
// index_u8 returns -1 if the byte can not be found.
@[direct_array_access]
pub fn (s string) index_u8(c u8) int {
for i, b in s {
if b == c {
return i
}
}
return -1
}
// index_u8_last returns the index of the *last* occurrence of the byte `c` (if found) in the string.
// It returns -1, if `c` is not found.
@[direct_array_access]
pub fn (s string) index_u8_last(c u8) int {
for i := s.len - 1; i >= 0; i-- {
if unsafe { s.str[i] == c } {
return i
}
}
return -1
}
// last_index_u8 returns the index of the last occurrence of byte `c` if found in the string.
// It returns -1, if the byte `c` is not found.
@[deprecated: 'use `.index_u8_last(c u8)` instead']
@[deprecated_after: '2023-12-18']
@[inline]
pub fn (s string) last_index_u8(c u8) int {
return s.index_u8_last(c)
}
// count returns the number of occurrences of `substr` in the string.
// count returns -1 if no `substr` could be found.
@[direct_array_access]
pub fn (s string) count(substr string) int {
if s.len == 0 || substr.len == 0 {
return 0
}
if substr.len > s.len {
return 0
}
mut n := 0
if substr.len == 1 {
target := substr[0]
for letter in s {
if letter == target {
n++
}
}
return n
}
mut i := 0
for {
i = s.index_after(substr, i) or { return n }
i += substr.len
n++
}
return 0 // TODO can never get here - v doesn't know that
}
// contains_u8 returns `true` if the string contains the byte value `x`.
// See also: [`string.index_u8`](#string.index_u8) , to get the index of the byte as well.
pub fn (s string) contains_u8(x u8) bool {
for c in s {
if x == c {
return true
}
}
return false
}
// contains returns `true` if the string contains `substr`.
// See also: [`string.index`](#string.index)
pub fn (s string) contains(substr string) bool {
if substr.len == 0 {
return true
}
if substr.len == 1 {
return s.contains_u8(unsafe { substr.str[0] })
}
return s.index_(substr) != -1
}
// contains_any returns `true` if the string contains any chars in `chars`.
pub fn (s string) contains_any(chars string) bool {
for c in chars {
if s.contains_u8(c) {
return true
}
}
return false
}
// contains_only returns `true`, if the string contains only the characters in `chars`.
pub fn (s string) contains_only(chars string) bool {
if chars.len == 0 {
return false
}
for ch in s {
mut res := 0
for i := 0; i < chars.len && res == 0; i++ {
res += int(ch == unsafe { chars.str[i] })
}
if res == 0 {
return false
}
}
return true
}
// contains_any_substr returns `true` if the string contains any of the strings in `substrs`.
pub fn (s string) contains_any_substr(substrs []string) bool {
if substrs.len == 0 {
return true
}
for sub in substrs {
if s.contains(sub) {
return true
}
}
return false
}
// starts_with returns `true` if the string starts with `p`.
@[direct_array_access]
pub fn (s string) starts_with(p string) bool {
if p.len > s.len {
return false
}
for i in 0 .. p.len {
if unsafe { s.str[i] != p.str[i] } {
return false
}
}
return true
}
// ends_with returns `true` if the string ends with `p`.
@[direct_array_access]
pub fn (s string) ends_with(p string) bool {
if p.len > s.len {
return false
}
for i in 0 .. p.len {
if unsafe { p.str[i] != s.str[s.len - p.len + i] } {
return false
}
}
return true
}
// to_lower returns the string in all lowercase characters.
// TODO only works with ASCII
@[direct_array_access]
pub fn (s string) to_lower() string {
unsafe {
mut b := malloc_noscan(s.len + 1)
for i in 0 .. s.len {
if s.str[i] >= `A` && s.str[i] <= `Z` {
b[i] = s.str[i] + 32
} else {
b[i] = s.str[i]
}
}
b[s.len] = 0
return tos(b, s.len)
}
}
// is_lower returns `true` if all characters in the string are lowercase.
// Example: assert 'hello developer'.is_lower() == true
@[direct_array_access]
pub fn (s string) is_lower() bool {
for i in 0 .. s.len {
if s[i] >= `A` && s[i] <= `Z` {
return false
}
}
return true
}
// to_upper returns the string in all uppercase characters.
// Example: assert 'Hello V'.to_upper() == 'HELLO V'
@[direct_array_access]
pub fn (s string) to_upper() string {
unsafe {
mut b := malloc_noscan(s.len + 1)
for i in 0 .. s.len {
if s.str[i] >= `a` && s.str[i] <= `z` {
b[i] = s.str[i] - 32
} else {
b[i] = s.str[i]
}
}
b[s.len] = 0
return tos(b, s.len)
}
}
// is_upper returns `true` if all characters in the string are uppercase.
// See also: [`byte.is_capital`](#byte.is_capital)
// Example: assert 'HELLO V'.is_upper() == true
@[direct_array_access]
pub fn (s string) is_upper() bool {
for i in 0 .. s.len {
if s[i] >= `a` && s[i] <= `z` {
return false
}
}
return true
}
// capitalize returns the string with the first character capitalized.
// Example: assert 'hello'.capitalize() == 'Hello'
@[direct_array_access]
pub fn (s string) capitalize() string {
if s.len == 0 {
return ''
}
s0 := s[0]
letter := s0.ascii_str()
uletter := letter.to_upper()
if s.len == 1 {
return uletter
}
srest := s[1..]
res := uletter + srest
return res
}
// uncapitalize returns the string with the first character uncapitalized.
// Example: assert 'Hello, Bob!'.uncapitalize() == 'hello, Bob!'
@[direct_array_access]
pub fn (s string) uncapitalize() string {
if s.len == 0 {
return ''
}
s0 := s[0]
letter := s0.ascii_str()
uletter := letter.to_lower()
if s.len == 1 {
return uletter
}
srest := s[1..]
res := uletter + srest
return res
}
// is_capital returns `true`, if the first character in the string `s`,
// is a capital letter, and the rest are NOT.
// Example: assert 'Hello'.is_capital() == true
// Example: assert 'HelloWorld'.is_capital() == false
@[direct_array_access]
pub fn (s string) is_capital() bool {
if s.len == 0 || !(s[0] >= `A` && s[0] <= `Z`) {
return false
}
for i in 1 .. s.len {
if s[i] >= `A` && s[i] <= `Z` {
return false
}
}
return true
}
// starts_with_capital returns `true`, if the first character in the string `s`,
// is a capital letter, even if the rest are not.
// Example: assert 'Hello'.starts_with_capital() == true
// Example: assert 'Hello. World.'.starts_with_capital() == true
@[direct_array_access]
pub fn (s string) starts_with_capital() bool {
if s.len == 0 || !(s[0] >= `A` && s[0] <= `Z`) {
return false
}
return true
}
// title returns the string with each word capitalized.
// Example: assert 'hello v developer'.title() == 'Hello V Developer'
pub fn (s string) title() string {
words := s.split(' ')
mut tit := []string{}
for word in words {
tit << word.capitalize()
}
title := tit.join(' ')
return title
}
// is_title returns true if all words of the string are capitalized.
// Example: assert 'Hello V Developer'.is_title() == true
pub fn (s string) is_title() bool {
words := s.split(' ')
for word in words {
if !word.is_capital() {
return false
}
}
return true
}
// find_between returns the string found between `start` string and `end` string.
// Example: assert 'hey [man] how you doin'.find_between('[', ']') == 'man'
pub fn (s string) find_between(start string, end string) string {
start_pos := s.index_(start)
if start_pos == -1 {
return ''
}
// First get everything to the right of 'start'
val := s[start_pos + start.len..]
end_pos := val.index_(end)
if end_pos == -1 {
return val
}
return val[..end_pos]
}
// trim_space strips any of ` `, `\n`, `\t`, `\v`, `\f`, `\r` from the start and end of the string.
// Example: assert ' Hello V '.trim_space() == 'Hello V'
@[inline]
pub fn (s string) trim_space() string {
return s.trim(' \n\t\v\f\r')
}
// trim strips any of the characters given in `cutset` from the start and end of the string.
// Example: assert ' ffHello V ffff'.trim(' f') == 'Hello V'
pub fn (s string) trim(cutset string) string {
if s.len < 1 || cutset.len < 1 {
return s.clone()
}
left, right := s.trim_indexes(cutset)
return s.substr(left, right)
}
// trim_indexes gets the new start and end indices of a string when any of the characters given in `cutset` were stripped from the start and end of the string. Should be used as an input to `substr()`. If the string contains only the characters in `cutset`, both values returned are zero.
// Example: left, right := '-hi-'.trim_indexes('-')
@[direct_array_access]
pub fn (s string) trim_indexes(cutset string) (int, int) {
mut pos_left := 0
mut pos_right := s.len - 1
mut cs_match := true
for pos_left <= s.len && pos_right >= -1 && cs_match {
cs_match = false
for cs in cutset {
if s[pos_left] == cs {
pos_left++
cs_match = true
break
}
}
for cs in cutset {
if s[pos_right] == cs {
pos_right--
cs_match = true
break
}
}
if pos_left > pos_right {
return 0, 0
}
}
return pos_left, pos_right + 1
}
// trim_left strips any of the characters given in `cutset` from the left of the string.
// Example: assert 'd Hello V developer'.trim_left(' d') == 'Hello V developer'
@[direct_array_access]
pub fn (s string) trim_left(cutset string) string {
if s.len < 1 || cutset.len < 1 {
return s.clone()
}
mut pos := 0
for pos < s.len {
mut found := false
for cs in cutset {
if s[pos] == cs {
found = true
break
}
}
if !found {
break
}
pos++
}
return s[pos..]
}
// trim_right strips any of the characters given in `cutset` from the right of the string.
// Example: assert ' Hello V d'.trim_right(' d') == ' Hello V'
@[direct_array_access]
pub fn (s string) trim_right(cutset string) string {
if s.len < 1 || cutset.len < 1 {
return s.clone()
}
mut pos := s.len - 1
for pos >= 0 {
mut found := false
for cs in cutset {
if s[pos] == cs {
found = true
}
}
if !found {
break
}
pos--
}
if pos < 0 {
return ''
}
return s[..pos + 1]
}
// trim_string_left strips `str` from the start of the string.
// Example: assert 'WorldHello V'.trim_string_left('World') == 'Hello V'
pub fn (s string) trim_string_left(str string) string {
if s.starts_with(str) {
return s[str.len..]
}
return s.clone()
}
// trim_string_right strips `str` from the end of the string.
// Example: assert 'Hello VWorld'.trim_string_right('World') == 'Hello V'
pub fn (s string) trim_string_right(str string) string {
if s.ends_with(str) {
return s[..s.len - str.len]
}
return s.clone()
}
// compare_strings returns `-1` if `a < b`, `1` if `a > b` else `0`.
pub fn compare_strings(a &string, b &string) int {
if a < b {
return -1
}
if a > b {
return 1
}
return 0
}
// compare_strings_by_len returns `-1` if `a.len < b.len`, `1` if `a.len > b.len` else `0`.
fn compare_strings_by_len(a &string, b &string) int {
if a.len < b.len {
return -1
}
if a.len > b.len {
return 1
}
return 0
}
// compare_lower_strings returns the same as compare_strings but converts `a` and `b` to lower case before comparing.
fn compare_lower_strings(a &string, b &string) int {
aa := a.to_lower()
bb := b.to_lower()
return compare_strings(&aa, &bb)
}
// sort_ignore_case sorts the string array using case insensitive comparing.
@[inline]
pub fn (mut s []string) sort_ignore_case() {
s.sort_with_compare(compare_lower_strings)
}
// sort_by_len sorts the string array by each string's `.len` length.
@[inline]
pub fn (mut s []string) sort_by_len() {
s.sort_with_compare(compare_strings_by_len)
}
// str returns a copy of the string
@[inline]
pub fn (s string) str() string {
return s.clone()
}
// at returns the byte at index `idx`.
// Example: assert 'ABC'.at(1) == u8(`B`)
fn (s string) at(idx int) u8 {
$if !no_bounds_checking {
if idx < 0 || idx >= s.len {
panic('string index out of range: ${idx} / ${s.len}')
}
}
return unsafe { s.str[idx] }
}
// version of `at()` that is used in `a[i] or {`
// return an error when the index is out of range
fn (s string) at_with_check(idx int) ?u8 {
if idx < 0 || idx >= s.len {
return none
}
unsafe {
return s.str[idx]
}
}
// Check if a string is an octal value. Returns 'true' if it is, or 'false' if it is not
@[direct_array_access]
pub fn (str string) is_oct() bool {
mut i := 0
if str.len == 0 {
return false
}
if str[i] == `0` {
i++
} else if str[i] == `-` || str[i] == `+` {
i++
if str[i] == `0` {
i++
} else {
return false
}
} else {
return false
}
if str[i] == `o` {
i++
} else {
return false
}
if i == str.len {
return false
}
for i < str.len {
if str[i] < `0` || str[i] > `7` {
return false
}
i++
}
return true
}
// is_bin returns `true` if the string is a binary value.
@[direct_array_access]
pub fn (str string) is_bin() bool {
mut i := 0
if str.len == 0 {
return false
}
if str[i] == `0` {
i++
} else if str[i] == `-` || str[i] == `+` {
i++
if str[i] == `0` {
i++
} else {
return false
}
} else {
return false
}
if str[i] == `b` {
i++
} else {
return false
}
if i == str.len {
return false
}
for i < str.len {
if str[i] < `0` || str[i] > `1` {
return false
}
i++
}
return true
}
// is_hex returns 'true' if the string is a hexadecimal value.
@[direct_array_access]
pub fn (str string) is_hex() bool {
mut i := 0
if str.len == 0 {
return false
}
if str[i] == `0` {
i++
} else if str[i] == `-` || str[i] == `+` {
i++
if str[i] == `0` {
i++
} else {
return false
}
} else {
return false
}
if str[i] == `x` {
i++
} else {
return false
}
if i == str.len {
return false
}
for i < str.len {
if (str[i] < `0` || str[i] > `9`) && ((str[i] < `a` || str[i] > `f`)
&& (str[i] < `A` || str[i] > `F`)) {
return false
}
i++
}
return true
}
// Check if a string is an integer value. Returns 'true' if it is, or 'false' if it is not
@[direct_array_access]
pub fn (str string) is_int() bool {
mut i := 0
if str.len == 0 {
return false
}
if (str[i] != `-` && str[i] != `+`) && (!str[i].is_digit()) {
return false
} else {
i++
}
if i == str.len && (!str[i - 1].is_digit()) {
return false
}
for i < str.len {
if str[i] < `0` || str[i] > `9` {
return false
}
i++
}
return true
}
// is_space returns `true` if the byte is a white space character.
// The following list is considered white space characters: ` `, `\t`, `\n`, `\v`, `\f`, `\r`, 0x85, 0xa0
// Example: assert u8(` `).is_space() == true
@[inline]
pub fn (c u8) is_space() bool {
// 0x85 is NEXT LINE (NEL)
// 0xa0 is NO-BREAK SPACE
return c == 32 || (c > 8 && c < 14) || c == 0x85 || c == 0xa0
}
// is_digit returns `true` if the byte is in range 0-9 and `false` otherwise.
// Example: assert u8(`9`).is_digit() == true
@[inline]
pub fn (c u8) is_digit() bool {
return c >= `0` && c <= `9`
}
// is_hex_digit returns `true` if the byte is either in range 0-9, a-f or A-F and `false` otherwise.
// Example: assert u8(`F`).is_hex_digit() == true
@[inline]
pub fn (c u8) is_hex_digit() bool {
return (c >= `0` && c <= `9`) || (c >= `a` && c <= `f`) || (c >= `A` && c <= `F`)
}
// is_oct_digit returns `true` if the byte is in range 0-7 and `false` otherwise.
// Example: assert u8(`7`).is_oct_digit() == true
@[inline]
pub fn (c u8) is_oct_digit() bool {
return c >= `0` && c <= `7`
}
// is_bin_digit returns `true` if the byte is a binary digit (0 or 1) and `false` otherwise.
// Example: assert u8(`0`).is_bin_digit() == true
@[inline]
pub fn (c u8) is_bin_digit() bool {
return c == `0` || c == `1`
}
// is_letter returns `true` if the byte is in range a-z or A-Z and `false` otherwise.
// Example: assert u8(`V`).is_letter() == true
@[inline]
pub fn (c u8) is_letter() bool {
return (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`)
}
// is_alnum returns `true` if the byte is in range a-z, A-Z, 0-9 and `false` otherwise.
// Example: assert u8(`V`).is_alnum() == true
@[inline]
pub fn (c u8) is_alnum() bool {
return (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || (c >= `0` && c <= `9`)
}
// free allows for manually freeing the memory occupied by the string
@[manualfree; unsafe]
pub fn (s &string) free() {
$if prealloc {
return
}
if s.is_lit == -98761234 {
double_free_msg := unsafe { &u8(c'double string.free() detected\n') }
double_free_msg_len := unsafe { vstrlen(double_free_msg) }
$if freestanding {
bare_eprint(double_free_msg, u64(double_free_msg_len))
} $else {
_write_buf_to_fd(1, double_free_msg, double_free_msg_len)
}
return
}
if s.is_lit == 1 || s.str == 0 {
return
}
unsafe {
// C.printf(c's: %x %s\n', s.str, s.str)
free(s.str)
s.str = nil
}
s.is_lit = -98761234
}
// before returns the contents before `sub` in the string.
// If the substring is not found, it returns the full input string.
// Example: assert '23:34:45.234'.before('.') == '23:34:45'
// Example: assert 'abcd'.before('.') == 'abcd'
// TODO: deprecate and remove either .before or .all_before
pub fn (s string) before(sub string) string {
pos := s.index_(sub)
if pos == -1 {
return s.clone()
}
return s[..pos]
}
// all_before returns the contents before `sub` in the string.
// If the substring is not found, it returns the full input string.
// Example: assert '23:34:45.234'.all_before('.') == '23:34:45'
// Example: assert 'abcd'.all_before('.') == 'abcd'
pub fn (s string) all_before(sub string) string {
// TODO remove dup method
pos := s.index_(sub)
if pos == -1 {
return s.clone()
}
return s[..pos]
}
// all_before_last returns the contents before the last occurrence of `sub` in the string.
// If the substring is not found, it returns the full input string.
// Example: assert '23:34:45.234'.all_before_last(':') == '23:34'
// Example: assert 'abcd'.all_before_last('.') == 'abcd'
pub fn (s string) all_before_last(sub string) string {
pos := s.index_last_(sub)
if pos == -1 {
return s.clone()
}
return s[..pos]
}
// all_after returns the contents after `sub` in the string.
// If the substring is not found, it returns the full input string.
// Example: assert '23:34:45.234'.all_after('.') == '234'
// Example: assert 'abcd'.all_after('z') == 'abcd'
pub fn (s string) all_after(sub string) string {
pos := s.index_(sub)
if pos == -1 {
return s.clone()
}
return s[pos + sub.len..]
}
// all_after_last returns the contents after the last occurrence of `sub` in the string.
// If the substring is not found, it returns the full input string.
// Example: assert '23:34:45.234'.all_after_last(':') == '45.234'
// Example: assert 'abcd'.all_after_last('z') == 'abcd'
pub fn (s string) all_after_last(sub string) string {
pos := s.index_last_(sub)
if pos == -1 {
return s.clone()
}
return s[pos + sub.len..]
}
// all_after_first returns the contents after the first occurrence of `sub` in the string.
// If the substring is not found, it returns the full input string.
// Example: assert '23:34:45.234'.all_after_first(':') == '34:45.234'
// Example: assert 'abcd'.all_after_first('z') == 'abcd'
pub fn (s string) all_after_first(sub string) string {
pos := s.index_(sub)
if pos == -1 {
return s.clone()
}
return s[pos + sub.len..]
}
// after returns the contents after the last occurrence of `sub` in the string.
// If the substring is not found, it returns the full input string.
// Example: assert '23:34:45.234'.after(':') == '45.234'
// Example: assert 'abcd'.after('z') == 'abcd'
// TODO: deprecate either .all_after_last or .after
@[inline]
pub fn (s string) after(sub string) string {
return s.all_after_last(sub)
}
// after_char returns the contents after the first occurrence of `sub` character in the string.
// If the substring is not found, it returns the full input string.
// Example: assert '23:34:45.234'.after_char(`:`) == '34:45.234'
// Example: assert 'abcd'.after_char(`:`) == 'abcd'
pub fn (s string) after_char(sub u8) string {
mut pos := -1
for i, c in s {
if c == sub {
pos = i
break
}
}
if pos == -1 {
return s.clone()
}
return s[pos + 1..]
}
// join joins a string array into a string using `sep` separator.
// Example: assert ['Hello','V'].join(' ') == 'Hello V'
pub fn (a []string) join(sep string) string {
if a.len == 0 {
return ''
}
mut len := 0
for val in a {
len += val.len + sep.len
}
len -= sep.len
// Allocate enough memory
mut res := string{
str: unsafe { malloc_noscan(len + 1) }
len: len
}
mut idx := 0
for i, val in a {
unsafe {
vmemcpy(voidptr(res.str + idx), val.str, val.len)
idx += val.len
}
// Add sep if it's not last
if i != a.len - 1 {
unsafe {
vmemcpy(voidptr(res.str + idx), sep.str, sep.len)
idx += sep.len
}
}
}
unsafe {
res.str[res.len] = 0
}
return res
}
// join joins a string array into a string using a `\n` newline delimiter.
@[inline]
pub fn (s []string) join_lines() string {
return s.join('\n')
}
// reverse returns a reversed string.
// Example: assert 'Hello V'.reverse() == 'V olleH'
@[direct_array_access]
pub fn (s string) reverse() string {
if s.len == 0 || s.len == 1 {
return s.clone()
}
mut res := string{
str: unsafe { malloc_noscan(s.len + 1) }
len: s.len
}
for i := s.len - 1; i >= 0; i-- {
unsafe {
res.str[s.len - i - 1] = s[i]
}
}
unsafe {
res.str[res.len] = 0
}
return res
}
// limit returns a portion of the string, starting at `0` and extending for a given number of characters afterward.
// 'hello'.limit(2) => 'he'
// 'hi'.limit(10) => 'hi'
pub fn (s string) limit(max int) string {
u := s.runes()
if u.len <= max {
return s.clone()
}
return u[0..max].string()
}
// hash returns an integer hash of the string.
pub fn (s string) hash() int {
mut h := u32(0)
if h == 0 && s.len > 0 {
for c in s {
h = h * 31 + u32(c)
}
}
return int(h)
}
// bytes returns the string converted to a byte array.
pub fn (s string) bytes() []u8 {
if s.len == 0 {
return []
}
mut buf := []u8{len: s.len}
unsafe { vmemcpy(buf.data, s.str, s.len) }
return buf
}
// repeat returns a new string with `count` number of copies of the string it was called on.
@[direct_array_access]
pub fn (s string) repeat(count int) string {
if count < 0 {
panic('string.repeat: count is negative: ${count}')
} else if count == 0 {
return ''
} else if count == 1 {
return s.clone()
}
mut ret := unsafe { malloc_noscan(s.len * count + 1) }
for i in 0 .. count {
unsafe {
vmemcpy(ret + i * s.len, s.str, s.len)
}
}
new_len := s.len * count
unsafe {
ret[new_len] = 0
}
return unsafe { ret.vstring_with_len(new_len) }
}
// fields returns a string array of the string split by `\t` and ` `
// Example: assert '\t\tv = v'.fields() == ['v', '=', 'v']
// Example: assert ' sss ssss'.fields() == ['sss', 'ssss']
pub fn (s string) fields() []string {
mut res := []string{}
mut word_start := 0
mut word_len := 0
mut is_in_word := false
mut is_space := false
for i, c in s {
is_space = c in [32, 9, 10]
if !is_space {
word_len++
}
if !is_in_word && !is_space {
word_start = i
is_in_word = true
continue
}
if is_space && is_in_word {
res << s[word_start..word_start + word_len]
is_in_word = false
word_len = 0
word_start = 0
continue
}
}
if is_in_word && word_len > 0 {
// collect the remainder word at the end
res << s[word_start..s.len]
}
return res
}
// strip_margin allows multi-line strings to be formatted in a way that removes white-space
// before a delimiter. By default `|` is used.
// Note: the delimiter has to be a byte at this time. That means surrounding
// the value in ``.
//
// See also: string.trim_indent()
//
// Example:
// ```v
// st := 'Hello there,
// | this is a string,
// | Everything before the first | is removed'.strip_margin()
//
// assert st == 'Hello there,
// this is a string,
// Everything before the first | is removed'
// ```
@[inline]
pub fn (s string) strip_margin() string {
return s.strip_margin_custom(`|`)
}
// strip_margin_custom does the same as `strip_margin` but will use `del` as delimiter instead of `|`
@[direct_array_access]
pub fn (s string) strip_margin_custom(del u8) string {
mut sep := del
if sep.is_space() {
println('Warning: `strip_margin` cannot use white-space as a delimiter')
println(' Defaulting to `|`')
sep = `|`
}
// don't know how much space the resulting string will be, but the max it
// can be is this big
mut ret := unsafe { malloc_noscan(s.len + 1) }
mut count := 0
for i := 0; i < s.len; i++ {
if s[i] in [10, 13] {
unsafe {
ret[count] = s[i]
}
count++
// CRLF
if s[i] == 13 && i < s.len - 1 && s[i + 1] == 10 {
unsafe {
ret[count] = s[i + 1]
}
count++
i++
}
for s[i] != sep {
i++
if i >= s.len {
break
}
}
} else {
unsafe {
ret[count] = s[i]
}
count++
}
}
unsafe {
ret[count] = 0
return ret.vstring_with_len(count)
}
}
// trim_indent detects a common minimal indent of all the input lines,
// removes it from every line and also removes the first and the last
// lines if they are blank (notice difference blank vs empty).
//
// Note that blank lines do not affect the detected indent level.
//
// In case if there are non-blank lines with no leading whitespace characters
// (no indent at all) then the common indent is 0, and therefore this function
// doesn't change the indentation.
//
// Example:
// ```v
// st := '
// Hello there,
// this is a string,
// all the leading indents are removed
// and also the first and the last lines if they are blank
// '.trim_indent()
//
// assert st == 'Hello there,
// this is a string,
// all the leading indents are removed
// and also the first and the last lines if they are blank'
// ```
pub fn (s string) trim_indent() string {
mut lines := s.split_into_lines()
lines_indents := lines
.filter(!it.is_blank())
.map(it.indent_width())
mut min_common_indent := int(max_int) // max int
for line_indent in lines_indents {
if line_indent < min_common_indent {
min_common_indent = line_indent
}
}
// trim first line if it's blank
if lines.len > 0 && lines.first().is_blank() {
lines = unsafe { lines[1..] }
}
// trim last line if it's blank
if lines.len > 0 && lines.last().is_blank() {
lines = unsafe { lines[..lines.len - 1] }
}
mut trimmed_lines := []string{cap: lines.len}
for line in lines {
if line.is_blank() {
trimmed_lines << line
continue
}
trimmed_lines << line[min_common_indent..]
}
return trimmed_lines.join('\n')
}
// indent_width returns the number of spaces or tabs at the beginning of the string.
// Example: assert ' v'.indent_width() == 2
// Example: assert '\t\tv'.indent_width() == 2
pub fn (s string) indent_width() int {
for i, c in s {
if !c.is_space() {
return i
}
}
return 0
}
// is_blank returns true if the string is empty or contains only white-space.
// Example: assert ' '.is_blank()
// Example: assert '\t'.is_blank()
// Example: assert 'v'.is_blank() == false
pub fn (s string) is_blank() bool {
if s.len == 0 {
return true
}
for c in s {
if !c.is_space() {
return false
}
}
return true
}
// match_glob matches the string, with a Unix shell-style wildcard pattern.
// Note: wildcard patterns are NOT the same as regular expressions.
// They are much simpler, and do not allow backtracking, captures, etc.
// The special characters used in shell-style wildcards are:
// `*` - matches everything
// `?` - matches any single character
// `[seq]` - matches any of the characters in the sequence
// `[^seq]` - matches any character that is NOT in the sequence
// Any other character in `pattern`, is matched 1:1 to the corresponding
// character in `name`, including / and \.
// You can wrap the meta-characters in brackets too, i.e. `[?]` matches `?`
// in the string, and `[*]` matches `*` in the string.
// Example: assert 'ABCD'.match_glob('AB*')
// Example: assert 'ABCD'.match_glob('*D')
// Example: assert 'ABCD'.match_glob('*B*')
// Example: assert !'ABCD'.match_glob('AB')
@[direct_array_access]
pub fn (name string) match_glob(pattern string) bool {
// Initial port based on https://research.swtch.com/glob.go
// See also https://research.swtch.com/glob
mut px := 0
mut nx := 0
mut next_px := 0
mut next_nx := 0
plen := pattern.len
nlen := name.len
for px < plen || nx < nlen {
if px < plen {
c := pattern[px]
match c {
`?` {
// single-character wildcard
if nx < nlen {
px++
nx++
continue
}
}
`*` {
// zero-or-more-character wildcard
// Try to match at nx.
// If that doesn't work out, restart at nx+1 next.
next_px = px
next_nx = nx + 1
px++
continue
}
`[` {
if nx < nlen {
wanted_c := name[nx]
mut bstart := px
mut is_inverted := false
mut inner_match := false
mut inner_idx := bstart + 1
mut inner_c := 0
if inner_idx < plen {
inner_c = pattern[inner_idx]
if inner_c == `^` {
is_inverted = true
inner_idx++
}
}
for ; inner_idx < plen; inner_idx++ {
inner_c = pattern[inner_idx]
if inner_c == `]` {
break
}
if inner_c == wanted_c {
inner_match = true
for px < plen && pattern[px] != `]` {
px++
}
break
}
}
if is_inverted {
if inner_match {
return false
} else {
px = inner_idx
}
}
}
px++
nx++
continue
}
else {
// an ordinary character
if nx < nlen && name[nx] == c {
px++
nx++
continue
}
}
}
}
if 0 < next_nx && next_nx <= nlen {
// A mismatch, try restarting:
px = next_px
nx = next_nx
continue
}
return false
}
// Matched all of `pattern` to all of `name`
return true
}
// is_ascii returns true if all characters belong to the US-ASCII set ([` `..`~`])
@[inline]
pub fn (s string) is_ascii() bool {
return !s.bytes().any(it < u8(` `) || it > u8(`~`))
}
================================================
FILE: src/tests/testdata/documentation/rendered.vv
================================================
module documentation
// has checks if the enum value has the passed flag.
//
// **bold**
// *italic*
// `code`
// [link](https://github.com)
// ---
//
// # Heading 1
// ## Heading 2
// ### Heading 3
//
// line break.
// line break!
// line break?
//
// Example:
// ```
// [flag]
// enum Permissions {
// read // = 0b0001
// write // = 0b0010
// other // = 0b0100
// }
//
// fn main() {
// p := Permissions.read
// assert p.has(.read) // test if p has read flag
// assert p.has(.read | .other) // test if *at least one* of the flags is set
// }
// ```
//
// Example: println('inline example')
fn f/*caret*/oo() {
}
================================================
FILE: src/tests/testdata/documentation/rendered.vv.md
================================================
Module: **documentation**
```v
fn foo()
```
has checks if the enum value has the passed flag.
**bold** *italic* `code` [link](https://github.com)
# Heading 1
## Heading 2
### Heading 3
line break.
line break!
line break?
Example:
```
[flag]
enum Permissions {
read // = 0b0001
write // = 0b0010
other // = 0b0100
}
fn main() {
p := Permissions.read
assert p.has(.read) // test if p has read flag
assert p.has(.read | .other) // test if *at least one* of the flags is set
}
```
Example:
```
println('inline example')
```
================================================
FILE: src/tests/testdata/documentation/stubs.vv
================================================
type Foo = v/*caret*/oidptr
================================================
FILE: src/tests/testdata/documentation/stubs.vv.md
================================================
Module: **stubs**
```v
pub type voidptr = voidptr
```
voidptr is an untyped pointer. You can pass any other type of pointer value, to a function that accepts a voidptr.
Mostly used for [C interoperability](https://docs.vosca.dev/advanced-concepts/v-and-c.html).
================================================
FILE: src/tests/testdata/types/bool_operators.vv
================================================
module types
expr_type(1 in [], 'bool')
expr_type(1 !in [], 'bool')
expr_type(1 is Foo, 'bool')
expr_type(1 !is Foo, 'bool')
expr_type(1 == 1, 'bool')
expr_type(1 != 1, 'bool')
expr_type(1 > 1, 'bool')
expr_type(1 >= 1, 'bool')
expr_type(1 < 1, 'bool')
expr_type(1 <= 1, 'bool')
expr_type(true && false, 'bool')
expr_type(true || false, 'bool')
expr_type(!false, 'bool')
expr_type(select {
a := <-ch {}
}, 'bool')
================================================
FILE: src/tests/testdata/types/call_expression.vv
================================================
module types
struct FooBar {
}
fn some_foo() string {}
fn foo_int() int {}
fn get_foo_bar() FooBar {}
fn main() {
expr_type(some_foo(), 'string')
expr_type(foo_int(), 'int')
expr_type(get_foo_bar(), 'types.FooBar')
}
================================================
FILE: src/tests/testdata/types/chan_type.vv
================================================
module types
fn main() {
ch := chan int{}
expr_type(ch, 'chan int')
expr_type(<-ch, 'int')
}
================================================
FILE: src/tests/testdata/types/constants.vv
================================================
module types
const simple = 1
const string_array = ['hello', 'world']
fn main() {
expr_type(types.simple, 'int')
expr_type(types.string_array, '[]string')
}
================================================
FILE: src/tests/testdata/types/fields.vv
================================================
module types
struct FieldsFoo {
name string
parts []int
cb fn () string
}
fn main() {
foo := FieldsFoo{
name: 'foo'
parts: [1, 2, 3]
}
expr_type(foo.name, 'string')
expr_type(foo.parts, '[]int')
expr_type(foo.cb, 'fn () string')
expr_type(foo.cb(), 'string')
}
================================================
FILE: src/tests/testdata/types/for_loop.vv
================================================
module types
for i in 0 .. 10 {
expr_type(i, 'int')
}
arr := ['str']
for val in arr {
expr_type(val, 'string')
}
for i, val in arr {
expr_type(i, 'int')
expr_type(val, 'string')
}
fixed_arr := ['str']!
expr_type(fixed_arr, '[1]string')
for val in fixed_arr {
expr_type(val, 'string')
}
for i, val in fixed_arr {
expr_type(i, 'int')
expr_type(val, 'string')
}
mp := map[string]int{}
for key, val in mp {
expr_type(key, 'string')
expr_type(val, 'int')
}
type MyMap = map[string]int
mp2 := MyMap{}
for key, val in mp2 {
expr_type(key, 'string')
expr_type(val, 'int')
}
for val in 'hello' {
expr_type(val, 'u8')
}
struct Iterator {}
fn (mut i Iterator) next() ?int {
return 1
}
it := Iterator{}
for val in it {
expr_type(val, 'int')
}
================================================
FILE: src/tests/testdata/types/for_loops.vv
================================================
module types
fn arrays() {
for index in 0 .. 10 {
expr_type(index, 'int')
}
int_array := [1, 2, 3]
for index in int_array {
expr_type(index, 'int')
}
for index, value in int_array {
expr_type(index, 'int')
expr_type(value, 'int')
}
string_array := ['1', '2', '3']
for index in string_array {
expr_type(index, 'string')
}
for index, value in string_array {
expr_type(index, 'int')
expr_type(value, 'string')
}
bool_array := [true, false]
for index in bool_array {
expr_type(index, 'bool')
}
for index, value in bool_array {
expr_type(index, 'int')
expr_type(value, 'bool')
}
}
fn maps() {
mp := map[string]int{}
for key, value in mp {
expr_type(key, 'string')
expr_type(value, 'int')
}
}
fn strings() {
mp := ''
for value in mp {
expr_type(value, 'u8')
}
for key, value in mp {
expr_type(key, 'int')
expr_type(value, 'u8')
}
}
================================================
FILE: src/tests/testdata/types/function_literal.vv
================================================
module types
fn types_foo(a string) string {}
fn types_foo1(a string) (int, string) {}
fn types_foo2(a string, b int) (int, string) {}
fn types_foo3(a string, b int) {}
fn types_foo4() {}
fn main() {
expr_type(types_foo, 'fn (string) string')
expr_type(types_foo1, 'fn (string) (int, string)')
expr_type(types_foo2, 'fn (string, int) (int, string)')
expr_type(types_foo3, 'fn (string, int)')
expr_type(types_foo4, 'fn ()')
expr_type(fn () {}, 'fn ()')
expr_type(fn (i int) {}, 'fn (int)')
expr_type(fn (i int) string {}, 'fn (int) string')
expr_type(fn (i int, s string) string {}, 'fn (int, string) string')
}
fn calls() {
func := fn (i int) string {}
expr_type(func(), 'string')
func1 := fn (i int) int {}
expr_type(func1(), 'int')
func2 := fn (i int) int {}()
expr_type(func2, 'int')
}
================================================
FILE: src/tests/testdata/types/generics.vv
================================================
module types
struct GenericStruct[T] {}
fn GenericStruct.static_method[T]() string {
return 'hello'
}
fn main() {
expr_type(GenericStruct[int]{}, 'types.GenericStruct[int]')
expr_type(GenericStruct.static_method[string](), 'string')
}
================================================
FILE: src/tests/testdata/types/if_expression.vv
================================================
module types
fn main() {
a := if true {
100
} else {
200
}
expr_type(a, 'int')
b := if true {
'100'
} else {
'200'
}
expr_type(b, 'string')
c := if true {
unsafe { 100 }
} else {
200
}
expr_type(c, 'int')
d := if true {
inner := unsafe { 100 }
inner
} else {
200
}
expr_type(d, 'int')
e := if true {
inner := map[int]string{}
inner[100]
} else {
200
}
expr_type(e, 'string')
f := if true {
unknown
} else {
200
}
expr_type(f, 'int')
g := $if macos {
1
} $else {
2
}
expr_type(g, 'int')
}
fn get_opt() ?int {
return 100
}
fn get_res() !int {
return 100
}
fn unwrapping() {
if a := get_opt() {
expr_type(a, 'int')
}
if a := get_res() {
expr_type(a, 'int')
}
}
================================================
FILE: src/tests/testdata/types/json_decode.vv
================================================
module types
import json
struct JsonData {}
fn main() {
data := ''
res := json.decode(JsonData, data)
expr_type(res, '!types.JsonData')
res2 := json.decode([]JsonData, data)
expr_type(res2, '![]types.JsonData')
res3 := json.decode(map[string]JsonData, data)
expr_type(res3, '!map[string]types.JsonData')
if res4 := json.decode(map[string]JsonData, data) {
expr_type(res4, 'map[string]types.JsonData')
}
}
================================================
FILE: src/tests/testdata/types/literals.vv
================================================
module types
fn main() {
expr_type(100, 'int')
expr_type(3.14, 'f64')
expr_type(`r`, 'rune')
expr_type(true, 'bool')
expr_type(false, 'bool')
expr_type(nil, 'voidptr')
expr_type(none, 'none')
expr_type('hello', 'string')
expr_type(r'raw hello', 'string')
expr_type(c'c hello', '&u8')
}
================================================
FILE: src/tests/testdata/types/map_init_expression.vv
================================================
module types
struct Foo {}
fn main() {
mp1 := {
0: 100
}
expr_type(mp1, 'map[int]int')
expr_type(mp1[0], 'int')
mp2 := {
'0': 100
}
expr_type(mp2, 'map[string]int')
expr_type(mp2[0], 'int')
mp3 := {
0: Foo{}
}
expr_type(mp3, 'map[int]types.Foo')
expr_type(mp3[0], 'types.Foo')
}
================================================
FILE: src/tests/testdata/types/match_expression.vv
================================================
module types
fn main() {
a := match true {
false { 100 }
else { 100 }
}
expr_type(a, 'int')
b := match true {
false {
inner := 100
inner
}
else {
100
}
}
expr_type(b, 'int')
}
================================================
FILE: src/tests/testdata/types/parameters.vv
================================================
module types
struct FooBar {}
fn foo(param &FooBar, param2 []&FooBar) {
expr_type(param, '&types.FooBar')
expr_type(param2, '[]&types.FooBar')
}
fn foo2(variadic ...FooBar) {
expr_type(variadic, '[]types.FooBar')
}
================================================
FILE: src/tests/testdata/types/pointers.vv
================================================
module types
fn main() {
a := 100
b := &a
c := &b
d := *c
e := *d
expr_type(a, 'int')
expr_type(b, '&int')
expr_type(c, '&&int')
expr_type(d, '&int')
expr_type(e, 'int')
}
================================================
FILE: src/tests/testdata/types/receiver.vv
================================================
module types
struct ReceiverFoo {}
fn (r &ReceiverFoo) method() {
expr_type(r, '&types.ReceiverFoo')
expr_type(*r, 'types.ReceiverFoo')
}
================================================
FILE: src/tests/testdata/types/slice_and_index_expression.vv
================================================
module types
fn main() {
arr := [1, 2, 3]
expr_type(arr[0], 'int')
expr_type(arr[0..10], '[]int')
fixed_arr := [1, 2, 3]!
expr_type(fixed_arr[0], 'int')
expr_type(fixed_arr[0..2], '[]int')
s := 'string'
expr_type(s[0], 'u8')
expr_type(s[0..2], 'string')
}
================================================
FILE: src/tests/testdata/types/type_initializer.vv
================================================
module types
struct FooBar {
}
fn main() {
expr_type(FooBar{}, 'types.FooBar')
expr_type(&FooBar{}, '&types.FooBar')
expr_type([]int{}, '[]int')
expr_type([]&FooBar{}, '[]&types.FooBar')
expr_type(map[string]int{}, 'map[string]int')
}
================================================
FILE: src/tests/testdata/types/unsafe_expression.vv
================================================
module types
expr_type(unsafe { 100 }, 'int')
expr_type(unsafe {
a := 100
a
}, 'int')
================================================
FILE: src/tests/types.v
================================================
module tests
import testing
fn types() testing.Tester {
mut t := testing.with_name('types')
t.type_test('literals', 'types/literals.vv')
t.type_test('parameters types', 'types/parameters.vv')
t.type_test('call expressions', 'types/call_expression.vv')
t.type_test('type initializer', 'types/type_initializer.vv')
t.type_test('for loops', 'types/for_loops.vv')
t.type_test('slice and index expression', 'types/slice_and_index_expression.vv')
t.type_test('function literal', 'types/function_literal.vv')
t.type_test('pointers', 'types/pointers.vv')
t.type_test('bool operators', 'types/bool_operators.vv')
t.type_test('unsafe expression', 'types/unsafe_expression.vv')
t.type_test('if expression', 'types/if_expression.vv')
t.type_test('match expression', 'types/match_expression.vv')
t.type_test('map init expression', 'types/map_init_expression.vv')
t.type_test('chan type', 'types/chan_type.vv')
t.type_test('struct fields', 'types/fields.vv')
t.type_test('receiver', 'types/receiver.vv')
t.type_test('json decode', 'types/json_decode.vv')
t.type_test('generics', 'types/generics.vv')
t.type_test('constants', 'types/constants.vv')
t.type_test('for loop', 'types/for_loop.vv')
return t
}
================================================
FILE: src/tools/project-checker.v
================================================
module main
import math
import runtime
import sync
import os
import time
import src.analyzer.parser
import tree_sitter_v.bindings
fn main() {
mut checker := Checker{
root: os.join_path(@VEXEROOT, 'vlib')
}
checker.check()
}
pub type AstNode = bindings.Node[bindings.NodeType]
struct ErrorInfo {
path string
node AstNode
}
fn (i ErrorInfo) str() string {
content := os.read_file(i.path) or { return 'no content' }
parent := i.node.parent() or { return 'no parent' }
text := parent.text(content)
if parent.start_byte() == 0 {
return 'root'
}
return '
Found error at ${i.path}:${i.node.start_point().row}:${i.node.start_point().column}
parent: ${parent}
parent text: ${text}
'
}
struct Checker {
root string
}
fn (mut _ Checker) need_check(path string) bool {
if !path.ends_with('.v') {
return false
}
if path.contains('stubs/') {
return false
}
return true
}
pub fn (mut i Checker) check() {
now := time.now()
println('Checking root ${i.root}')
file_chan := chan string{cap: 1000}
errors_chan := chan []ErrorInfo{cap: 1000}
spawn fn [mut i, file_chan] () {
if os.is_file(i.root) {
file_chan <- i.root
file_chan.close()
return
}
path := i.root
os.walk(path, fn [mut i, file_chan] (path string) {
if i.need_check(path) {
file_chan <- path
}
})
file_chan.close()
}()
spawn i.spawn_checking_workers(errors_chan, file_chan)
mut processed_files := 0
mut errors := []ErrorInfo{cap: 100}
for {
error := <-errors_chan or { break }
errors << error
processed_files++
}
mut per_file := map[string]bool{}
for error in errors {
per_file[error.path] = true
}
println('Checking finished')
for error in errors[..50] {
println(error)
}
println('Checking took ${time.since(now)}')
println('\nFound ${errors.len} errors in ${per_file.len} files')
println('\nParsed correctly ${(100 - (f64(per_file.len) / f64(processed_files) * 100))}% files out of ${processed_files}')
}
pub fn (mut c Checker) check_file(path string) []ErrorInfo {
content := os.read_file(path) or { return [] }
mut p := parser.Parser.new()
defer { p.free() }
res := p.parse_code(content)
root := res.tree.root_node()
errors := c.check_node(path, AstNode(root))
// unsafe { res.tree.free() }
return errors
}
pub fn (mut c Checker) check_node(path string, node AstNode) []ErrorInfo {
mut errors := []ErrorInfo{}
if node().type_name == .error {
errors << c.create_error(path, node)
}
for i := 0; i < node.child_count(); i++ {
if child := node.child(u32(i)) {
errors << c.check_node(path, child)
}
}
return errors
}
pub fn (mut c Checker) create_error(path string, node AstNode) ErrorInfo {
return ErrorInfo{
path: path
node: node
}
}
pub fn (mut i Checker) spawn_checking_workers(errors_chan chan []ErrorInfo, file_chan chan string) {
mut wg := sync.new_waitgroup()
cpus := runtime.nr_cpus()
workers := math.max(cpus - 1, 1)
wg.add(workers)
for j := 0; j < workers; j++ {
spawn fn [file_chan, mut wg, mut i, errors_chan] () {
for {
file := <-file_chan or { break }
errors_chan <- i.check_file(file)
}
wg.done()
}()
}
wg.wait()
errors_chan.close()
}
================================================
FILE: src/up.v
================================================
module main
import cli
import term
fn up_cmd(cmd cli.Command) ! {
download_install_vsh()!
is_nightly := cmd.flags.get_bool('nightly') or { false }
nightly_flag := if is_nightly { '--nightly' } else { '' }
command := 'up ${nightly_flag}'
exit_code := call_install_vsh(command)!
if exit_code != 0 {
errorln('Failed to update ${term.bold('v-analyzer')}')
return
}
}
================================================
FILE: src/utils/text_utils.v
================================================
module utils
pub fn pascal_case_to_snake_case(s string) string {
mut res := ''
for index, c in s {
if c.ascii_str().is_upper() || c.is_digit() {
if index > 0 {
res += '_'
}
res += c.ascii_str().to_lower()
} else {
res += c.ascii_str()
}
}
return res
}
pub fn snake_case_to_camel_case(s string) string {
mut res := ''
mut upper := false
for c in s {
if c == `_` {
upper = true
} else {
if upper {
res += c.ascii_str().to_upper()
upper = false
} else {
res += c.ascii_str()
}
}
}
return res
}
// compute_offset returns a byte offset from the given position
pub fn compute_offset(src string, line int, col int) int {
mut offset := 0
mut src_line := 0
mut src_col := 0
src_len := src.len
for i := 0; i < src_len; i++ {
byt := src[i]
is_lf := byt == `\n`
is_crlf := i + 1 < src_len && byt == `\r` && src[i + 1] == `\n`
is_eol := is_lf || is_crlf
if src_line == line && src_col == col {
return offset
}
if is_eol {
if src_line == line && col > src_col {
return -1
}
src_line++
src_col = 0
if is_crlf {
offset += 2
i++
} else {
offset++
}
continue
}
src_col++
offset++
}
return offset
}
================================================
FILE: src/utils/text_utils_test.v
================================================
module utils
fn test_pascal_case_to_snake_case() {
assert pascal_case_to_snake_case('CamelCase') == 'camel_case'
assert pascal_case_to_snake_case('SomeValue') == 'some_value'
assert pascal_case_to_snake_case('SomeValue') == 'some_value'
assert pascal_case_to_snake_case('SomeValue1') == 'some_value_1'
assert pascal_case_to_snake_case('Some') == 'some'
}
fn test_snake_case_to_camel_case() {
assert snake_case_to_camel_case('snake_case') == 'snakeCase'
assert snake_case_to_camel_case('some_value') == 'someValue'
assert snake_case_to_camel_case('some_value_1') == 'someValue1'
assert snake_case_to_camel_case('some') == 'some'
}
================================================
FILE: src/utils.v
================================================
module main
import os
import term
import net.http
const download_dir = os.join_path(os.vtmp_dir(), 'v-analyzer')
const analyzer_install_script_download_path = 'https://raw.githubusercontent.com/vlang/v-analyzer/main/install.vsh'
const analyzer_install_script_path = os.join_path(download_dir, 'install.vsh')
pub fn errorln(msg string) {
eprintln('${term.red('[ERROR]')} ${msg}')
}
pub fn warnln(msg string) {
println('${term.yellow('[WARN]')} ${msg}')
}
pub fn infoln(msg string) {
println('${term.blue('[INFO]')} ${msg}')
}
pub fn successln(msg string) {
println('${term.green('[SUCCESS]')} ${msg}')
}
pub fn download_install_vsh() ! {
if !os.exists(download_dir) {
os.mkdir(download_dir) or { return error('Failed to create tmp dir: ${err}') }
}
mut file := os.create(analyzer_install_script_path) or {
return error('Error creating/opening file for script: ${err}')
}
defer { file.close() }
req := http.get(analyzer_install_script_download_path) or {
return error('Failed to download script: ${err}')
}
file.write(req.body.bytes()) or { return error('Error writing to script file: ${err}') }
}
pub fn call_install_vsh(cmd string) !int {
$if windows {
// On Windows we cannot use `os.Command` because it doesn't support Windows
res := os.execute('v ${analyzer_install_script_path} ${cmd}')
println(res.output)
return res.exit_code
}
mut command := os.Command{
path: 'v ${analyzer_install_script_path} ${cmd}'
redirect_stdout: true
}
command.start()!
for !command.eof {
println(command.read_line())
}
command.close()!
return command.exit_code
}
================================================
FILE: tree_sitter_v/.gitattributes
================================================
src/tree_sitter/* linguist-generated
src/grammar.json linguist-generated
src/node-types.json linguist-generated
src/parser.c linguist-generated
================================================
FILE: tree_sitter_v/.gitignore
================================================
# Avoid including irrelevant files
node_modules/
*.log
build/
package-lock.json
Package.swift
# Ignore binary output folders
bin/
# Ignore common editor/system specific metadata
.DS_Store
.idea/
.vscode/
*.iml
target/
Cargo.lock
yarn.lock
Cargo.toml
bindings/c/
bindings/go/
bindings/node/
bindings/python/
bindings/rust/
bindings/swift/
binding.gyp
pyproject.toml
setup.py
================================================
FILE: tree_sitter_v/.prettierrc.js
================================================
module.exports = {
printWidth: 100,
singleQuote: true,
useTabs: true,
};
================================================
FILE: tree_sitter_v/LICENSE
================================================
MIT License
Copyright (c) 2021 Ned Palacios
Copyright (c) 2023 V Open Source Community Association (VOSCA)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: tree_sitter_v/README.md
================================================
# tree-sitter-v
V language grammar for [tree-sitter](https://github.com/tree-sitter/tree-sitter)
This grammar is heavily derived from the following language grammars:
- [tree-sitter-go](https://github.com/tree-sitter/tree-sitter-go)
- [tree-sitter-ruby](https://github.com/tree-sitter/tree-sitter-ruby/)
- [tree-sitter-c](https://github.com/tree-sitter/tree-sitter-c/)
## Limitations
1. It does not support all deprecated/outdated syntaxes to avoid any ambiguities and to enforce the
one-way philosophy as much as possible.
2. Assembly/SQL code in ASM/SQL block nodes are loosely checked and parsed immediately regardless of
the content.
## Authors
This project initially started by
[nedpals](https://github.com/nedpals)
and after that, till July 2023, it was heavily modified by the
[VOSCA](https://github.com/vlang-association).
The project is now developed by *all interested contributors*,
just like [V itself](https://github.com/vlang/v).
## License
This project is under the **MIT License**. See the
[LICENSE](https://github.com/vlang/v-analyzer/blob/main/LICENSE)
file for the full license text.
================================================
FILE: tree_sitter_v/bindings/bindings.c.v
================================================
module bindings
// This file contains the bindings for C API of tree-sitter.
// They are indented to be used by wrapper functions in "tree_sitter.v".
//
// See "core/lib/include/tree_sitter/api.h" for function references.
// We directly build "lib.c" rather using the static library.
#flag -I @VMODROOT/tree_sitter_v/bindings/core/lib/include
#flag -I @VMODROOT/tree_sitter_v/bindings/core/lib/src
#flag @VMODROOT/tree_sitter_v/bindings/core/lib/src/lib.c
#include "tree_sitter/api.h"
#flag -I @VMODROOT/tree_sitter_v/bindings
#flag -I @VMODROOT/tree_sitter_v/src
#flag @VMODROOT/tree_sitter_v/src/parser.c
#include "bindings.h"
pub type TSDecodeFunction = fn (string, u32, &int) u32
pub enum TSVInputEncoding {
utf8
utf16le
utf16be
custom
}
pub type C.TSInputEncoding = TSVInputEncoding
@[typedef]
pub struct C.TSInput {
mut:
payload voidptr
read fn (payload voidptr, byte_index u32, position C.TSPoint, bytes_read &u32) &char
encoding C.TSInputEncoding
decode TSDecodeFunction
}
@[typedef]
pub struct C.TSLanguage {}
@[typedef]
pub struct C.TSParser {}
fn C.tree_sitter_v() &C.TSLanguage
fn C.ts_parser_new() &C.TSParser
fn C.ts_parser_set_language(parser &C.TSParser, language &C.TSLanguage) bool
fn C.ts_parser_parse_string(parser &C.TSParser, const_old_tree &C.TSTree, str &char, len u32) &C.TSTree
fn C.ts_parser_parse(parser &C.TSParser, const_old_tree &C.TSTree, input C.TSInput) &C.TSTree
fn C.ts_parser_delete(tree &C.TSParser)
fn C.ts_parser_reset(parser &C.TSParser)
@[inline]
fn new_ts_parser() &C.TSParser {
return C.ts_parser_new()
}
@[inline]
fn (mut p C.TSParser) parse(old_tree &TSTree, input C.TSInput) &TSTree {
return &TSTree(C.ts_parser_parse(p, voidptr(old_tree), input))
}
@[inline]
fn (mut p C.TSParser) reset() {
C.ts_parser_reset(p)
}
@[inline]
fn (mut p C.TSParser) set_language(language &C.TSLanguage) bool {
return C.ts_parser_set_language(p, language)
}
@[inline]
fn (mut p C.TSParser) parse_string(content string) &TSTree {
return p.parse_string_with_old_tree(content, &TSTree(unsafe { nil }))
}
@[inline]
fn (mut p C.TSParser) parse_string_with_old_tree(content string, old_tree &TSTree) &TSTree {
return p.parse_string_with_old_tree_and_len(content, old_tree, u32(content.len))
}
@[inline]
fn (mut p C.TSParser) parse_string_with_old_tree_and_len(content string, old_tree &TSTree, len u32) &TSTree {
return &TSTree(C.ts_parser_parse_string(p, voidptr(old_tree), &char(content.str), len))
}
@[inline]
fn (mut p C.TSParser) parse_bytes(content []u8) &TSTree {
return p.parse_bytes_with_old_tree(content, &TSTree(unsafe { nil }))
}
fn byte_array_input_read(pl voidptr, byte_index u32, position C.TSPoint, bytes_read &u32) &char {
payload := *(&[]u8(pl))
if byte_index >= u32(payload.len) {
unsafe {
*bytes_read = 0
}
return c''
} else {
unsafe {
*bytes_read = u32(payload.len) - byte_index
}
return unsafe { &char(payload.data) + byte_index }
}
}
fn (mut p C.TSParser) parse_bytes_with_old_tree(content []u8, old_tree &TSTree) &TSTree {
return p.parse(old_tree,
payload: &content
read: byte_array_input_read
encoding: TSVInputEncoding.utf8
)
}
@[inline; unsafe]
fn (p &C.TSParser) delete() {
unsafe {
C.ts_parser_delete(p)
}
}
@[typedef]
pub struct C.TSLanguage {}
pub struct C.TSTree {
included_range_count u32
}
@[export: 'TSTree']
pub struct TSTree {
included_range_count u32
}
fn C.ts_tree_copy(tree &C.TSTree) &C.TSTree
fn C.ts_tree_root_node(tree &C.TSTree) C.TSNode
fn C.ts_tree_delete(tree &C.TSTree)
fn C.ts_tree_edit(tree &C.TSTree, edit &C.TSInputEdit)
fn C.ts_tree_get_changed_ranges(old_tree &C.TSTree, new_tree &C.TSTree, count &u32) &C.TSRange
@[inline]
fn (tree &TSTree) copy() &TSTree {
return &TSTree(C.ts_tree_copy(voidptr(tree)))
}
@[inline]
fn (tree &TSTree) root_node() C.TSNode {
return C.ts_tree_root_node(&C.TSTree(tree))
}
@[inline]
fn (tree &TSTree) edit(input_edit &C.TSInputEdit) {
C.ts_tree_edit(&C.TSTree(tree), input_edit)
}
fn (tree &TSTree) get_changed_ranges(new_tree &TSTree) []C.TSRange {
mut len := u32(0)
buf := C.ts_tree_get_changed_ranges(&C.TSTree(tree), &C.TSTree(new_tree), &len)
element_size := int(sizeof(C.TSRange))
return unsafe {
array{
element_size: element_size
len: int(len)
cap: int(len)
data: buf
}
}
}
@[unsafe]
fn (tree &TSTree) free() {
unsafe {
C.ts_tree_delete(&C.TSTree(tree))
}
}
@[typedef]
pub struct C.TSNode {
context [4]u32
id voidptr
tree &TSTree
}
fn C.ts_node_string(node C.TSNode) &char
fn C.ts_node_type(node C.TSNode) &char
fn C.ts_node_is_null(node C.TSNode) bool
fn C.ts_node_is_named(node C.TSNode) bool
fn C.ts_node_is_missing(node C.TSNode) bool
fn C.ts_node_is_extra(node C.TSNode) bool
fn C.ts_node_has_changes(node C.TSNode) bool
fn C.ts_node_has_error(node C.TSNode) bool
fn C.ts_node_start_point(node C.TSNode) C.TSPoint
fn C.ts_node_end_point(node C.TSNode) C.TSPoint
fn C.ts_node_start_byte(node C.TSNode) u32
fn C.ts_node_end_byte(node C.TSNode) u32
fn C.ts_node_parent(node C.TSNode) C.TSNode
fn C.ts_node_child(node C.TSNode, index u32) C.TSNode
fn C.ts_node_child_count(node C.TSNode) u32
fn C.ts_node_named_child(node C.TSNode, index u32) C.TSNode
fn C.ts_node_named_child_count(node C.TSNode) u32
fn C.ts_node_child_by_field_name(node C.TSNode, field_name &char, field_name_length u32) C.TSNode
fn C.ts_node_next_sibling(node C.TSNode) C.TSNode
fn C.ts_node_prev_sibling(node C.TSNode) C.TSNode
fn C.ts_node_next_named_sibling(node C.TSNode) C.TSNode
fn C.ts_node_prev_named_sibling(node C.TSNode) C.TSNode
fn C.ts_node_first_child_for_byte(node C.TSNode, offset u32) C.TSNode
fn C.ts_node_first_named_child_for_byte(node C.TSNode, offset u32) C.TSNode
fn C.ts_node_descendant_for_byte_range(node C.TSNode, start_offset u32, end_offset u32) C.TSNode
fn C.ts_node_descendant_for_point_range(node C.TSNode, start_point C.TSPoint, end_point C.TSPoint) C.TSNode
fn C.ts_node_named_descendant_for_byte_range(node C.TSNode, start_offset u32, end_offset u32) C.TSNode
fn C.ts_node_named_descendant_for_point_range(node C.TSNode, start_point C.TSPoint, end_point C.TSPoint) C.TSNode
fn C.ts_node_eq(node C.TSNode, another_node C.TSNode) bool
pub fn (node C.TSNode) text(text string) string {
start_index := node.start_byte()
end_index := node.end_byte()
if start_index >= end_index || start_index >= u32(text.len) || end_index > u32(text.len) {
return ''
}
return text.substr(int(start_index), int(end_index))
}
@[inline]
fn (node C.TSNode) sexpr_str() string {
if node.is_null() {
return ''
}
sexpr := C.ts_node_string(node)
return unsafe { sexpr.vstring() }
}
@[inline]
pub fn (node C.TSNode) text_length() u32 {
start := node.start_byte()
end := node.end_byte()
return end - start
}
@[inline]
pub fn (node C.TSNode) start_point() C.TSPoint {
if node.is_null() {
return C.TSPoint{0, 0}
}
return C.ts_node_start_point(node)
}
fn (node C.TSNode) end_point() C.TSPoint {
if node.is_null() {
return C.TSPoint{0, 0}
}
return C.ts_node_end_point(node)
}
fn (node C.TSNode) start_byte() u32 {
if node.is_null() {
return 0
}
return C.ts_node_start_byte(node)
}
fn (node C.TSNode) end_byte() u32 {
if node.is_null() {
return 0
}
return C.ts_node_end_byte(node)
}
@[inline]
fn (node C.TSNode) range() C.TSRange {
return C.TSRange{
start_point: node.start_point()
end_point: node.end_point()
start_byte: node.start_byte()
end_byte: node.end_byte()
}
}
pub fn (node C.TSNode) type_name() string {
if node.is_null() {
return ''
}
c := &char(C.ts_node_type(node))
return unsafe { c.vstring() }
}
@[inline]
fn (node C.TSNode) is_null() bool {
return C.ts_node_is_null(node)
}
@[inline]
fn (node C.TSNode) is_named() bool {
return C.ts_node_is_named(node)
}
@[inline]
fn (node C.TSNode) is_missing() bool {
return C.ts_node_is_missing(node)
}
@[inline]
fn (node C.TSNode) is_extra() bool {
return C.ts_node_is_extra(node)
}
@[inline]
fn (node C.TSNode) has_changes() bool {
return C.ts_node_has_changes(node)
}
fn (node C.TSNode) is_error() bool {
if node.is_null() {
return true
}
return C.ts_node_has_error(node)
}
pub fn (node C.TSNode) parent_nth(depth int) ?TSNode {
if node.is_null() {
return none
}
mut res := node
for _ in 0 .. depth {
res = res.parent()?
}
if res.is_null() {
return none
}
return res
}
pub fn (node C.TSNode) parent() ?C.TSNode {
if node.is_null() {
return none
}
parent := C.ts_node_parent(node)
if parent.is_null() {
return none
}
return parent
}
pub fn (node C.TSNode) first_child() ?C.TSNode {
if node.is_null() {
return none
}
count_child := node.child_count()
if count_child == 0 {
return none
}
child := C.ts_node_child(node, 0)
if child.is_null() {
return none
}
return child
}
pub fn (node C.TSNode) last_child() ?C.TSNode {
if node.is_null() {
return none
}
count_child := node.child_count()
if count_child == 0 {
return none
}
child := C.ts_node_child(node, count_child - 1)
if child.is_null() {
return none
}
return child
}
fn (node C.TSNode) child(index u32) ?C.TSNode {
if node.is_null() {
return none
}
child := C.ts_node_child(node, index)
if child.is_null() {
return none
}
return child
}
@[inline]
fn (node C.TSNode) child_count() u32 {
return C.ts_node_child_count(node)
}
fn (node C.TSNode) named_child(pos u32) ?C.TSNode {
if node.is_null() {
return none
}
child := C.ts_node_named_child(node, pos)
if child.is_null() {
return none
}
return child
}
fn (node C.TSNode) named_child_count() u32 {
if node.is_null() {
return 0
}
return C.ts_node_named_child_count(node)
}
pub fn (node C.TSNode) child_by_field_name(name string) ?C.TSNode {
if node.is_null() {
return none
}
child := C.ts_node_child_by_field_name(node, &char(name.str), u32(name.len))
if child.is_null() {
return none
}
return child
}
fn (node C.TSNode) next_sibling() ?C.TSNode {
if node.is_null() {
return none
}
sibling := C.ts_node_next_sibling(node)
if sibling.is_null() {
return none
}
return sibling
}
fn (node C.TSNode) prev_sibling() ?C.TSNode {
if node.is_null() {
return none
}
sibling := C.ts_node_prev_sibling(node)
if sibling.is_null() {
return none
}
return sibling
}
fn (node C.TSNode) next_named_sibling() ?C.TSNode {
if node.is_null() {
return none
}
sibling := C.ts_node_next_named_sibling(node)
if sibling.is_null() {
return none
}
return sibling
}
fn (node C.TSNode) prev_named_sibling() ?C.TSNode {
if node.is_null() {
return none
}
sibling := C.ts_node_prev_named_sibling(node)
if sibling.is_null() {
return none
}
return sibling
}
fn (node C.TSNode) first_child_for_byte(offset u32) ?C.TSNode {
if node.is_null() {
return none
}
got_node := C.ts_node_first_child_for_byte(node, offset)
if got_node.is_null() {
return none
}
return got_node
}
fn (node C.TSNode) first_named_child_for_byte(offset u32) ?C.TSNode {
if node.is_null() {
return none
}
got_node := C.ts_node_first_named_child_for_byte(node, offset)
if got_node.is_null() {
return none
}
return got_node
}
fn (node C.TSNode) descendant_for_byte_range(start_range u32, end_range u32) ?C.TSNode {
if node.is_null() {
return none
}
got_node := C.ts_node_descendant_for_byte_range(node, start_range, end_range)
if got_node.is_null() {
return none
}
return got_node
}
fn (node C.TSNode) descendant_for_point_range(start_point C.TSPoint, end_point C.TSPoint) ?C.TSNode {
if node.is_null() {
return none
}
got_node := C.ts_node_descendant_for_point_range(node, start_point, end_point)
if got_node.is_null() {
return none
}
return got_node
}
fn (node C.TSNode) named_descendant_for_byte_range(start_range u32, end_range u32) ?C.TSNode {
if node.is_null() {
return none
}
got_node := C.ts_node_named_descendant_for_byte_range(node, start_range, end_range)
if got_node.is_null() {
return none
}
return got_node
}
fn (node C.TSNode) named_descendant_for_point_range(start_point C.TSPoint, end_point C.TSPoint) ?C.TSNode {
if node.is_null() {
return none
}
got_node := C.ts_node_named_descendant_for_point_range(node, start_point, end_point)
if got_node.is_null() {
return none
}
return got_node
}
fn C.ts_tree_cursor_new(node C.TSNode) C.TSTreeCursor
pub type TSTreeCursor = C.TSTreeCursor
@[inline]
pub fn (node C.TSNode) tree_cursor() TSTreeCursor {
return C.ts_tree_cursor_new(node)
}
@[typedef]
pub struct C.TSTreeCursor {
tree voidptr
id voidptr
context [3]u32
}
fn C.ts_tree_cursor_delete(cursor &C.TSTreeCursor)
fn C.ts_tree_cursor_reset(cursor &C.TSTreeCursor, node C.TSNode)
fn C.ts_tree_cursor_current_node(cursor &C.TSTreeCursor) C.TSNode
fn C.ts_tree_cursor_current_field_name(cursor &C.TSTreeCursor) &char
fn C.ts_tree_cursor_goto_parent(cursor &C.TSTreeCursor) bool
fn C.ts_tree_cursor_goto_next_sibling(cursor &C.TSTreeCursor) bool
fn C.ts_tree_cursor_goto_first_child(cursor &C.TSTreeCursor) bool
fn C.ts_tree_cursor_first_child_for_byte(cursor &C.TSTreeCursor, idx u32) i64
fn C.ts_tree_cursor_copy(cursor &C.TSTreeCursor) C.TSTreeCursor
@[inline; unsafe]
pub fn (cursor &C.TSTreeCursor) delete() {
C.ts_tree_cursor_delete(cursor)
}
@[inline]
fn (mut cursor C.TSTreeCursor) reset(node C.TSNode) {
C.ts_tree_cursor_reset(cursor, node)
}
pub type TSNode = C.TSNode
@[inline]
pub fn (cursor &C.TSTreeCursor) current_node() ?TSNode {
got_node := C.ts_tree_cursor_current_node(cursor)
if got_node.is_null() {
return none
}
return got_node
}
@[inline]
pub fn (cursor &C.TSTreeCursor) current_field_name() string {
c := &char(C.ts_tree_cursor_current_field_name(cursor))
return unsafe { c.vstring() }
}
@[inline]
pub fn (mut cursor C.TSTreeCursor) to_parent() bool {
return C.ts_tree_cursor_goto_parent(cursor)
}
@[inline]
pub fn (mut cursor C.TSTreeCursor) next() bool {
return C.ts_tree_cursor_goto_next_sibling(cursor)
}
@[inline]
pub fn (mut cursor C.TSTreeCursor) to_first_child() bool {
return C.ts_tree_cursor_goto_first_child(cursor)
}
@[typedef]
pub struct C.TSInputEdit {
start_byte u32
old_end_byte u32
new_end_byte u32
start_point C.TSPoint
old_end_point C.TSPoint
new_end_point C.TSPoint
}
@[typedef]
pub struct C.TSPoint {
pub:
row u32
column u32
}
fn (left_point C.TSPoint) eq(right_point C.TSPoint) bool {
return left_point.row == right_point.row && left_point.column == right_point.column
}
@[typedef]
pub struct C.TSRange {
pub:
start_point C.TSPoint
end_point C.TSPoint
start_byte u32
end_byte u32
}
fn (left_range C.TSRange) eq(right_range C.TSRange) bool {
return left_range.start_point.eq(right_range.start_point)
&& left_range.end_point.eq(right_range.end_point)
&& left_range.start_byte == right_range.start_byte
&& left_range.end_byte == right_range.end_byte
}
================================================
FILE: tree_sitter_v/bindings/bindings.h
================================================
#ifndef TREE_SITTER_V_H_
#define TREE_SITTER_V_H_
#ifdef __cplusplus
extern "C" {
#endif
const TSLanguage *tree_sitter_v(void);
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_V_H_
================================================
FILE: tree_sitter_v/bindings/bindings.v
================================================
module bindings
pub type TSParser = C.TSParser
pub type TSLanguage = C.TSLanguage
pub const language = unsafe { &TSLanguage(C.tree_sitter_v()) }
pub struct Parser[T] {
mut:
raw_parser &TSParser = unsafe { nil } @[required]
type_factory NodeTypeFactory[T] @[required]
}
pub fn new_parser[T](type_factory NodeTypeFactory[T]) &Parser[T] {
mut parser := new_ts_parser()
return &Parser[T]{
raw_parser: parser
type_factory: type_factory
}
}
@[inline]
pub fn (mut p Parser[T]) set_language(language &TSLanguage) {
p.raw_parser.set_language(language)
}
@[inline]
pub fn (mut p Parser[T]) reset() {
p.raw_parser.reset()
}
@[inline]
pub fn (p &Parser[T]) free() {
unsafe {
p.raw_parser.delete()
}
}
@[params]
pub struct ParseConfig {
pub:
source string @[required]
tree &TSTree = &TSTree(unsafe { nil })
}
pub fn (mut p Parser[T]) parse_string(cfg ParseConfig) &Tree[T] {
tree := p.raw_parser.parse_string_with_old_tree(cfg.source, cfg.tree)
return &Tree[T]{
raw_tree: tree
type_factory: p.type_factory
}
}
pub interface NodeTypeFactory[T] {
get_type(type_name string) T
}
pub struct Tree[T] {
type_factory NodeTypeFactory[T] @[required]
pub:
raw_tree &TSTree = unsafe { nil } @[required]
}
@[unsafe]
pub fn (tree &Tree[T]) free() {
unsafe { tree.raw_tree.free() }
}
pub fn (tree Tree[T]) root_node() Node[T] {
return new_tsnode[T](tree.type_factory, tree.raw_tree.root_node())
}
pub fn new_tsnode[T](factory NodeTypeFactory[T], node TSNode) Node[T] {
return Node[T]{
raw_node: node
type_factory: factory
type_name: factory.get_type(node.type_name())
}
}
pub struct Node[T] {
type_factory NodeTypeFactory[T] @[required]
pub:
raw_node TSNode @[required]
type_name T @[required]
}
@[inline]
pub fn (node Node[T]) text(text string) string {
return node.raw_node.text(text)
}
@[inline]
pub fn (node Node[T]) text_matches(all_text string, text_to_find string) bool {
text_len := u32(text_to_find.len)
node_len := node.text_length()
// if the text we are looking for does not match in length,
// then the text cannot exactly match
if text_len != node_len {
return false
}
return node.text(all_text) == text_to_find
}
@[inline]
pub fn (node Node[T]) first_char(text string) u8 {
start_index := node.start_byte()
if start_index >= u32(text.len) {
return 0
}
return text[start_index]
}
@[inline]
pub fn (node Node[T]) text_length() u32 {
start := node.raw_node.start_byte()
end := node.raw_node.end_byte()
return end - start
}
@[inline]
pub fn (node Node[T]) str() string {
return node.raw_node.sexpr_str()
}
@[inline]
pub fn (node Node[T]) start_point() TSPoint {
return node.raw_node.start_point()
}
@[inline]
pub fn (node Node[T]) end_point() TSPoint {
return node.raw_node.end_point()
}
@[inline]
pub fn (node Node[T]) start_byte() u32 {
return node.raw_node.start_byte()
}
@[inline]
pub fn (node Node[T]) end_byte() u32 {
return node.raw_node.end_byte()
}
@[inline]
pub fn (node Node[T]) range() TSRange {
return node.raw_node.range()
}
@[inline]
pub fn (node Node[T]) is_null() bool {
return node.raw_node.is_null()
}
@[inline]
pub fn (node Node[T]) is_leaf() bool {
return node.child_count() == 0
}
@[inline]
pub fn (node Node[T]) is_named() bool {
return node.raw_node.is_named()
}
@[inline]
pub fn (node Node[T]) is_missing() bool {
return node.raw_node.is_missing()
}
@[inline]
pub fn (node Node[T]) is_extra() bool {
return node.raw_node.is_extra()
}
@[inline]
pub fn (node Node[T]) has_changes() bool {
return node.raw_node.has_changes()
}
@[inline]
pub fn (node Node[T]) is_error() bool {
return node.raw_node.is_error()
}
pub fn (node Node[T]) parent() ?Node[T] {
parent := node.raw_node.parent()?
return new_tsnode[T](node.type_factory, parent)
}
pub fn (node Node[T]) parent_nth(depth int) ?Node[T] {
mut res := node.raw_node
for _ in 0 .. depth {
res = res.parent()?
}
return new_tsnode[T](node.type_factory, res)
}
pub fn (node Node[T]) is_parent_of(other Node[T]) bool {
mut parent := other.parent() or { return false }
for {
if parent.equal(node) {
return true
}
parent = parent.parent() or { break }
}
return false
}
pub fn (node Node[T]) child(pos u32) ?Node[T] {
child := node.raw_node.child(pos)?
return new_tsnode[T](node.type_factory, child)
}
@[inline]
pub fn (node Node[T]) child_count() u32 {
return node.raw_node.child_count()
}
pub fn (node Node[T]) named_child(pos u32) ?Node[T] {
child := node.raw_node.named_child(pos)?
return new_tsnode[T](node.type_factory, child)
}
@[inline]
pub fn (node Node[T]) named_child_count() u32 {
return node.raw_node.named_child_count()
}
pub fn (node Node[T]) child_by_field_name(name string) ?Node[T] {
child := node.raw_node.child_by_field_name(name)?
return new_tsnode[T](node.type_factory, child)
}
pub fn (node Node[T]) first_child() ?Node[T] {
count_child := node.child_count()
if count_child == 0 {
return none
}
child := node.raw_node.child(0) or { return none }
return new_tsnode[T](node.type_factory, child)
}
pub fn (node Node[T]) last_child() ?Node[T] {
count_child := node.child_count()
if count_child == 0 {
return none
}
child := node.raw_node.child(count_child - 1) or { return none }
return new_tsnode[T](node.type_factory, child)
}
pub fn (node Node[T]) next_sibling() ?Node[T] {
sibling := node.raw_node.next_sibling() or { return none }
return new_tsnode[T](node.type_factory, sibling)
}
pub fn (node Node[T]) prev_sibling() ?Node[T] {
sibling := node.raw_node.prev_sibling() or { return none }
return new_tsnode[T](node.type_factory, sibling)
}
pub fn (node Node[T]) next_named_sibling() ?Node[T] {
sibling := node.raw_node.next_named_sibling() or { return none }
return new_tsnode[T](node.type_factory, sibling)
}
pub fn (node Node[T]) prev_named_sibling() ?Node[T] {
sibling := node.raw_node.prev_named_sibling() or { return none }
return new_tsnode[T](node.type_factory, sibling)
}
pub fn (node Node[T]) first_child_for_byte(offset u32) ?Node[T] {
child := node.raw_node.first_child_for_byte(offset) or { return none }
return new_tsnode[T](node.type_factory, child)
}
pub fn (node Node[T]) first_named_child_for_byte(offset u32) ?Node[T] {
child := node.raw_node.first_named_child_for_byte(offset) or { return none }
return new_tsnode[T](node.type_factory, child)
}
pub fn (node Node[T]) descendant_for_byte_range(start_range u32, end_range u32) ?Node[T] {
desc := node.raw_node.descendant_for_byte_range(start_range, end_range) or { return none }
return new_tsnode[T](node.type_factory, desc)
}
pub fn (node Node[T]) descendant_for_point_range(start_point TSPoint, end_point TSPoint) ?Node[T] {
desc := node.raw_node.descendant_for_point_range(start_point, end_point) or { return none }
return new_tsnode[T](node.type_factory, desc)
}
pub fn (node Node[T]) named_descendant_for_byte_range(start_range u32, end_range u32) ?Node[T] {
desc := node.raw_node.named_descendant_for_byte_range(start_range, end_range) or { return none }
return new_tsnode[T](node.type_factory, desc)
}
pub fn (node Node[T]) named_descendant_for_point_range(start_point TSPoint, end_point TSPoint) ?Node[T] {
desc := node.raw_node.named_descendant_for_point_range(start_point, end_point) or {
return none
}
return new_tsnode[T](node.type_factory, desc)
}
pub fn (node Node[T]) first_node_by_type(type_name T) ?Node[T] {
mut named_child := node.named_child(0) or { return none }
len := node.child_count()
for i := 0; i < int(len); i++ {
if named_child.type_name == type_name {
return named_child
}
named_child = named_child.next_sibling() or { continue }
}
return none
}
pub fn (node Node[T]) last_node_by_type(type_name T) ?Node[T] {
len := node.child_count()
mut named_child := node.named_child(len - 1) or { return none }
for i := int(len - 1); i >= 0; i-- {
if named_child.type_name == type_name {
return named_child
}
named_child = named_child.prev_sibling() or { continue }
}
return none
}
@[inline]
pub fn (node Node[T]) == (other_node Node[T]) bool {
return C.ts_node_eq(node.raw_node, other_node.raw_node)
}
@[inline]
pub fn (node Node[T]) equal(other_node Node[T]) bool {
return C.ts_node_eq(node.raw_node, other_node.raw_node)
}
@[inline]
pub fn (node Node[T]) tree_cursor() TreeCursor[T] {
return TreeCursor[T]{
type_factory: node.type_factory
raw_cursor: node.raw_node.tree_cursor()
}
}
pub struct TreeCursor[T] {
type_factory NodeTypeFactory[T] @[required]
pub mut:
raw_cursor C.TSTreeCursor @[required]
}
@[inline]
pub fn (mut cursor TreeCursor[T]) reset(node Node[T]) {
cursor.raw_cursor.reset(node.raw_node)
}
@[inline]
pub fn (cursor TreeCursor[T]) current_node() ?Node[T] {
got_node := cursor.raw_cursor.current_node()?
return new_tsnode[T](cursor.type_factory, got_node)
}
@[inline]
pub fn (cursor TreeCursor[T]) current_field_name() string {
return cursor.raw_cursor.current_field_name()
}
@[inline]
pub fn (mut cursor TreeCursor[T]) to_parent() bool {
return cursor.raw_cursor.to_parent()
}
@[inline]
pub fn (mut cursor TreeCursor[T]) next() bool {
return cursor.raw_cursor.next()
}
@[inline]
pub fn (mut cursor TreeCursor[T]) to_first_child() bool {
return cursor.raw_cursor.to_first_child()
}
pub type TSRange = C.TSRange
pub fn (r TSRange) str() string {
return '
{
start: ${TSPoint(r.start_point)}
end: ${TSPoint(r.end_point)}
start_byte: ${r.start_byte}
end_byte: ${r.end_byte}
}
'.trim_indent()
}
pub type TSPoint = C.TSPoint
pub fn (p TSPoint) str() string {
return '(${p.row}, ${p.column})'
}
================================================
FILE: tree_sitter_v/bindings/generate_types.vsh
================================================
import json
import strings
import os
// This is a script file which creates static type declarations
// for tree-sitter-v's node types by using the information found
// in 'node-types.json' and turn it into a pseudo-sum type using enums
// and create a `NodeTypeFactory` implementation that will convert type names
// into respective `NodeType` enum.
// Anonymous nodes are automatically identified as `NodeType.unknown`.
//
// See: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
const to_be_escaped = ['none', 'true', 'false', 'map', 'type', 'nil']
fn escape_name(name string) string {
if name in to_be_escaped {
return name + '_'
}
return name
}
fn write_enum_member(mut wr strings.Builder, type_name string, member_name string) {
wr.write_string('${type_name}.${escape_name(member_name)}')
}
fn write_enum_array(mut wr strings.Builder, enum_type_name string, list []string) {
wr.writeln('[')
for i, name in list {
wr.write_string(' ')
// write fully qualified name for enum member only for the first member
type_name := if i == 0 { enum_type_name } else { '' }
write_enum_member(mut wr, type_name, name)
if i < list.len - 1 {
wr.write_u8(`,`)
}
wr.write_u8(`\n`)
}
wr.write_u8(`]`)
}
fn write_const_enum_array(mut wr strings.Builder, var_name string, enum_type_name string, list []string) {
wr.write_string('\nconst ${var_name} = ')
write_enum_array(mut wr, enum_type_name, list)
wr.write_u8(`\n`)
}
struct TSNodeType {
name string @[json: 'type']
named bool
subtypes []TSNodeType
}
fn (typ TSNodeType) is_anon() bool {
return !typ.named || typ.name.len == 0 || typ.name[0] == `_`
}
cur_dir := dir(@FILE)
node_types_json := read_file(join_path(@VMODROOT, 'tree_sitter_v', 'src', 'node-types.json'))!
node_types := json.decode([]TSNodeType, node_types_json)!
node_type_enum_name := 'NodeType'
super_type_enum_name := 'SuperType'
file_path := join_path(cur_dir, 'node_types.v')
mut file := open_file(file_path, 'w+')!
mut sb := strings.new_builder(1024 * 1024)
mut supertype_node_groups := map[string][]string{}
sb.writeln('// This is an AUTO-GENERATED file. DO NOT EDIT this file directly! See `generate_types.vsh`')
sb.writeln('module bindings')
sb.writeln('\n')
sb.writeln('import arrays { merge }')
// write supertypes
sb.writeln('pub enum ${super_type_enum_name} {')
sb.writeln(' unknown')
for node_type in node_types {
if !node_type.named || node_type.name.len == 0 || node_type.name[0] != `_`
|| node_type.subtypes.len == 0 {
continue
}
sb.writeln(' ${escape_name(node_type.name[1..])}')
supertype_node_groups[node_type.name] = node_type.subtypes.map(it.name)
}
sb.writeln('}\n')
sb.writeln('pub enum ${node_type_enum_name} {')
sb.writeln(' unknown')
sb.writeln(' error')
mut declaration_node_types := []string{cap: 100}
mut identifier_node_types := []string{cap: 100}
mut literal_node_types := []string{cap: 100}
// write node types as enum members
for node_type in node_types {
if node_type.is_anon() {
continue
}
if node_type.name.ends_with('_declaration') {
declaration_node_types << node_type.name
} else if node_type.name == 'identifier' || node_type.name.ends_with('_identifier') {
identifier_node_types << node_type.name
} else if node_type.name.ends_with('_literal') {
literal_node_types << node_type.name
}
sb.writeln(' ${escape_name(node_type.name)}')
}
sb.writeln('}')
for supertype_name, supertype_node_types in supertype_node_groups {
sb.write_string('\n')
sb.write_string('const supertype_${supertype_name}_nodes = ')
super_type_members := supertype_node_types.filter(it.starts_with('_'))
for type_member in super_type_members {
sb.write_string('merge(supertype_${type_member}_nodes, ')
}
write_enum_array(mut sb, node_type_enum_name, supertype_node_types.filter(!it.starts_with('_')))
sb.writeln(')'.repeat(super_type_members.len))
}
sb.write_string('\n')
sb.write_string('pub fn (typ ${node_type_enum_name}) group() ${super_type_enum_name} {')
sb.write_string(' return ')
supertype_ordered_names := [
'top_level_declaration',
'expression',
'statement',
'unknown',
]
mut super_type_index := 0
for supertype_name in supertype_ordered_names {
if super_type_index < supertype_ordered_names.len - 1 {
sb.write_string('if typ in supertype__${supertype_name}_nodes ')
}
sb.write_string('{\n ')
write_enum_member(mut sb, super_type_enum_name, supertype_name)
sb.write_string('\n }')
if super_type_index < supertype_ordered_names.len - 1 {
sb.write_string(' else ')
} else {
sb.write_u8(`\n`)
}
super_type_index++
}
sb.writeln('}')
// write constants
write_const_enum_array(mut sb, 'declaration_node_types', node_type_enum_name,
declaration_node_types)
write_const_enum_array(mut sb, 'identifier_node_types', node_type_enum_name, identifier_node_types)
write_const_enum_array(mut sb, 'literal_node_types', node_type_enum_name, literal_node_types)
sb.writeln('\n')
sb.writeln('pub fn (typ ${node_type_enum_name}) is_declaration() bool { return typ in declaration_node_types }')
sb.writeln('pub fn (typ ${node_type_enum_name}) is_identifier() bool { return typ in identifier_node_types }')
sb.writeln('pub fn (typ ${node_type_enum_name}) is_literal() bool { return typ in literal_node_types }')
// create VNodeTypeFactory
node_type_factory_sym_name := 'VNodeTypeFactory'
sb.writeln('\n')
sb.writeln('pub const type_factory = &${node_type_factory_sym_name}{}')
sb.writeln('\n')
sb.writeln('pub struct ${node_type_factory_sym_name} {}')
sb.writeln('\n')
sb.writeln('pub fn (nf ${node_type_factory_sym_name}) get_type(type_name string) ${node_type_enum_name} {')
sb.writeln(' return bindings.node_type_name_to_enum[type_name] or { NodeType.unknown }')
sb.writeln('}')
sb.writeln('\n')
sb.writeln('const node_type_name_to_enum = {')
sb.write_string(" 'ERROR': ${node_type_enum_name}.error")
for node_type in node_types {
if node_type.is_anon() {
continue
}
sb.write_string(" '${node_type.name}': ")
write_enum_member(mut sb, node_type_enum_name, node_type.name)
sb.writeln('')
}
sb.writeln('}')
file.write(sb)!
file.close()
res := os.execute('v fmt -w ${file_path}')
if res.exit_code != 0 {
panic('v fmt failed:\n\n${res.output}')
}
println('Successfully generated `${file_path}`')
================================================
FILE: tree_sitter_v/bindings/node_types.v
================================================
// This is an AUTO-GENERATED file. DO NOT EDIT this file directly! See `generate_types.vsh`
module bindings
import arrays { merge }
pub enum SuperType {
unknown
expression
expression_with_blocks
statement
top_level_declaration
}
pub enum NodeType {
unknown
error
anon_struct_type
anon_struct_value_expression
append_statement
argument
argument_list
array_creation
array_type
as_type_cast_expression
asm_statement
assert_statement
assignment_statement
atomic_type
attribute
attribute_expression
attributes
binary_expression
block
block_comment
break_statement
c_string_literal
call_expression
capture
capture_list
channel_type
compile_time_for_statement
compile_time_if_expression
compile_time_selector_expression
const_declaration
const_definition
continue_statement
dec_expression
defer_statement
element_list
else_branch
embedded_definition
enum_backed_type
enum_declaration
enum_fetch
enum_field_definition
expression_list
field_name
fixed_array_creation
fixed_array_type
for_clause
for_statement
format_specifier
function_declaration
function_literal
function_type
generic_parameter
generic_parameters
generic_type
global_var_declaration
global_var_definition
go_expression
goto_statement
hash_statement
identifier_list
if_attribute
if_expression
implements_clause
import_alias
import_declaration
import_list
import_name
import_path
import_spec
in_expression
inc_expression
index_expression
interface_declaration
interface_method_definition
interpreted_string_literal
is_clause
is_expression
key_value_attribute
keyed_element
label_definition
label_reference
labeled_statement
line_comment
literal
literal_attribute
lock_expression
map_init_expression
map_keyed_element
map_type
match_arm
match_arm_type
match_arms
match_else_arm_clause
match_expression
match_expression_list
module_clause
multi_return_type
mutability_modifiers
mutable_expression
mutable_identifier
option_propagation_expression
option_type
or_block
or_block_expression
overridable_operator
parameter_declaration
parameter_list
parenthesized_expression
plain_type
pointer_type
qualified_type
range
range_clause
raw_string_literal
receive_expression
receiver
reference_expression
result_propagation_expression
result_type
return_statement
select_arm
select_arm_statement
select_else_arn_clause
select_expression
selective_import_list
selector_expression
send_statement
shared_type
shebang
short_element_list
short_lambda
signature
simple_statement
slice_expression
source_file
spawn_expression
special_argument_list
spread_expression
sql_expression
static_method_declaration
static_receiver
string_interpolation
struct_declaration
struct_field_declaration
struct_field_scope
sum_type
thread_type
type_declaration
type_initializer
type_initializer_body
type_parameter_declaration
type_parameter_list
type_parameters
type_reference_expression
unary_expression
unsafe_expression
value_attribute
var_declaration
var_definition
var_definition_list
variadic_parameter
visibility_modifiers
wrong_pointer_type
escape_sequence
false_
float_literal
identifier
int_literal
interpolation_closing
interpolation_opening
nil_
none_
pseudo_compile_time_identifier
rune_literal
true_
}
const supertype__expression_nodes = merge(supertype__expression_with_blocks_nodes, [
NodeType.array_creation,
.as_type_cast_expression,
.binary_expression,
.call_expression,
.dec_expression,
.enum_fetch,
.fixed_array_creation,
.function_literal,
.go_expression,
.in_expression,
.inc_expression,
.index_expression,
.is_expression,
.literal,
.option_propagation_expression,
.or_block_expression,
.parenthesized_expression,
.pseudo_compile_time_identifier,
.receive_expression,
.reference_expression,
.result_propagation_expression,
.selector_expression,
.slice_expression,
.spawn_expression,
.unary_expression,
])
const supertype__expression_with_blocks_nodes = [
NodeType.anon_struct_value_expression,
.compile_time_if_expression,
.if_expression,
.lock_expression,
.map_init_expression,
.match_expression,
.select_expression,
.sql_expression,
.type_initializer,
.unsafe_expression,
]
const supertype__statement_nodes = [
NodeType.append_statement,
.asm_statement,
.assert_statement,
.block,
.break_statement,
.compile_time_for_statement,
.continue_statement,
.defer_statement,
.for_statement,
.goto_statement,
.hash_statement,
.labeled_statement,
.return_statement,
.send_statement,
.simple_statement,
]
const supertype__top_level_declaration_nodes = [
NodeType.const_declaration,
.enum_declaration,
.function_declaration,
.global_var_declaration,
.interface_declaration,
.static_method_declaration,
.struct_declaration,
.type_declaration,
]
pub fn (typ NodeType) group() SuperType {
return if typ in supertype__top_level_declaration_nodes {
SuperType.top_level_declaration
} else if typ in supertype__expression_nodes {
SuperType.expression
} else if typ in supertype__statement_nodes {
SuperType.statement
} else {
SuperType.unknown
}
}
const declaration_node_types = [
NodeType.const_declaration,
.enum_declaration,
.function_declaration,
.global_var_declaration,
.import_declaration,
.interface_declaration,
.parameter_declaration,
.static_method_declaration,
.struct_declaration,
.struct_field_declaration,
.type_declaration,
.type_parameter_declaration,
.var_declaration,
]
const identifier_node_types = [
NodeType.mutable_identifier,
.identifier,
.pseudo_compile_time_identifier,
]
const literal_node_types = [
NodeType.c_string_literal,
.function_literal,
.interpreted_string_literal,
.raw_string_literal,
.float_literal,
.int_literal,
.rune_literal,
]
pub fn (typ NodeType) is_declaration() bool {
return typ in declaration_node_types
}
pub fn (typ NodeType) is_identifier() bool {
return typ in identifier_node_types
}
pub fn (typ NodeType) is_literal() bool {
return typ in literal_node_types
}
pub const type_factory = &VNodeTypeFactory{}
pub struct VNodeTypeFactory {}
pub fn (nf VNodeTypeFactory) get_type(type_name string) NodeType {
return node_type_name_to_enum[type_name] or { NodeType.unknown }
}
const node_type_name_to_enum = {
'ERROR': NodeType.error
'anon_struct_type': NodeType.anon_struct_type
'anon_struct_value_expression': NodeType.anon_struct_value_expression
'append_statement': NodeType.append_statement
'argument': NodeType.argument
'argument_list': NodeType.argument_list
'array_creation': NodeType.array_creation
'array_type': NodeType.array_type
'as_type_cast_expression': NodeType.as_type_cast_expression
'asm_statement': NodeType.asm_statement
'assert_statement': NodeType.assert_statement
'assignment_statement': NodeType.assignment_statement
'atomic_type': NodeType.atomic_type
'attribute': NodeType.attribute
'attribute_expression': NodeType.attribute_expression
'attributes': NodeType.attributes
'binary_expression': NodeType.binary_expression
'block': NodeType.block
'block_comment': NodeType.block_comment
'break_statement': NodeType.break_statement
'c_string_literal': NodeType.c_string_literal
'call_expression': NodeType.call_expression
'capture': NodeType.capture
'capture_list': NodeType.capture_list
'channel_type': NodeType.channel_type
'compile_time_for_statement': NodeType.compile_time_for_statement
'compile_time_if_expression': NodeType.compile_time_if_expression
'compile_time_selector_expression': NodeType.compile_time_selector_expression
'const_declaration': NodeType.const_declaration
'const_definition': NodeType.const_definition
'continue_statement': NodeType.continue_statement
'dec_expression': NodeType.dec_expression
'defer_statement': NodeType.defer_statement
'element_list': NodeType.element_list
'else_branch': NodeType.else_branch
'embedded_definition': NodeType.embedded_definition
'enum_backed_type': NodeType.enum_backed_type
'enum_declaration': NodeType.enum_declaration
'enum_fetch': NodeType.enum_fetch
'enum_field_definition': NodeType.enum_field_definition
'expression_list': NodeType.expression_list
'field_name': NodeType.field_name
'fixed_array_creation': NodeType.fixed_array_creation
'fixed_array_type': NodeType.fixed_array_type
'for_clause': NodeType.for_clause
'for_statement': NodeType.for_statement
'format_specifier': NodeType.format_specifier
'function_declaration': NodeType.function_declaration
'function_literal': NodeType.function_literal
'function_type': NodeType.function_type
'generic_parameter': NodeType.generic_parameter
'generic_parameters': NodeType.generic_parameters
'generic_type': NodeType.generic_type
'global_var_declaration': NodeType.global_var_declaration
'global_var_definition': NodeType.global_var_definition
'go_expression': NodeType.go_expression
'goto_statement': NodeType.goto_statement
'hash_statement': NodeType.hash_statement
'identifier_list': NodeType.identifier_list
'if_attribute': NodeType.if_attribute
'if_expression': NodeType.if_expression
'implements_clause': NodeType.implements_clause
'import_alias': NodeType.import_alias
'import_declaration': NodeType.import_declaration
'import_list': NodeType.import_list
'import_name': NodeType.import_name
'import_path': NodeType.import_path
'import_spec': NodeType.import_spec
'in_expression': NodeType.in_expression
'inc_expression': NodeType.inc_expression
'index_expression': NodeType.index_expression
'interface_declaration': NodeType.interface_declaration
'interface_method_definition': NodeType.interface_method_definition
'interpreted_string_literal': NodeType.interpreted_string_literal
'is_clause': NodeType.is_clause
'is_expression': NodeType.is_expression
'key_value_attribute': NodeType.key_value_attribute
'keyed_element': NodeType.keyed_element
'label_definition': NodeType.label_definition
'label_reference': NodeType.label_reference
'labeled_statement': NodeType.labeled_statement
'line_comment': NodeType.line_comment
'literal': NodeType.literal
'literal_attribute': NodeType.literal_attribute
'lock_expression': NodeType.lock_expression
'map_init_expression': NodeType.map_init_expression
'map_keyed_element': NodeType.map_keyed_element
'map_type': NodeType.map_type
'match_arm': NodeType.match_arm
'match_arm_type': NodeType.match_arm_type
'match_arms': NodeType.match_arms
'match_else_arm_clause': NodeType.match_else_arm_clause
'match_expression': NodeType.match_expression
'match_expression_list': NodeType.match_expression_list
'module_clause': NodeType.module_clause
'multi_return_type': NodeType.multi_return_type
'mutability_modifiers': NodeType.mutability_modifiers
'mutable_expression': NodeType.mutable_expression
'mutable_identifier': NodeType.mutable_identifier
'option_propagation_expression': NodeType.option_propagation_expression
'option_type': NodeType.option_type
'or_block': NodeType.or_block
'or_block_expression': NodeType.or_block_expression
'overridable_operator': NodeType.overridable_operator
'parameter_declaration': NodeType.parameter_declaration
'parameter_list': NodeType.parameter_list
'parenthesized_expression': NodeType.parenthesized_expression
'plain_type': NodeType.plain_type
'pointer_type': NodeType.pointer_type
'qualified_type': NodeType.qualified_type
'range': NodeType.range
'range_clause': NodeType.range_clause
'raw_string_literal': NodeType.raw_string_literal
'receive_expression': NodeType.receive_expression
'receiver': NodeType.receiver
'reference_expression': NodeType.reference_expression
'result_propagation_expression': NodeType.result_propagation_expression
'result_type': NodeType.result_type
'return_statement': NodeType.return_statement
'select_arm': NodeType.select_arm
'select_arm_statement': NodeType.select_arm_statement
'select_else_arn_clause': NodeType.select_else_arn_clause
'select_expression': NodeType.select_expression
'selective_import_list': NodeType.selective_import_list
'selector_expression': NodeType.selector_expression
'send_statement': NodeType.send_statement
'shared_type': NodeType.shared_type
'shebang': NodeType.shebang
'short_element_list': NodeType.short_element_list
'short_lambda': NodeType.short_lambda
'signature': NodeType.signature
'simple_statement': NodeType.simple_statement
'slice_expression': NodeType.slice_expression
'source_file': NodeType.source_file
'spawn_expression': NodeType.spawn_expression
'special_argument_list': NodeType.special_argument_list
'spread_expression': NodeType.spread_expression
'sql_expression': NodeType.sql_expression
'static_method_declaration': NodeType.static_method_declaration
'static_receiver': NodeType.static_receiver
'string_interpolation': NodeType.string_interpolation
'struct_declaration': NodeType.struct_declaration
'struct_field_declaration': NodeType.struct_field_declaration
'struct_field_scope': NodeType.struct_field_scope
'sum_type': NodeType.sum_type
'thread_type': NodeType.thread_type
'type_declaration': NodeType.type_declaration
'type_initializer': NodeType.type_initializer
'type_initializer_body': NodeType.type_initializer_body
'type_parameter_declaration': NodeType.type_parameter_declaration
'type_parameter_list': NodeType.type_parameter_list
'type_parameters': NodeType.type_parameters
'type_reference_expression': NodeType.type_reference_expression
'unary_expression': NodeType.unary_expression
'unsafe_expression': NodeType.unsafe_expression
'value_attribute': NodeType.value_attribute
'var_declaration': NodeType.var_declaration
'var_definition': NodeType.var_definition
'var_definition_list': NodeType.var_definition_list
'variadic_parameter': NodeType.variadic_parameter
'visibility_modifiers': NodeType.visibility_modifiers
'wrong_pointer_type': NodeType.wrong_pointer_type
'escape_sequence': NodeType.escape_sequence
'false': NodeType.false_
'float_literal': NodeType.float_literal
'identifier': NodeType.identifier
'int_literal': NodeType.int_literal
'interpolation_closing': NodeType.interpolation_closing
'interpolation_opening': NodeType.interpolation_opening
'nil': NodeType.nil_
'none': NodeType.none_
'pseudo_compile_time_identifier': NodeType.pseudo_compile_time_identifier
'rune_literal': NodeType.rune_literal
'true': NodeType.true_
}
================================================
FILE: tree_sitter_v/bindings/simple_test.v
================================================
module bindings
fn test_simple() {
mut p := new_parser[NodeType](type_factory)
p.set_language(language)
code := 'fn main() {}'
tree := p.parse_string(source: code)
root := tree.root_node()
println(root)
fc := root.first_child()?
if fc.type_name == .function_declaration {
if name_node := fc.child_by_field_name('name') {
assert name_node.text(code) == 'main'
assert name_node.range().start_point.row == 0
assert name_node.range().start_point.column == 3
assert name_node.range().end_point.row == 0
assert name_node.range().end_point.column == 7
} else {
assert false, 'name node not found'
}
} else {
assert false, 'function declaration not found'
}
}
================================================
FILE: tree_sitter_v/examples/cursor.v
================================================
module main
import bindings
fn main() {
mut p := bindings.new_parser[bindings.NodeType](bindings.type_factory)
p.set_language(bindings.language)
code := '
fn foo() int {
return 1
}
'.trim_indent()
tree := p.parse_string(source: code)
root := tree.root_node()
mut cursor := root.tree_cursor()
cursor.to_first_child() // go to all the children of the root node
cursor.to_first_child() // go to the first child of the function node
for {
node := cursor.current_node() or { break }
println('Node "${node.type_name}" with text: ' + node.text(code))
if !cursor.next() {
break
}
}
}
================================================
FILE: tree_sitter_v/examples/simple.v
================================================
module main
import bindings
fn main() {
mut p := bindings.new_parser[bindings.NodeType](bindings.type_factory)
p.set_language(bindings.language)
code := 'fn main() {}'
tree := p.parse_string(source: code)
root := tree.root_node()
println(root)
fc := root.first_child()?
if fc.type_name == .function_declaration {
if name_node := fc.child_by_field_name('name') {
println('Found function: ${name_node.text(code)}')
println('Position: ${name_node.range()}')
println('Line: ${name_node.start_point().row}')
}
}
}
================================================
FILE: tree_sitter_v/examples/with_old_tree.v
================================================
module main
import time
import bindings
fn main() {
mut p := bindings.new_parser[bindings.NodeType](bindings.type_factory)
p.set_language(bindings.language)
code := '
fn foo() int {
return 1
}
'.trim_indent()
mut now := time.now()
tree := p.parse_string(source: code)
println('Parsed in ${time.since(now)}')
root := tree.root_node()
println(root)
new_code := '
fn foo() int {
return 2
}
'.trim_indent()
now = time.now()
new_tree := p.parse_string(source: new_code, tree: tree.raw_tree)
println('Parsed in ${time.since(now)}')
new_root := new_tree.root_node()
println(new_root)
}
================================================
FILE: tree_sitter_v/grammar.js
================================================
/**
* @file V grammar for tree-sitter
*/
/* eslint-disable no-undef */
/* eslint-disable arrow-parens */
/* eslint-disable camelcase */
/* eslint-disable-next-line spaced-comment */
///
const PREC = {
attributes: 10,
match_arm_type: 9,
type_initializer: 8,
primary: 7,
unary: 6,
multiplicative: 5,
additive: 4,
comparative: 3,
and: 2,
or: 1,
resolve: 1,
composite_literal: -1,
strictly_expression_list: -2,
};
const multiplicative_operators = ['*', '/', '%', '<<', '>>', '>>>', '&', '&^'];
const additive_operators = ['+', '-', '|', '^'];
const comparative_operators = ['==', '!=', '<', '<=', '>', '>='];
const assignment_operators = multiplicative_operators
.concat(additive_operators)
.map((operator) => operator + '=')
.concat('=');
const unary_operators = ['+', '-', '!', '~', '^', '*', '&'];
const overridable_operators = ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '<=', '>='].map(
(operator) => token(operator),
);
const terminator = choice('\n', '\r', '\r\n');
const unicode_digit = /[0-9]/;
const unicode_letter = /[a-zA-Zα-ωΑ-Ωµ]/;
const letter = choice(unicode_letter, '_');
const hex_digit = /[0-9a-fA-F]/;
const octal_digit = /[0-7]/;
const decimal_digit = /[0-9]/;
const binary_digit = /[01]/;
const hex_digits = seq(hex_digit, repeat(seq(optional('_'), hex_digit)));
const octal_digits = seq(octal_digit, repeat(seq(optional('_'), octal_digit)));
const decimal_digits = seq(decimal_digit, repeat(seq(optional('_'), decimal_digit)));
const binary_digits = seq(binary_digit, repeat(seq(optional('_'), binary_digit)));
const hex_literal = seq('0', choice('x', 'X'), optional('_'), hex_digits);
const octal_literal = seq('0', optional(choice('o', 'O')), optional('_'), octal_digits);
const decimal_literal = choice('0', seq(/[1-9]/, optional(seq(optional('_'), decimal_digits))));
const binary_literal = seq('0', choice('b', 'B'), optional('_'), binary_digits);
const int_literal = choice(binary_literal, decimal_literal, octal_literal, hex_literal);
const decimal_exponent = seq(choice('e', 'E'), optional(choice('+', '-')), decimal_digits);
const decimal_float_literal = choice(
seq(decimal_digits, '.', decimal_digits, optional(decimal_exponent)),
seq(decimal_digits, decimal_exponent),
seq('.', decimal_digits, optional(decimal_exponent)),
);
const hex_exponent = seq(choice('p', 'P'), optional(choice('+', '-')), decimal_digits);
const hex_mantissa = choice(
seq(optional('_'), hex_digits, '.', optional(hex_digits)),
seq(optional('_'), hex_digits),
seq('.', hex_digits),
);
const hex_float_literal = seq('0', choice('x', 'X'), hex_mantissa, hex_exponent);
const float_literal = choice(decimal_float_literal, hex_float_literal);
const format_flag = token(/[bgGeEfFcdoxXpsS]/);
const semi = choice(terminator, ';');
const list_separator = choice(semi, ',');
module.exports = grammar({
name: 'v',
extras: ($) => [/\s/, $.line_comment, $.block_comment],
word: ($) => $.identifier,
externals: (_) => [],
inline: ($) => [$._string_literal, $._top_level_declaration, $._array],
supertypes: ($) => [
$._expression,
$._statement,
$._top_level_declaration,
$._expression_with_blocks,
],
conflicts: ($) => [
[$.fixed_array_type, $._expression_without_blocks],
[$.qualified_type, $._expression_without_blocks],
[$.fixed_array_type, $.literal],
[$.reference_expression, $.type_reference_expression],
[$.is_expression],
[$._expression_without_blocks, $.element_list],
],
rules: {
source_file: ($) =>
seq(
optional($.shebang),
optional($.module_clause),
repeat(
choice(
seq($.import_list, optional(terminator)),
seq($._top_level_declaration, optional(terminator)),
seq($._statement, optional(terminator)),
),
),
),
shebang: (_) => seq('#!', /.*/),
line_comment: (_) => seq('//', /.*/),
block_comment: (_) =>
seq(
'/*',
repeat(
choice(
/\*/,
regexOr(
'[^*]', // any symbol except reserved
'[/][^*]', // start of nested comment
'[^*][/]', // end of nested comment
),
),
),
'*/',
),
comment: ($) => choice($.line_comment, $.block_comment),
module_clause: ($) => seq(optional($.attributes), 'module', $.identifier),
import_list: ($) => prec.right(repeat1($.import_declaration)),
import_declaration: ($) => seq('import', $.import_spec, semi),
import_spec: ($) =>
seq($.import_path, optional($.import_alias), optional($.selective_import_list)),
// foo.bar.baz
import_path: ($) => seq($.import_name, repeat(seq('.', $.import_name))),
// foo
import_name: ($) => $.identifier,
// foo as bar
// ^^^^^^
import_alias: ($) => seq('as', $.import_name),
// { foo, bar }
selective_import_list: ($) =>
seq(
'{',
$.reference_expression,
repeat(seq(choice(',', terminator), optional($.reference_expression))),
'}',
),
// ==================== TOP LEVEL DECLARATIONS ====================
_top_level_declaration: ($) =>
choice(
$.const_declaration,
$.global_var_declaration,
$.type_declaration,
$.function_declaration,
$.static_method_declaration,
$.struct_declaration,
$.enum_declaration,
$.interface_declaration,
),
const_declaration: ($) =>
seq(
optional(field('attributes', $.attributes)),
optional($.visibility_modifiers),
'const',
choice($.const_definition, seq('(', repeat(seq($.const_definition, semi)), ')')),
),
const_definition: ($) => seq(field('name', $.identifier), '=', field('value', $._expression)),
global_var_declaration: ($) =>
seq(
optional(field('attributes', $.attributes)),
'__global',
choice($.global_var_definition, seq('(', repeat(seq($.global_var_definition, semi)), ')')),
),
global_var_definition: ($) =>
seq(
optional(field('modifiers', 'volatile')),
field('name', $.identifier),
choice($.plain_type, $._global_var_value),
),
_global_var_value: ($) => seq('=', field('value', $._expression)),
type_declaration: ($) =>
prec.right(
PREC.resolve,
seq(
optional($.visibility_modifiers),
'type',
field('name', $.identifier),
optional(field('generic_parameters', $.generic_parameters)),
'=',
field('type', choice($.sum_type, $.plain_type)),
),
),
function_declaration: ($) =>
prec.right(
PREC.resolve,
seq(
optional(field('attributes', $.attributes)),
optional($.visibility_modifiers),
'fn',
optional(field('receiver', $.receiver)),
field('name', $._function_name),
optional(field('generic_parameters', $.generic_parameters)),
field('signature', $.signature),
optional(field('body', $.block)),
),
),
static_method_declaration: ($) =>
prec.right(
PREC.resolve,
seq(
optional(field('attributes', $.attributes)),
optional($.visibility_modifiers),
'fn',
field('static_receiver', $.static_receiver),
'.',
field('name', $._function_name),
optional(field('generic_parameters', $.generic_parameters)),
field('signature', $.signature),
optional(field('body', $.block)),
),
),
static_receiver: ($) => $.reference_expression,
_function_name: ($) => choice($.identifier, $.overridable_operator),
overridable_operator: () => choice(...overridable_operators),
receiver: ($) =>
prec(
PREC.primary,
seq(
'(',
seq(
optional(field('mutability', $.mutability_modifiers)),
field('name', $.identifier),
field('type', alias($._plain_type_without_special, $.plain_type)),
),
')',
),
),
signature: ($) =>
prec.right(
seq(
field('parameters', choice($.parameter_list, $.type_parameter_list)),
optional(field('result', $.plain_type)),
),
),
parameter_list: ($) =>
prec(PREC.resolve, seq(
'(',
optional(choice(
$.variadic_parameter,
seq(
sep($.parameter_declaration),
optional(seq(',', $.variadic_parameter))
)
)),
')'
)),
parameter_declaration: ($) =>
seq(
optional(field('mutability', $.mutability_modifiers)),
field('name', $.identifier),
optional(field('variadic', '...')),
field('type', $.plain_type),
),
variadic_parameter: ($) => '...',
type_parameter_list: ($) => seq('(', sep($.type_parameter_declaration), ')'),
type_parameter_declaration: ($) =>
prec(
PREC.primary,
seq(
optional($.mutability_modifiers),
optional(field('variadic', '...')),
field('type', $.plain_type),
),
),
// fn foo[T, T2]() {}
// ^^^^^^^
generic_parameters: ($) =>
seq(
token.immediate('['),
sep($.generic_parameter),
optional(','),
']',
),
generic_parameter: ($) => $.identifier,
struct_declaration: ($) =>
seq(
optional(field('attributes', $.attributes)),
optional($.visibility_modifiers),
choice('struct', 'union'),
field('name', $.identifier),
optional(field('generic_parameters', $.generic_parameters)),
optional(seq('implements', field('implements', $.implements_clause))),
$._struct_body,
),
implements_clause: ($) =>
seq(
choice($.type_reference_expression, $.qualified_type),
repeat(seq(',', choice($.type_reference_expression, $.qualified_type))),
),
_struct_body: ($) =>
seq(
'{',
repeat(
choice(
seq($.struct_field_scope, optional(terminator)),
seq($.struct_field_declaration, optional(terminator)),
),
),
'}',
),
// pub:
// mut:
// pub mut:
// __global:
struct_field_scope: () => seq(choice('pub', 'mut', seq('pub', 'mut'), '__global'), ':'),
struct_field_declaration: ($) => choice($._struct_field_definition, $.embedded_definition),
_struct_field_definition: ($) =>
prec.right(
PREC.type_initializer,
seq(
field('name', $.identifier),
field('type', $.plain_type),
optional(seq('=', field('default_value', $._expression))),
optional(field('attributes', $.attribute)),
),
),
embedded_definition: ($) =>
choice($.type_reference_expression, $.qualified_type, $.generic_type),
enum_declaration: ($) =>
seq(
optional(field('attributes', $.attributes)),
optional($.visibility_modifiers),
'enum',
field('name', $.identifier),
optional($.enum_backed_type),
$._enum_body,
),
enum_backed_type: ($) => seq('as', $.plain_type),
_enum_body: ($) => seq('{', repeat(seq($.enum_field_definition, optional(terminator))), '}'),
enum_field_definition: ($) =>
seq(
field('name', $.identifier),
optional(seq('=', field('value', $._expression))),
optional(field('attributes', $.attribute)),
),
interface_declaration: ($) =>
seq(
optional(field('attributes', $.attributes)),
optional($.visibility_modifiers),
'interface',
field('name', $.identifier),
optional(field('generic_parameters', $.generic_parameters)),
$._interface_body,
),
_interface_body: ($) =>
seq(
'{',
repeat(
choice(
seq($.struct_field_scope, optional(terminator)),
seq($.struct_field_declaration, optional(terminator)),
seq($.interface_method_definition, optional(terminator)),
),
),
'}',
),
interface_method_definition: ($) =>
prec.right(
seq(
field('name', $.identifier),
optional(field('generic_parameters', $.generic_parameters)),
field('signature', $.signature),
optional(field('attributes', $.attribute)),
),
),
// ==================== EXPRESSIONS ====================
_expression: ($) => choice($._expression_without_blocks, $._expression_with_blocks),
_expression_without_blocks: ($) =>
choice(
$.parenthesized_expression,
$.go_expression,
$.spawn_expression,
$.call_expression,
$.function_literal,
$.reference_expression,
$._max_group,
$.array_creation,
$.fixed_array_creation,
$.unary_expression,
$.receive_expression,
$.binary_expression,
$.is_expression,
$.in_expression,
$.index_expression,
$.slice_expression,
$.as_type_cast_expression,
$.selector_expression,
$.enum_fetch,
$.inc_expression,
$.dec_expression,
$.or_block_expression,
$.option_propagation_expression,
$.result_propagation_expression,
),
_expression_with_blocks: ($) =>
choice(
$.type_initializer,
$.anon_struct_value_expression,
$.if_expression,
$.match_expression,
$.select_expression,
$.sql_expression,
$.lock_expression,
$.unsafe_expression,
$.compile_time_if_expression,
$.map_init_expression,
),
strictly_expression_list: ($) =>
prec(
PREC.strictly_expression_list,
seq(
choice($._expression, $.mutable_expression),
',',
sep(choice($._expression, $.mutable_expression)),
),
),
inc_expression: ($) => seq($._expression, '++'),
dec_expression: ($) => seq($._expression, '--'),
or_block_expression: ($) => seq($._expression, $.or_block),
option_propagation_expression: ($) => prec(PREC.match_arm_type, seq($._expression, '?')),
result_propagation_expression: ($) => prec(PREC.match_arm_type, seq($._expression, '!')),
anon_struct_value_expression: ($) =>
seq(
'struct',
'{',
choice(
field('element_list', $.element_list),
// For short struct init syntax
field('short_element_list', $.short_element_list),
),
'}',
),
go_expression: ($) => prec.left(PREC.composite_literal, seq('go', $._expression)),
spawn_expression: ($) => prec.left(PREC.composite_literal, seq('spawn', $._expression)),
parenthesized_expression: ($) => seq('(', field('expression', $._expression), ')'),
call_expression: ($) =>
prec.right(
PREC.primary,
choice(
seq(field('function', token('json.decode')), field('arguments', $.special_argument_list)),
seq(
field('name', $._expression),
optional(field('type_parameters', $.type_parameters)),
field('arguments', $.argument_list),
),
),
),
type_parameters: ($) => prec.dynamic(2, seq(token.immediate('['), sep($.plain_type), ']')),
argument_list: ($) =>
seq('(', choice(repeat(seq($.argument, optional(list_separator))), $.short_lambda), ')'),
short_lambda: ($) =>
seq('|', optional(sep($.reference_expression)), '|', $._expression_without_blocks),
argument: ($) =>
choice($._expression, $.mutable_expression, $.keyed_element, $.spread_expression),
special_argument_list: ($) =>
seq(
'(',
alias($._plain_type_without_special, $.plain_type),
optional(seq(',', $._expression)),
')',
),
type_initializer: ($) =>
prec.right(
PREC.type_initializer,
seq(field('type', $.plain_type), field('body', $.type_initializer_body)),
),
type_initializer_body: ($) =>
seq(
'{',
optional(
choice(
field('element_list', $.element_list),
// For short struct init syntax
field('short_element_list', $.short_element_list),
),
),
'}',
),
element_list: ($) =>
repeat1(
seq(
choice($.spread_expression, $.keyed_element, $.reference_expression),
optional(list_separator),
),
),
short_element_list: ($) =>
repeat1(seq(alias($._expression, $.element), optional(list_separator))),
field_name: ($) => $.reference_expression,
keyed_element: ($) => seq(field('key', $.field_name), ':', field('value', $._expression)),
function_literal: ($) =>
prec.right(
seq(
'fn',
optional(field('capture_list', $.capture_list)),
optional(field('generic_parameters', $.generic_parameters)),
field('signature', $.signature),
field('body', $.block),
),
),
capture_list: ($) => seq('[', sep($.capture), optional(','), ']'),
capture: ($) => seq(optional($.mutability_modifiers), $.reference_expression),
reference_expression: ($) => prec.left($.identifier),
type_reference_expression: ($) => prec.left($.identifier),
unary_expression: ($) =>
prec(
PREC.unary,
seq(field('operator', choice(...unary_operators)), field('operand', $._expression)),
),
receive_expression: ($) =>
prec.right(PREC.unary, seq(field('operator', '<-'), field('operand', $._expression))),
binary_expression: ($) => {
const table = [
[PREC.multiplicative, choice(...multiplicative_operators)],
[PREC.additive, choice(...additive_operators)],
[PREC.comparative, choice(...comparative_operators)],
[PREC.and, '&&'],
[PREC.or, '||'],
];
return choice(
...table.map(([precedence, operator]) =>
prec.left(
Number(precedence),
seq(
field('left', $._expression),
// @ts-ignore
field('operator', operator),
field('right', $._expression),
),
),
),
);
},
as_type_cast_expression: ($) => seq($._expression, 'as', $.plain_type),
or_block: ($) => seq('or', field('block', $.block)),
_max_group: ($) => prec.left(PREC.resolve, choice($.pseudo_compile_time_identifier, $.literal)),
escape_sequence: () =>
token(
prec(
1,
seq(
'\\',
choice(
/u[a-fA-F\d]{4}/,
/U[a-fA-F\d]{8}/,
/x[a-fA-F\d]{2}/,
/\d{3}/,
/\r?\n/,
/['"abfrntv$\\]/,
/\S/,
),
),
),
),
literal: ($) =>
choice(
$.int_literal,
$.float_literal,
$._string_literal,
$.rune_literal,
$.none,
$.true,
$.false,
$.nil,
),
none: () => 'none',
true: () => 'true',
false: () => 'false',
nil: () => 'nil',
spread_expression: ($) => prec.right(PREC.unary, seq('...', $._expression)),
map_init_expression: ($) =>
prec(
PREC.composite_literal,
seq('{', repeat(seq($.map_keyed_element, optional(list_separator))), '}'),
),
map_keyed_element: ($) => seq(field('key', $._expression), ':', field('value', $._expression)),
array_creation: ($) => prec.right(PREC.multiplicative, $._array),
fixed_array_creation: ($) => prec.right(PREC.multiplicative, seq($._array, '!')),
_array: ($) => seq('[', repeat(seq($._expression, optional(','))), ']'),
selector_expression: ($) =>
prec.dynamic(
-1,
prec(
PREC.primary,
seq(
field('operand', $._expression),
choice('.', '?.'),
field('field', choice($.reference_expression, $.compile_time_selector_expression)),
),
),
),
compile_time_selector_expression: ($) =>
seq(
token.immediate('$('),
field('field', choice($.reference_expression, $.selector_expression)),
')',
),
index_expression: ($) =>
prec.dynamic(
-1,
prec.right(
PREC.primary,
seq(
field('operand', $._expression),
choice('[', token.immediate('['), token('#[')),
field('index', $._expression),
']',
),
),
),
slice_expression: ($) =>
prec(
PREC.primary,
seq(
field('operand', $._expression),
choice('[', token.immediate('['), token('#[')),
$.range,
']',
),
),
if_expression: ($) =>
seq(
'if',
choice(field('condition', $._expression), field('guard', $.var_declaration)),
field('block', $.block),
optional($.else_branch),
),
else_branch: ($) =>
seq('else', field('else_branch', choice(field('block', $.block), $.if_expression))),
compile_time_if_expression: ($) =>
seq(
'$if',
field('condition', seq($._expression, optional('?'))),
field('block', $.block),
optional(seq('$else', field('else_branch', choice($.block, $.compile_time_if_expression)))),
),
is_expression: ($) =>
prec.dynamic(
2,
seq(
field('left', seq(optional($.mutability_modifiers), $._expression)),
choice('is', '!is'),
field('right', $.plain_type),
),
),
in_expression: ($) =>
prec.left(
PREC.comparative,
seq(field('left', $._expression), choice('in', '!in'), field('right', $._expression)),
),
enum_fetch: ($) => prec.dynamic(-1, seq('.', $.reference_expression)),
match_expression: ($) =>
seq(
'match',
field('condition', choice($._expression, $.mutable_expression)),
'{',
optional($.match_arms),
'}',
),
match_arms: ($) => repeat1(choice($.match_arm, $.match_else_arm_clause)),
match_arm: ($) => seq(field('value', $.match_expression_list), field('block', $.block)),
match_expression_list: ($) =>
sep(
choice($._expression_without_blocks, $.match_arm_type, alias($._definite_range, $.range)),
),
match_arm_type: ($) => $.plain_type,
match_else_arm_clause: ($) => seq('else', field('block', $.block)),
select_expression: ($) =>
seq(
'select',
optional(field('selected_variables', $.expression_list)),
'{',
repeat($.select_arm),
optional($.select_else_arn_clause),
'}',
),
select_arm: ($) => seq($.select_arm_statement, $.block),
select_arm_statement: ($) =>
prec.left(
choice(
alias($.select_var_declaration, $.var_declaration),
$.send_statement,
seq(
alias($.expression_without_blocks_list, $.expression_list),
optional($._select_arm_assignment_statement),
),
),
),
_select_arm_assignment_statement: ($) =>
seq(
choice(...assignment_operators),
alias($.expression_without_blocks_list, $.expression_list),
),
select_var_declaration: ($) =>
prec.left(
seq(
field('var_list', $.identifier_list),
':=',
field('expression_list', alias($.expression_without_blocks_list, $.expression_list)),
),
),
select_else_arn_clause: ($) => seq('else', $.block),
lock_expression: ($) =>
seq(
choice('lock', 'rlock'),
optional(field('locked_variables', $.expression_list)),
field('body', $.block),
),
unsafe_expression: ($) => seq('unsafe', $.block),
// TODO: this should be put into a separate grammar to avoid any "noise"
sql_expression: ($) => prec(PREC.resolve, seq('sql', optional($.identifier), $._content_block)),
// ==================== LITERALS ====================
int_literal: () => token(int_literal),
float_literal: () => token(float_literal),
rune_literal: () =>
token(
seq(
'`',
choice(
/[^'\\]/,
"'",
'"',
seq(
'\\',
choice(
'0',
'`',
seq('x', hex_digit, hex_digit),
seq(octal_digit, octal_digit, octal_digit),
seq('u', hex_digit, hex_digit, hex_digit, hex_digit),
seq(
'U',
hex_digit,
hex_digit,
hex_digit,
hex_digit,
hex_digit,
hex_digit,
hex_digit,
hex_digit,
),
seq(choice('a', 'b', 'e', 'f', 'n', 'r', 't', 'v', '\\', "'", '"')),
),
),
),
'`',
),
),
_string_literal: ($) =>
choice($.interpreted_string_literal, $.c_string_literal, $.raw_string_literal),
interpreted_string_literal: ($) =>
choice(
seq("'", repeat(stringBody(/[^'\\$]+/, $)), "'"),
seq('"', repeat(stringBody(/[^"\\$]+/, $)), '"'),
),
c_string_literal: ($) =>
choice(
seq("c'", repeat(stringBody(/[^'\\$]+/, $)), "'"),
seq('c"', repeat(stringBody(/[^"\\$]+/, $)), '"'),
),
raw_string_literal: (_) =>
choice(
seq("r'", repeat(token.immediate(prec.right(1, /[^']+/))), "'"),
seq('r"', repeat(token.immediate(prec.right(1, /[^"]+/))), '"'),
),
string_interpolation: ($) =>
seq(
alias('${', $.interpolation_opening),
choice(
repeat(alias($._expression, $.interpolation_expression)),
seq(alias($._expression, $.interpolation_expression), $.format_specifier),
),
alias('}', $.interpolation_closing),
),
format_specifier: ($) =>
seq(
token(':'),
choice(
format_flag,
seq(
optional(choice(token(/[+\-]/), token('0'))),
optional($.int_literal),
optional(seq('.', $.int_literal)),
optional(format_flag),
),
),
),
pseudo_compile_time_identifier: ($) =>
token(seq('@', alias(token.immediate(/[A-Z][A-Z0-9_]+/), $.identifier))),
identifier: () =>
token(
seq(
optional('@'),
optional('$'),
optional('C.'),
optional('JS.'),
choice(unicode_letter, '_'),
repeat(choice(letter, unicode_digit)),
),
),
visibility_modifiers: () => prec.left(choice('pub', '__global')),
mutability_modifiers: () =>
prec.left(
PREC.resolve,
choice(seq('mut', optional('static'), optional('volatile')), 'shared'),
),
mutable_identifier: ($) => prec(PREC.resolve, seq($.mutability_modifiers, $.identifier)),
mutable_expression: ($) => prec(PREC.resolve, seq($.mutability_modifiers, $._expression)),
identifier_list: ($) => prec(PREC.and, sep(choice($.mutable_identifier, $.identifier))),
expression_list: ($) => prec(PREC.resolve, sep(choice($._expression, $.mutable_expression))),
expression_without_blocks_list: ($) => prec(PREC.resolve, sep($._expression_without_blocks)),
// ==================== TYPES ====================
// int | string | Foo
sum_type: ($) =>
prec.right(
seq($.plain_type, repeat1(seq(optional(/\s+/), token.immediate('|'), $.plain_type))),
),
plain_type: ($) =>
prec.right(
PREC.primary,
choice($._plain_type_without_special, $.option_type, $.result_type, $.multi_return_type),
),
_plain_type_without_special: ($) =>
prec.right(
PREC.primary,
choice(
$.type_reference_expression,
$.qualified_type,
$.pointer_type,
$.wrong_pointer_type,
$.array_type,
$.fixed_array_type,
$.function_type,
$.generic_type,
$.map_type,
$.channel_type,
$.shared_type,
$.thread_type,
$.atomic_type,
$.anon_struct_type,
),
),
anon_struct_type: ($) => seq('struct', $._struct_body),
multi_return_type: ($) => seq('(', sep($.plain_type), optional(','), ')'),
result_type: ($) => prec.right(seq('!', optional($.plain_type))),
option_type: ($) => prec.right(seq('?', optional($.plain_type))),
qualified_type: ($) =>
seq(field('module', $.reference_expression), '.', field('name', $.type_reference_expression)),
fixed_array_type: ($) =>
seq(
'[',
field('size', choice($.int_literal, $.reference_expression, $.selector_expression)),
']',
field('element', $.plain_type),
),
array_type: ($) => prec.right(PREC.primary, seq('[', ']', field('element', $.plain_type))),
pointer_type: ($) => prec(PREC.match_arm_type, seq('&', $.plain_type)),
// In languages like Go, pointers use an asterisk, not an ampersand,
// so this rule is needed to properly parse and then give an error to the user.
wrong_pointer_type: ($) => prec(PREC.match_arm_type, seq('*', $.plain_type)),
map_type: ($) => seq('map[', field('key', $.plain_type), ']', field('value', $.plain_type)),
channel_type: ($) => prec.right(PREC.primary, seq('chan', $.plain_type)),
shared_type: ($) => seq('shared', $.plain_type),
thread_type: ($) => seq('thread', $.plain_type),
atomic_type: ($) => seq('atomic', $.plain_type),
generic_type: ($) =>
seq(choice($.qualified_type, $.type_reference_expression), $.type_parameters),
function_type: ($) => prec.right(seq('fn', field('signature', $.signature))),
// ==================== TYPES END ====================
// ==================== STATEMENTS ====================
_statement: ($) =>
choice(
$.simple_statement,
$.assert_statement,
$.continue_statement,
$.break_statement,
$.return_statement,
$.asm_statement,
$.goto_statement,
$.labeled_statement,
$.defer_statement,
$.for_statement,
$.compile_time_for_statement,
$.send_statement,
$.block,
$.hash_statement,
$.append_statement,
),
simple_statement: ($) =>
choice(
$.var_declaration,
$._expression,
$.assignment_statement,
alias($.strictly_expression_list, $.expression_list),
),
assert_statement: ($) =>
prec.left(seq('assert', $._expression, optional(seq(',', $._expression)))),
append_statement: ($) =>
prec(PREC.unary, seq(field('left', $._expression), '<<', field('right', $._expression))),
send_statement: ($) =>
prec.right(
PREC.primary,
seq(field('channel', $._expression), '<-', field('value', $._expression)),
),
var_declaration: ($) =>
prec.right(
seq(
field('var_list', $.expression_list),
':=',
field('expression_list', $.expression_list),
),
),
var_definition_list: ($) => sep($.var_definition),
var_definition: ($) =>
prec(
PREC.type_initializer,
seq(optional(field('modifiers', 'mut')), field('name', $.identifier)),
),
assignment_statement: ($) =>
seq(
field('left', $.expression_list),
field('operator', choice(...assignment_operators)),
field('right', $.expression_list),
),
_block_element: ($) =>
choice(
$._statement,
$.import_list,
$._top_level_declaration
),
block: ($) => seq('{', repeat(seq($._block_element, optional(semi))), '}'),
defer_statement: ($) => seq('defer', $.block),
label_reference: ($) => $.identifier,
goto_statement: ($) => seq('goto', $.label_reference),
break_statement: ($) => prec.right(seq('break', optional($.label_reference))),
continue_statement: ($) => prec.right(seq('continue', optional($.label_reference))),
return_statement: ($) =>
prec.right(seq('return', optional(field('expression_list', $.expression_list)))),
label_definition: ($) => seq($.identifier, ':'),
labeled_statement: ($) => prec.right(seq($.label_definition, optional($._statement))),
compile_time_for_statement: ($) => seq('$for', $.range_clause, field('body', $.block)),
for_statement: ($) =>
seq(
'for',
optional(choice($.range_clause, $.for_clause, $.is_clause, $._expression)),
field('body', $.block),
),
is_clause: ($) =>
prec(PREC.primary, seq(optional(alias('mut', $.mutability_modifiers)), $.is_expression)),
range_clause: ($) =>
prec.left(
PREC.primary,
seq(
field('left', $.var_definition_list),
'in',
field('right', choice(alias($._definite_range, $.range), $._expression)),
),
),
for_clause: ($) =>
prec.left(
seq(
optional(field('initializer', $.simple_statement)),
';',
optional(field('condition', $._expression)),
';',
optional(field('update', $.simple_statement)),
),
),
_definite_range: ($) =>
prec(
PREC.multiplicative,
seq(
field('start', $._expression),
field('operator', choice('..', '...')),
field('end', $._expression),
),
),
range: ($) =>
prec(
PREC.multiplicative,
seq(
optional(field('start', $._expression)),
field('operator', '..'),
optional(field('end', $._expression)),
),
),
hash_statement: () => seq('#', token.immediate(repeat1(/[^\\\r\n]/))),
asm_statement: ($) =>
seq(
'asm',
optional(field('modifiers', choice('volatile', 'goto'))),
optional(field('arch', $.identifier)),
$._content_block
),
// Loose checking for asm and sql statements
_content_block: () => seq('{', token.immediate(prec(1, /[^{}]+/)), '}'),
// ==================== ATTRIBUTES ====================
attributes: ($) => repeat1(seq($.attribute, optional(terminator))),
attribute: ($) =>
seq(
choice('[', '@['),
seq($.attribute_expression, repeat(seq(';', $.attribute_expression))),
']',
),
attribute_expression: ($) => prec(PREC.attributes, choice($.if_attribute, $._plain_attribute)),
// [if some ?]
// @[if some ?]
if_attribute: ($) => prec(PREC.attributes, seq('if', $.reference_expression, optional('?'))),
_plain_attribute: ($) => choice($.literal_attribute, $.value_attribute, $.key_value_attribute),
// ['/query']
// @['/query']
literal_attribute: ($) => prec(PREC.attributes, $.literal),
value_attribute: ($) =>
prec(
PREC.attributes,
field('name', choice(alias('unsafe', $.reference_expression), $.reference_expression)),
),
// [key]
// [key: value]
// @[key]
// @[key: value]
key_value_attribute: ($) =>
prec(
PREC.attributes,
seq($.value_attribute, ':', field('value', choice($.literal, $.identifier))),
),
},
});
/**
* Creates a comma separated rule sequence to match one or more of the passed rule.
*
* @param {RuleOrLiteral} rule
*
* @return {SeqRule}
*
*/
function sep(rule) {
return seq(rule, repeat(seq(',', rule)));
}
/**
*
* @param {RegExp} re
* @param {$} $
*
* @return {SeqRule}
*
*/
function stringBody(re, $) {
return choice(token.immediate(prec.right(1, re)), '$', $.escape_sequence, $.string_interpolation);
}
/**
* @param {...string} args - One or more regular expression patterns.
*
* @return {PatternRule}
*/
function regexOr(...args) {
const regex = args.length > 1 ? args.join('|') : args[0];
return {
type: 'PATTERN',
value: regex,
};
}
================================================
FILE: tree_sitter_v/package.json
================================================
{
"name": "tree-sitter-v",
"version": "0.0.6",
"private": true,
"description": "V grammar for tree-sitter",
"main": "bindings/node",
"license": "MIT",
"scripts": {
"test": "tree-sitter test",
"generate": "tree-sitter generate && v bindings/generate_types.vsh",
"parse": "tree-sitter parse",
"format": "prettier --write grammar.js"
},
"devDependencies": {
"prettier": "^3.7.4",
"tree-sitter-cli": "0.26.3"
}
}
================================================
FILE: tree_sitter_v/queries/helix.highlights.scm
================================================
[
(line_comment)
(block_comment)
(shebang)
] @comment
(module_clause
(identifier) @namespace)
(import_path
(import_name) @namespace)
(import_alias
(import_name) @namespace)
(enum_fetch
(reference_expression) @constant)
(enum_field_definition
(identifier) @constant)
(global_var_definition
(identifier) @constant)
(compile_time_if_expression
condition: (reference_expression) @constant)
(compile_time_if_expression
condition: (binary_expression
left: (reference_expression) @constant
right: (reference_expression) @constant))
(compile_time_if_expression
condition: (binary_expression
left: (reference_expression) @constant
right: (unary_expression (reference_expression) @constant)))
(label_reference) @label
(parameter_declaration
name: (identifier) @variable.parameter)
(receiver
name: (identifier) @variable.parameter)
(function_declaration
name: (identifier) @function)
(function_declaration
receiver: (receiver)
name: (identifier) @function.method)
(interface_method_definition
name: (identifier) @function.method)
(short_lambda
(reference_expression) @parameter)
(call_expression
name: (selector_expression
field: (reference_expression) @function.method))
(call_expression
name: (reference_expression) @function)
(struct_declaration
name: (identifier) @type)
(enum_declaration
name: (identifier) @type)
(interface_declaration
name: (identifier) @type)
(type_declaration
name: (identifier) @type)
(struct_field_declaration
name: (identifier) @variable.other.member)
(field_name) @variable.other.member
(selector_expression
field: (reference_expression) @variable.other.member)
(int_literal) @constant.numeric.integer
(escape_sequence) @constant.character.escape
[
(c_string_literal)
(raw_string_literal)
(interpreted_string_literal)
(string_interpolation)
(rune_literal)
] @string
(string_interpolation
(interpolation_opening) @punctuation.bracket
(interpolation_expression) @none
(interpolation_closing) @punctuation.bracket)
(attribute) @attribute
[
(type_reference_expression)
] @type
[
(true)
(false)
] @constant.builtin.boolean
[
"pub"
"assert"
"asm"
"defer"
"unsafe"
"sql"
(nil)
(none)
] @keyword
[
"interface"
"enum"
"type"
"union"
"struct"
"module"
] @keyword.storage.type
[
"static"
"const"
"__global"
] @keyword.storage.modifier
[
"mut"
] @keyword.storage.modifier.mut
[
"shared"
"lock"
"rlock"
"spawn"
"break"
"continue"
"go"
] @keyword.control
[
"if"
"$if"
"select"
"else"
"$else"
"match"
] @keyword.control.conditional
[
"for"
] @keyword.control.repeat
[
"goto"
"return"
] @keyword.control.return
[
"fn"
] @keyword.control.function
[
"import"
] @keyword.control.import
[
"as"
"in"
"is"
"or"
] @keyword.operator
[
"."
","
":"
";"
] @punctuation.delimiter
[
"("
")"
"{"
"}"
"["
"]"
] @punctuation.bracket
(array_creation) @punctuation.bracket
[
"++"
"--"
"+"
"-"
"*"
"/"
"%"
"~"
"&"
"|"
"^"
"!"
"&&"
"||"
"!="
"<<"
">>"
"<"
">"
"<="
">="
"+="
"-="
"*="
"/="
"&="
"|="
"^="
"<<="
">>="
"="
":="
"=="
"?"
"<-"
"$"
".."
"..."
] @operator
================================================
FILE: tree_sitter_v/queries/highlights.scm
================================================
(ERROR) @error
[
(line_comment)
(block_comment)
(shebang)
] @comment
(identifier) @variable
(import_path) @variable
(parameter_declaration
name: (identifier) @parameter)
(function_declaration
name: (identifier) @function)
(function_declaration
receiver: (receiver)
name: (identifier) @method)
(short_lambda
(reference_expression) @parameter)
(call_expression
name: (selector_expression
field: (reference_expression) @method))
(type_reference_expression) @type
(pointer_type) @type
(array_type) @type
(field_name) @property
(selector_expression
field: (reference_expression) @property)
(int_literal) @number
(interpreted_string_literal) @string
(rune_literal) @string
(escape_sequence) @string.escape
[
"as"
"asm"
"assert"
;"atomic"
"break"
"const"
"continue"
"defer"
"else"
"enum"
"fn"
"for"
"$for"
"go"
"goto"
"if"
"$if"
"implements"
"import"
"in"
"!in"
"interface"
"is"
"!is"
"lock"
"match"
"module"
"mut"
"or"
"pub"
"return"
"rlock"
"select"
"shared"
"spawn"
"static"
"struct"
"type"
"union"
"unsafe"
] @keyword
[
(true)
(false)
] @boolean
[
"."
","
":"
";"
] @punctuation.delimiter
[
"("
")"
"{"
"}"
"["
"]"
] @punctuation.bracket
(array_creation) @punctuation.bracket
[
"++"
"--"
"+"
"-"
"*"
"/"
"%"
"~"
"&"
"|"
"^"
"!"
"&&"
"||"
"!="
"<<"
">>"
"<"
">"
"<="
">="
"+="
"-="
"*="
"/="
"&="
"|="
"^="
"<<="
">>="
"="
":="
"=="
"?"
"<-"
"$"
".."
"..."
] @operator
================================================
FILE: tree_sitter_v/src/grammar.json
================================================
{
"$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/grammar.schema.json",
"name": "v",
"word": "identifier",
"rules": {
"source_file": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "shebang"
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "module_clause"
},
{
"type": "BLANK"
}
]
},
{
"type": "REPEAT",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "import_list"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "BLANK"
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_top_level_declaration"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "BLANK"
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_statement"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "BLANK"
}
]
}
]
}
]
}
}
]
},
"shebang": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "#!"
},
{
"type": "PATTERN",
"value": ".*"
}
]
},
"line_comment": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "//"
},
{
"type": "PATTERN",
"value": ".*"
}
]
},
"block_comment": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "/*"
},
{
"type": "REPEAT",
"content": {
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "\\*"
},
{
"type": "PATTERN",
"value": "[^*]|[/][^*]|[^*][/]"
}
]
}
},
{
"type": "STRING",
"value": "*/"
}
]
},
"comment": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "line_comment"
},
{
"type": "SYMBOL",
"name": "block_comment"
}
]
},
"module_clause": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "attributes"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "module"
},
{
"type": "SYMBOL",
"name": "identifier"
}
]
},
"import_list": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "REPEAT1",
"content": {
"type": "SYMBOL",
"name": "import_declaration"
}
}
},
"import_declaration": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "import"
},
{
"type": "SYMBOL",
"name": "import_spec"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "STRING",
"value": ";"
}
]
}
]
},
"import_spec": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "import_path"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "import_alias"
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "selective_import_list"
},
{
"type": "BLANK"
}
]
}
]
},
"import_path": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "import_name"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "."
},
{
"type": "SYMBOL",
"name": "import_name"
}
]
}
}
]
},
"import_name": {
"type": "SYMBOL",
"name": "identifier"
},
"import_alias": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "as"
},
{
"type": "SYMBOL",
"name": "import_name"
}
]
},
"selective_import_list": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{"
},
{
"type": "SYMBOL",
"name": "reference_expression"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "reference_expression"
},
{
"type": "BLANK"
}
]
}
]
}
},
{
"type": "STRING",
"value": "}"
}
]
},
"_top_level_declaration": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "const_declaration"
},
{
"type": "SYMBOL",
"name": "global_var_declaration"
},
{
"type": "SYMBOL",
"name": "type_declaration"
},
{
"type": "SYMBOL",
"name": "function_declaration"
},
{
"type": "SYMBOL",
"name": "static_method_declaration"
},
{
"type": "SYMBOL",
"name": "struct_declaration"
},
{
"type": "SYMBOL",
"name": "enum_declaration"
},
{
"type": "SYMBOL",
"name": "interface_declaration"
}
]
},
"const_declaration": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "attributes",
"content": {
"type": "SYMBOL",
"name": "attributes"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "visibility_modifiers"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "const"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "const_definition"
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "("
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "const_definition"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "STRING",
"value": ";"
}
]
}
]
}
},
{
"type": "STRING",
"value": ")"
}
]
}
]
}
]
},
"const_definition": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "STRING",
"value": "="
},
{
"type": "FIELD",
"name": "value",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
},
"global_var_declaration": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "attributes",
"content": {
"type": "SYMBOL",
"name": "attributes"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "__global"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "global_var_definition"
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "("
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "global_var_definition"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "STRING",
"value": ";"
}
]
}
]
}
},
{
"type": "STRING",
"value": ")"
}
]
}
]
}
]
},
"global_var_definition": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "modifiers",
"content": {
"type": "STRING",
"value": "volatile"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "plain_type"
},
{
"type": "SYMBOL",
"name": "_global_var_value"
}
]
}
]
},
"_global_var_value": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "="
},
{
"type": "FIELD",
"name": "value",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
},
"type_declaration": {
"type": "PREC_RIGHT",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "visibility_modifiers"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "type"
},
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "generic_parameters",
"content": {
"type": "SYMBOL",
"name": "generic_parameters"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "="
},
{
"type": "FIELD",
"name": "type",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "sum_type"
},
{
"type": "SYMBOL",
"name": "plain_type"
}
]
}
}
]
}
},
"function_declaration": {
"type": "PREC_RIGHT",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "attributes",
"content": {
"type": "SYMBOL",
"name": "attributes"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "visibility_modifiers"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "fn"
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "receiver",
"content": {
"type": "SYMBOL",
"name": "receiver"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "_function_name"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "generic_parameters",
"content": {
"type": "SYMBOL",
"name": "generic_parameters"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "signature",
"content": {
"type": "SYMBOL",
"name": "signature"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "body",
"content": {
"type": "SYMBOL",
"name": "block"
}
},
{
"type": "BLANK"
}
]
}
]
}
},
"static_method_declaration": {
"type": "PREC_RIGHT",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "attributes",
"content": {
"type": "SYMBOL",
"name": "attributes"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "visibility_modifiers"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "fn"
},
{
"type": "FIELD",
"name": "static_receiver",
"content": {
"type": "SYMBOL",
"name": "static_receiver"
}
},
{
"type": "STRING",
"value": "."
},
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "_function_name"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "generic_parameters",
"content": {
"type": "SYMBOL",
"name": "generic_parameters"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "signature",
"content": {
"type": "SYMBOL",
"name": "signature"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "body",
"content": {
"type": "SYMBOL",
"name": "block"
}
},
{
"type": "BLANK"
}
]
}
]
}
},
"static_receiver": {
"type": "SYMBOL",
"name": "reference_expression"
},
"_function_name": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "identifier"
},
{
"type": "SYMBOL",
"name": "overridable_operator"
}
]
},
"overridable_operator": {
"type": "CHOICE",
"members": [
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "+"
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "-"
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "*"
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "/"
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "%"
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "<"
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": ">"
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "=="
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "!="
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "<="
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": ">="
}
}
]
},
"receiver": {
"type": "PREC",
"value": 7,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "("
},
{
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "mutability",
"content": {
"type": "SYMBOL",
"name": "mutability_modifiers"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "FIELD",
"name": "type",
"content": {
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "_plain_type_without_special"
},
"named": true,
"value": "plain_type"
}
}
]
},
{
"type": "STRING",
"value": ")"
}
]
}
},
"signature": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "parameters",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "parameter_list"
},
{
"type": "SYMBOL",
"name": "type_parameter_list"
}
]
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "result",
"content": {
"type": "SYMBOL",
"name": "plain_type"
}
},
{
"type": "BLANK"
}
]
}
]
}
},
"parameter_list": {
"type": "PREC",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "("
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "variadic_parameter"
},
{
"type": "SEQ",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "parameter_declaration"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "parameter_declaration"
}
]
}
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "variadic_parameter"
}
]
},
{
"type": "BLANK"
}
]
}
]
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": ")"
}
]
}
},
"parameter_declaration": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "mutability",
"content": {
"type": "SYMBOL",
"name": "mutability_modifiers"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "variadic",
"content": {
"type": "STRING",
"value": "..."
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "type",
"content": {
"type": "SYMBOL",
"name": "plain_type"
}
}
]
},
"variadic_parameter": {
"type": "STRING",
"value": "..."
},
"type_parameter_list": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "("
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "type_parameter_declaration"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "type_parameter_declaration"
}
]
}
}
]
},
{
"type": "STRING",
"value": ")"
}
]
},
"type_parameter_declaration": {
"type": "PREC",
"value": 7,
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "mutability_modifiers"
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "variadic",
"content": {
"type": "STRING",
"value": "..."
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "type",
"content": {
"type": "SYMBOL",
"name": "plain_type"
}
}
]
}
},
"generic_parameters": {
"type": "SEQ",
"members": [
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "STRING",
"value": "["
}
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "generic_parameter"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "generic_parameter"
}
]
}
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "]"
}
]
},
"generic_parameter": {
"type": "SYMBOL",
"name": "identifier"
},
"struct_declaration": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "attributes",
"content": {
"type": "SYMBOL",
"name": "attributes"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "visibility_modifiers"
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "struct"
},
{
"type": "STRING",
"value": "union"
}
]
},
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "generic_parameters",
"content": {
"type": "SYMBOL",
"name": "generic_parameters"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "implements"
},
{
"type": "FIELD",
"name": "implements",
"content": {
"type": "SYMBOL",
"name": "implements_clause"
}
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "SYMBOL",
"name": "_struct_body"
}
]
},
"implements_clause": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "type_reference_expression"
},
{
"type": "SYMBOL",
"name": "qualified_type"
}
]
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "type_reference_expression"
},
{
"type": "SYMBOL",
"name": "qualified_type"
}
]
}
]
}
}
]
},
"_struct_body": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "struct_field_scope"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "BLANK"
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "struct_field_declaration"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "BLANK"
}
]
}
]
}
]
}
},
{
"type": "STRING",
"value": "}"
}
]
},
"struct_field_scope": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "pub"
},
{
"type": "STRING",
"value": "mut"
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "pub"
},
{
"type": "STRING",
"value": "mut"
}
]
},
{
"type": "STRING",
"value": "__global"
}
]
},
{
"type": "STRING",
"value": ":"
}
]
},
"struct_field_declaration": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_struct_field_definition"
},
{
"type": "SYMBOL",
"name": "embedded_definition"
}
]
},
"_struct_field_definition": {
"type": "PREC_RIGHT",
"value": 8,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "FIELD",
"name": "type",
"content": {
"type": "SYMBOL",
"name": "plain_type"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "="
},
{
"type": "FIELD",
"name": "default_value",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "attributes",
"content": {
"type": "SYMBOL",
"name": "attribute"
}
},
{
"type": "BLANK"
}
]
}
]
}
},
"embedded_definition": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "type_reference_expression"
},
{
"type": "SYMBOL",
"name": "qualified_type"
},
{
"type": "SYMBOL",
"name": "generic_type"
}
]
},
"enum_declaration": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "attributes",
"content": {
"type": "SYMBOL",
"name": "attributes"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "visibility_modifiers"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "enum"
},
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "enum_backed_type"
},
{
"type": "BLANK"
}
]
},
{
"type": "SYMBOL",
"name": "_enum_body"
}
]
},
"enum_backed_type": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "as"
},
{
"type": "SYMBOL",
"name": "plain_type"
}
]
},
"_enum_body": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "enum_field_definition"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "BLANK"
}
]
}
]
}
},
{
"type": "STRING",
"value": "}"
}
]
},
"enum_field_definition": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "="
},
{
"type": "FIELD",
"name": "value",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "attributes",
"content": {
"type": "SYMBOL",
"name": "attribute"
}
},
{
"type": "BLANK"
}
]
}
]
},
"interface_declaration": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "attributes",
"content": {
"type": "SYMBOL",
"name": "attributes"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "visibility_modifiers"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "interface"
},
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "generic_parameters",
"content": {
"type": "SYMBOL",
"name": "generic_parameters"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "SYMBOL",
"name": "_interface_body"
}
]
},
"_interface_body": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "struct_field_scope"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "BLANK"
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "struct_field_declaration"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "BLANK"
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "interface_method_definition"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "BLANK"
}
]
}
]
}
]
}
},
{
"type": "STRING",
"value": "}"
}
]
},
"interface_method_definition": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "generic_parameters",
"content": {
"type": "SYMBOL",
"name": "generic_parameters"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "signature",
"content": {
"type": "SYMBOL",
"name": "signature"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "attributes",
"content": {
"type": "SYMBOL",
"name": "attribute"
}
},
{
"type": "BLANK"
}
]
}
]
}
},
"_expression": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_expression_without_blocks"
},
{
"type": "SYMBOL",
"name": "_expression_with_blocks"
}
]
},
"_expression_without_blocks": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "parenthesized_expression"
},
{
"type": "SYMBOL",
"name": "go_expression"
},
{
"type": "SYMBOL",
"name": "spawn_expression"
},
{
"type": "SYMBOL",
"name": "call_expression"
},
{
"type": "SYMBOL",
"name": "function_literal"
},
{
"type": "SYMBOL",
"name": "reference_expression"
},
{
"type": "SYMBOL",
"name": "_max_group"
},
{
"type": "SYMBOL",
"name": "array_creation"
},
{
"type": "SYMBOL",
"name": "fixed_array_creation"
},
{
"type": "SYMBOL",
"name": "unary_expression"
},
{
"type": "SYMBOL",
"name": "receive_expression"
},
{
"type": "SYMBOL",
"name": "binary_expression"
},
{
"type": "SYMBOL",
"name": "is_expression"
},
{
"type": "SYMBOL",
"name": "in_expression"
},
{
"type": "SYMBOL",
"name": "index_expression"
},
{
"type": "SYMBOL",
"name": "slice_expression"
},
{
"type": "SYMBOL",
"name": "as_type_cast_expression"
},
{
"type": "SYMBOL",
"name": "selector_expression"
},
{
"type": "SYMBOL",
"name": "enum_fetch"
},
{
"type": "SYMBOL",
"name": "inc_expression"
},
{
"type": "SYMBOL",
"name": "dec_expression"
},
{
"type": "SYMBOL",
"name": "or_block_expression"
},
{
"type": "SYMBOL",
"name": "option_propagation_expression"
},
{
"type": "SYMBOL",
"name": "result_propagation_expression"
}
]
},
"_expression_with_blocks": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "type_initializer"
},
{
"type": "SYMBOL",
"name": "anon_struct_value_expression"
},
{
"type": "SYMBOL",
"name": "if_expression"
},
{
"type": "SYMBOL",
"name": "match_expression"
},
{
"type": "SYMBOL",
"name": "select_expression"
},
{
"type": "SYMBOL",
"name": "sql_expression"
},
{
"type": "SYMBOL",
"name": "lock_expression"
},
{
"type": "SYMBOL",
"name": "unsafe_expression"
},
{
"type": "SYMBOL",
"name": "compile_time_if_expression"
},
{
"type": "SYMBOL",
"name": "map_init_expression"
}
]
},
"strictly_expression_list": {
"type": "PREC",
"value": -2,
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "SYMBOL",
"name": "mutable_expression"
}
]
},
{
"type": "STRING",
"value": ","
},
{
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "SYMBOL",
"name": "mutable_expression"
}
]
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "SYMBOL",
"name": "mutable_expression"
}
]
}
]
}
}
]
}
]
}
},
"inc_expression": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "STRING",
"value": "++"
}
]
},
"dec_expression": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "STRING",
"value": "--"
}
]
},
"or_block_expression": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "SYMBOL",
"name": "or_block"
}
]
},
"option_propagation_expression": {
"type": "PREC",
"value": 9,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "STRING",
"value": "?"
}
]
}
},
"result_propagation_expression": {
"type": "PREC",
"value": 9,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "STRING",
"value": "!"
}
]
}
},
"anon_struct_value_expression": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "struct"
},
{
"type": "STRING",
"value": "{"
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "element_list",
"content": {
"type": "SYMBOL",
"name": "element_list"
}
},
{
"type": "FIELD",
"name": "short_element_list",
"content": {
"type": "SYMBOL",
"name": "short_element_list"
}
}
]
},
{
"type": "STRING",
"value": "}"
}
]
},
"go_expression": {
"type": "PREC_LEFT",
"value": -1,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "go"
},
{
"type": "SYMBOL",
"name": "_expression"
}
]
}
},
"spawn_expression": {
"type": "PREC_LEFT",
"value": -1,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "spawn"
},
{
"type": "SYMBOL",
"name": "_expression"
}
]
}
},
"parenthesized_expression": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "("
},
{
"type": "FIELD",
"name": "expression",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "STRING",
"value": ")"
}
]
},
"call_expression": {
"type": "PREC_RIGHT",
"value": 7,
"content": {
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "function",
"content": {
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "json.decode"
}
}
},
{
"type": "FIELD",
"name": "arguments",
"content": {
"type": "SYMBOL",
"name": "special_argument_list"
}
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "type_parameters",
"content": {
"type": "SYMBOL",
"name": "type_parameters"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "arguments",
"content": {
"type": "SYMBOL",
"name": "argument_list"
}
}
]
}
]
}
},
"type_parameters": {
"type": "PREC_DYNAMIC",
"value": 2,
"content": {
"type": "SEQ",
"members": [
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "STRING",
"value": "["
}
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "plain_type"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "plain_type"
}
]
}
}
]
},
{
"type": "STRING",
"value": "]"
}
]
}
},
"argument_list": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "("
},
{
"type": "CHOICE",
"members": [
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "argument"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "STRING",
"value": ";"
}
]
},
{
"type": "STRING",
"value": ","
}
]
},
{
"type": "BLANK"
}
]
}
]
}
},
{
"type": "SYMBOL",
"name": "short_lambda"
}
]
},
{
"type": "STRING",
"value": ")"
}
]
},
"short_lambda": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "|"
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "reference_expression"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "reference_expression"
}
]
}
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "|"
},
{
"type": "SYMBOL",
"name": "_expression_without_blocks"
}
]
},
"argument": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "SYMBOL",
"name": "mutable_expression"
},
{
"type": "SYMBOL",
"name": "keyed_element"
},
{
"type": "SYMBOL",
"name": "spread_expression"
}
]
},
"special_argument_list": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "("
},
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "_plain_type_without_special"
},
"named": true,
"value": "plain_type"
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "_expression"
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": ")"
}
]
},
"type_initializer": {
"type": "PREC_RIGHT",
"value": 8,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "type",
"content": {
"type": "SYMBOL",
"name": "plain_type"
}
},
{
"type": "FIELD",
"name": "body",
"content": {
"type": "SYMBOL",
"name": "type_initializer_body"
}
}
]
}
},
"type_initializer_body": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "element_list",
"content": {
"type": "SYMBOL",
"name": "element_list"
}
},
{
"type": "FIELD",
"name": "short_element_list",
"content": {
"type": "SYMBOL",
"name": "short_element_list"
}
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "}"
}
]
},
"element_list": {
"type": "REPEAT1",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "spread_expression"
},
{
"type": "SYMBOL",
"name": "keyed_element"
},
{
"type": "SYMBOL",
"name": "reference_expression"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "STRING",
"value": ";"
}
]
},
{
"type": "STRING",
"value": ","
}
]
},
{
"type": "BLANK"
}
]
}
]
}
},
"short_element_list": {
"type": "REPEAT1",
"content": {
"type": "SEQ",
"members": [
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "_expression"
},
"named": true,
"value": "element"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "STRING",
"value": ";"
}
]
},
{
"type": "STRING",
"value": ","
}
]
},
{
"type": "BLANK"
}
]
}
]
}
},
"field_name": {
"type": "SYMBOL",
"name": "reference_expression"
},
"keyed_element": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "key",
"content": {
"type": "SYMBOL",
"name": "field_name"
}
},
{
"type": "STRING",
"value": ":"
},
{
"type": "FIELD",
"name": "value",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
},
"function_literal": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "fn"
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "capture_list",
"content": {
"type": "SYMBOL",
"name": "capture_list"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "generic_parameters",
"content": {
"type": "SYMBOL",
"name": "generic_parameters"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "signature",
"content": {
"type": "SYMBOL",
"name": "signature"
}
},
{
"type": "FIELD",
"name": "body",
"content": {
"type": "SYMBOL",
"name": "block"
}
}
]
}
},
"capture_list": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "["
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "capture"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "capture"
}
]
}
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "]"
}
]
},
"capture": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "mutability_modifiers"
},
{
"type": "BLANK"
}
]
},
{
"type": "SYMBOL",
"name": "reference_expression"
}
]
},
"reference_expression": {
"type": "PREC_LEFT",
"value": 0,
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
"type_reference_expression": {
"type": "PREC_LEFT",
"value": 0,
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
"unary_expression": {
"type": "PREC",
"value": 6,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "operator",
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "+"
},
{
"type": "STRING",
"value": "-"
},
{
"type": "STRING",
"value": "!"
},
{
"type": "STRING",
"value": "~"
},
{
"type": "STRING",
"value": "^"
},
{
"type": "STRING",
"value": "*"
},
{
"type": "STRING",
"value": "&"
}
]
}
},
{
"type": "FIELD",
"name": "operand",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
}
},
"receive_expression": {
"type": "PREC_RIGHT",
"value": 6,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "operator",
"content": {
"type": "STRING",
"value": "<-"
}
},
{
"type": "FIELD",
"name": "operand",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
}
},
"binary_expression": {
"type": "CHOICE",
"members": [
{
"type": "PREC_LEFT",
"value": 5,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "left",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "FIELD",
"name": "operator",
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "*"
},
{
"type": "STRING",
"value": "/"
},
{
"type": "STRING",
"value": "%"
},
{
"type": "STRING",
"value": "<<"
},
{
"type": "STRING",
"value": ">>"
},
{
"type": "STRING",
"value": ">>>"
},
{
"type": "STRING",
"value": "&"
},
{
"type": "STRING",
"value": "&^"
}
]
}
},
{
"type": "FIELD",
"name": "right",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
}
},
{
"type": "PREC_LEFT",
"value": 4,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "left",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "FIELD",
"name": "operator",
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "+"
},
{
"type": "STRING",
"value": "-"
},
{
"type": "STRING",
"value": "|"
},
{
"type": "STRING",
"value": "^"
}
]
}
},
{
"type": "FIELD",
"name": "right",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
}
},
{
"type": "PREC_LEFT",
"value": 3,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "left",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "FIELD",
"name": "operator",
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "=="
},
{
"type": "STRING",
"value": "!="
},
{
"type": "STRING",
"value": "<"
},
{
"type": "STRING",
"value": "<="
},
{
"type": "STRING",
"value": ">"
},
{
"type": "STRING",
"value": ">="
}
]
}
},
{
"type": "FIELD",
"name": "right",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
}
},
{
"type": "PREC_LEFT",
"value": 2,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "left",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "FIELD",
"name": "operator",
"content": {
"type": "STRING",
"value": "&&"
}
},
{
"type": "FIELD",
"name": "right",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
}
},
{
"type": "PREC_LEFT",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "left",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "FIELD",
"name": "operator",
"content": {
"type": "STRING",
"value": "||"
}
},
{
"type": "FIELD",
"name": "right",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
}
}
]
},
"as_type_cast_expression": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "STRING",
"value": "as"
},
{
"type": "SYMBOL",
"name": "plain_type"
}
]
},
"or_block": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "or"
},
{
"type": "FIELD",
"name": "block",
"content": {
"type": "SYMBOL",
"name": "block"
}
}
]
},
"_max_group": {
"type": "PREC_LEFT",
"value": 1,
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "pseudo_compile_time_identifier"
},
{
"type": "SYMBOL",
"name": "literal"
}
]
}
},
"escape_sequence": {
"type": "TOKEN",
"content": {
"type": "PREC",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "\\"
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "u[a-fA-F\\d]{4}"
},
{
"type": "PATTERN",
"value": "U[a-fA-F\\d]{8}"
},
{
"type": "PATTERN",
"value": "x[a-fA-F\\d]{2}"
},
{
"type": "PATTERN",
"value": "\\d{3}"
},
{
"type": "PATTERN",
"value": "\\r?\\n"
},
{
"type": "PATTERN",
"value": "['\"abfrntv$\\\\]"
},
{
"type": "PATTERN",
"value": "\\S"
}
]
}
]
}
}
},
"literal": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "int_literal"
},
{
"type": "SYMBOL",
"name": "float_literal"
},
{
"type": "SYMBOL",
"name": "_string_literal"
},
{
"type": "SYMBOL",
"name": "rune_literal"
},
{
"type": "SYMBOL",
"name": "none"
},
{
"type": "SYMBOL",
"name": "true"
},
{
"type": "SYMBOL",
"name": "false"
},
{
"type": "SYMBOL",
"name": "nil"
}
]
},
"none": {
"type": "STRING",
"value": "none"
},
"true": {
"type": "STRING",
"value": "true"
},
"false": {
"type": "STRING",
"value": "false"
},
"nil": {
"type": "STRING",
"value": "nil"
},
"spread_expression": {
"type": "PREC_RIGHT",
"value": 6,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "..."
},
{
"type": "SYMBOL",
"name": "_expression"
}
]
}
},
"map_init_expression": {
"type": "PREC",
"value": -1,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "map_keyed_element"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "STRING",
"value": ";"
}
]
},
{
"type": "STRING",
"value": ","
}
]
},
{
"type": "BLANK"
}
]
}
]
}
},
{
"type": "STRING",
"value": "}"
}
]
}
},
"map_keyed_element": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "key",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "STRING",
"value": ":"
},
{
"type": "FIELD",
"name": "value",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
},
"array_creation": {
"type": "PREC_RIGHT",
"value": 5,
"content": {
"type": "SYMBOL",
"name": "_array"
}
},
"fixed_array_creation": {
"type": "PREC_RIGHT",
"value": 5,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_array"
},
{
"type": "STRING",
"value": "!"
}
]
}
},
"_array": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "["
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "BLANK"
}
]
}
]
}
},
{
"type": "STRING",
"value": "]"
}
]
},
"selector_expression": {
"type": "PREC_DYNAMIC",
"value": -1,
"content": {
"type": "PREC",
"value": 7,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "operand",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "."
},
{
"type": "STRING",
"value": "?."
}
]
},
{
"type": "FIELD",
"name": "field",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "reference_expression"
},
{
"type": "SYMBOL",
"name": "compile_time_selector_expression"
}
]
}
}
]
}
}
},
"compile_time_selector_expression": {
"type": "SEQ",
"members": [
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "STRING",
"value": "$("
}
},
{
"type": "FIELD",
"name": "field",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "reference_expression"
},
{
"type": "SYMBOL",
"name": "selector_expression"
}
]
}
},
{
"type": "STRING",
"value": ")"
}
]
},
"index_expression": {
"type": "PREC_DYNAMIC",
"value": -1,
"content": {
"type": "PREC_RIGHT",
"value": 7,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "operand",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "["
},
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "STRING",
"value": "["
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "#["
}
}
]
},
{
"type": "FIELD",
"name": "index",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "STRING",
"value": "]"
}
]
}
}
},
"slice_expression": {
"type": "PREC",
"value": 7,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "operand",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "["
},
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "STRING",
"value": "["
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "#["
}
}
]
},
{
"type": "SYMBOL",
"name": "range"
},
{
"type": "STRING",
"value": "]"
}
]
}
},
"if_expression": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "if"
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "condition",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "FIELD",
"name": "guard",
"content": {
"type": "SYMBOL",
"name": "var_declaration"
}
}
]
},
{
"type": "FIELD",
"name": "block",
"content": {
"type": "SYMBOL",
"name": "block"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "else_branch"
},
{
"type": "BLANK"
}
]
}
]
},
"else_branch": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "else"
},
{
"type": "FIELD",
"name": "else_branch",
"content": {
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "block",
"content": {
"type": "SYMBOL",
"name": "block"
}
},
{
"type": "SYMBOL",
"name": "if_expression"
}
]
}
}
]
},
"compile_time_if_expression": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "$if"
},
{
"type": "FIELD",
"name": "condition",
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "?"
},
{
"type": "BLANK"
}
]
}
]
}
},
{
"type": "FIELD",
"name": "block",
"content": {
"type": "SYMBOL",
"name": "block"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "$else"
},
{
"type": "FIELD",
"name": "else_branch",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "block"
},
{
"type": "SYMBOL",
"name": "compile_time_if_expression"
}
]
}
}
]
},
{
"type": "BLANK"
}
]
}
]
},
"is_expression": {
"type": "PREC_DYNAMIC",
"value": 2,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "left",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "mutability_modifiers"
},
{
"type": "BLANK"
}
]
},
{
"type": "SYMBOL",
"name": "_expression"
}
]
}
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "is"
},
{
"type": "STRING",
"value": "!is"
}
]
},
{
"type": "FIELD",
"name": "right",
"content": {
"type": "SYMBOL",
"name": "plain_type"
}
}
]
}
},
"in_expression": {
"type": "PREC_LEFT",
"value": 3,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "left",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "in"
},
{
"type": "STRING",
"value": "!in"
}
]
},
{
"type": "FIELD",
"name": "right",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
}
},
"enum_fetch": {
"type": "PREC_DYNAMIC",
"value": -1,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "."
},
{
"type": "SYMBOL",
"name": "reference_expression"
}
]
}
},
"match_expression": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "match"
},
{
"type": "FIELD",
"name": "condition",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "SYMBOL",
"name": "mutable_expression"
}
]
}
},
{
"type": "STRING",
"value": "{"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "match_arms"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "}"
}
]
},
"match_arms": {
"type": "REPEAT1",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "match_arm"
},
{
"type": "SYMBOL",
"name": "match_else_arm_clause"
}
]
}
},
"match_arm": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "value",
"content": {
"type": "SYMBOL",
"name": "match_expression_list"
}
},
{
"type": "FIELD",
"name": "block",
"content": {
"type": "SYMBOL",
"name": "block"
}
}
]
},
"match_expression_list": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_expression_without_blocks"
},
{
"type": "SYMBOL",
"name": "match_arm_type"
},
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "_definite_range"
},
"named": true,
"value": "range"
}
]
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_expression_without_blocks"
},
{
"type": "SYMBOL",
"name": "match_arm_type"
},
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "_definite_range"
},
"named": true,
"value": "range"
}
]
}
]
}
}
]
},
"match_arm_type": {
"type": "SYMBOL",
"name": "plain_type"
},
"match_else_arm_clause": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "else"
},
{
"type": "FIELD",
"name": "block",
"content": {
"type": "SYMBOL",
"name": "block"
}
}
]
},
"select_expression": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "select"
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "selected_variables",
"content": {
"type": "SYMBOL",
"name": "expression_list"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "select_arm"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "select_else_arn_clause"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "}"
}
]
},
"select_arm": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "select_arm_statement"
},
{
"type": "SYMBOL",
"name": "block"
}
]
},
"select_arm_statement": {
"type": "PREC_LEFT",
"value": 0,
"content": {
"type": "CHOICE",
"members": [
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "select_var_declaration"
},
"named": true,
"value": "var_declaration"
},
{
"type": "SYMBOL",
"name": "send_statement"
},
{
"type": "SEQ",
"members": [
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "expression_without_blocks_list"
},
"named": true,
"value": "expression_list"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_select_arm_assignment_statement"
},
{
"type": "BLANK"
}
]
}
]
}
]
}
},
"_select_arm_assignment_statement": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "*="
},
{
"type": "STRING",
"value": "/="
},
{
"type": "STRING",
"value": "%="
},
{
"type": "STRING",
"value": "<<="
},
{
"type": "STRING",
"value": ">>="
},
{
"type": "STRING",
"value": ">>>="
},
{
"type": "STRING",
"value": "&="
},
{
"type": "STRING",
"value": "&^="
},
{
"type": "STRING",
"value": "+="
},
{
"type": "STRING",
"value": "-="
},
{
"type": "STRING",
"value": "|="
},
{
"type": "STRING",
"value": "^="
},
{
"type": "STRING",
"value": "="
}
]
},
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "expression_without_blocks_list"
},
"named": true,
"value": "expression_list"
}
]
},
"select_var_declaration": {
"type": "PREC_LEFT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "var_list",
"content": {
"type": "SYMBOL",
"name": "identifier_list"
}
},
{
"type": "STRING",
"value": ":="
},
{
"type": "FIELD",
"name": "expression_list",
"content": {
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "expression_without_blocks_list"
},
"named": true,
"value": "expression_list"
}
}
]
}
},
"select_else_arn_clause": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "else"
},
{
"type": "SYMBOL",
"name": "block"
}
]
},
"lock_expression": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "lock"
},
{
"type": "STRING",
"value": "rlock"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "locked_variables",
"content": {
"type": "SYMBOL",
"name": "expression_list"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "body",
"content": {
"type": "SYMBOL",
"name": "block"
}
}
]
},
"unsafe_expression": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "unsafe"
},
{
"type": "SYMBOL",
"name": "block"
}
]
},
"sql_expression": {
"type": "PREC",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "sql"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "identifier"
},
{
"type": "BLANK"
}
]
},
{
"type": "SYMBOL",
"name": "_content_block"
}
]
}
},
"int_literal": {
"type": "TOKEN",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "0"
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "b"
},
{
"type": "STRING",
"value": "B"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[01]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[01]"
}
]
}
}
]
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "0"
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[1-9]"
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9]"
}
]
}
}
]
}
]
},
{
"type": "BLANK"
}
]
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "0"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "o"
},
{
"type": "STRING",
"value": "O"
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-7]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-7]"
}
]
}
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "0"
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "x"
},
{
"type": "STRING",
"value": "X"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
}
]
}
}
]
}
]
}
]
}
},
"float_literal": {
"type": "TOKEN",
"content": {
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9]"
}
]
}
}
]
},
{
"type": "STRING",
"value": "."
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9]"
}
]
}
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "e"
},
{
"type": "STRING",
"value": "E"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "+"
},
{
"type": "STRING",
"value": "-"
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9]"
}
]
}
}
]
}
]
},
{
"type": "BLANK"
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9]"
}
]
}
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "e"
},
{
"type": "STRING",
"value": "E"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "+"
},
{
"type": "STRING",
"value": "-"
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9]"
}
]
}
}
]
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "."
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9]"
}
]
}
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "e"
},
{
"type": "STRING",
"value": "E"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "+"
},
{
"type": "STRING",
"value": "-"
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9]"
}
]
}
}
]
}
]
},
{
"type": "BLANK"
}
]
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "0"
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "x"
},
{
"type": "STRING",
"value": "X"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
}
]
}
}
]
},
{
"type": "STRING",
"value": "."
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
}
]
}
}
]
},
{
"type": "BLANK"
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
}
]
}
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "."
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
}
]
}
}
]
}
]
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "p"
},
{
"type": "STRING",
"value": "P"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "+"
},
{
"type": "STRING",
"value": "-"
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-9]"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "_"
},
{
"type": "BLANK"
}
]
},
{
"type": "PATTERN",
"value": "[0-9]"
}
]
}
}
]
}
]
}
]
}
]
}
},
"rune_literal": {
"type": "TOKEN",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "`"
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[^'\\\\]"
},
{
"type": "STRING",
"value": "'"
},
{
"type": "STRING",
"value": "\""
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "\\"
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "0"
},
{
"type": "STRING",
"value": "`"
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "x"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[0-7]"
},
{
"type": "PATTERN",
"value": "[0-7]"
},
{
"type": "PATTERN",
"value": "[0-7]"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "u"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "U"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
},
{
"type": "PATTERN",
"value": "[0-9a-fA-F]"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "a"
},
{
"type": "STRING",
"value": "b"
},
{
"type": "STRING",
"value": "e"
},
{
"type": "STRING",
"value": "f"
},
{
"type": "STRING",
"value": "n"
},
{
"type": "STRING",
"value": "r"
},
{
"type": "STRING",
"value": "t"
},
{
"type": "STRING",
"value": "v"
},
{
"type": "STRING",
"value": "\\"
},
{
"type": "STRING",
"value": "'"
},
{
"type": "STRING",
"value": "\""
}
]
}
]
}
]
}
]
}
]
},
{
"type": "STRING",
"value": "`"
}
]
}
},
"_string_literal": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "interpreted_string_literal"
},
{
"type": "SYMBOL",
"name": "c_string_literal"
},
{
"type": "SYMBOL",
"name": "raw_string_literal"
}
]
},
"interpreted_string_literal": {
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "'"
},
{
"type": "REPEAT",
"content": {
"type": "CHOICE",
"members": [
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "PREC_RIGHT",
"value": 1,
"content": {
"type": "PATTERN",
"value": "[^'\\\\$]+"
}
}
},
{
"type": "STRING",
"value": "$"
},
{
"type": "SYMBOL",
"name": "escape_sequence"
},
{
"type": "SYMBOL",
"name": "string_interpolation"
}
]
}
},
{
"type": "STRING",
"value": "'"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "\""
},
{
"type": "REPEAT",
"content": {
"type": "CHOICE",
"members": [
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "PREC_RIGHT",
"value": 1,
"content": {
"type": "PATTERN",
"value": "[^\"\\\\$]+"
}
}
},
{
"type": "STRING",
"value": "$"
},
{
"type": "SYMBOL",
"name": "escape_sequence"
},
{
"type": "SYMBOL",
"name": "string_interpolation"
}
]
}
},
{
"type": "STRING",
"value": "\""
}
]
}
]
},
"c_string_literal": {
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "c'"
},
{
"type": "REPEAT",
"content": {
"type": "CHOICE",
"members": [
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "PREC_RIGHT",
"value": 1,
"content": {
"type": "PATTERN",
"value": "[^'\\\\$]+"
}
}
},
{
"type": "STRING",
"value": "$"
},
{
"type": "SYMBOL",
"name": "escape_sequence"
},
{
"type": "SYMBOL",
"name": "string_interpolation"
}
]
}
},
{
"type": "STRING",
"value": "'"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "c\""
},
{
"type": "REPEAT",
"content": {
"type": "CHOICE",
"members": [
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "PREC_RIGHT",
"value": 1,
"content": {
"type": "PATTERN",
"value": "[^\"\\\\$]+"
}
}
},
{
"type": "STRING",
"value": "$"
},
{
"type": "SYMBOL",
"name": "escape_sequence"
},
{
"type": "SYMBOL",
"name": "string_interpolation"
}
]
}
},
{
"type": "STRING",
"value": "\""
}
]
}
]
},
"raw_string_literal": {
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "r'"
},
{
"type": "REPEAT",
"content": {
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "PREC_RIGHT",
"value": 1,
"content": {
"type": "PATTERN",
"value": "[^']+"
}
}
}
},
{
"type": "STRING",
"value": "'"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "r\""
},
{
"type": "REPEAT",
"content": {
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "PREC_RIGHT",
"value": 1,
"content": {
"type": "PATTERN",
"value": "[^\"]+"
}
}
}
},
{
"type": "STRING",
"value": "\""
}
]
}
]
},
"string_interpolation": {
"type": "SEQ",
"members": [
{
"type": "ALIAS",
"content": {
"type": "STRING",
"value": "${"
},
"named": true,
"value": "interpolation_opening"
},
{
"type": "CHOICE",
"members": [
{
"type": "REPEAT",
"content": {
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "_expression"
},
"named": true,
"value": "interpolation_expression"
}
},
{
"type": "SEQ",
"members": [
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "_expression"
},
"named": true,
"value": "interpolation_expression"
},
{
"type": "SYMBOL",
"name": "format_specifier"
}
]
}
]
},
{
"type": "ALIAS",
"content": {
"type": "STRING",
"value": "}"
},
"named": true,
"value": "interpolation_closing"
}
]
},
"format_specifier": {
"type": "SEQ",
"members": [
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": ":"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "TOKEN",
"content": {
"type": "PATTERN",
"value": "[bgGeEfFcdoxXpsS]"
}
},
{
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "TOKEN",
"content": {
"type": "PATTERN",
"value": "[+\\-]"
}
},
{
"type": "TOKEN",
"content": {
"type": "STRING",
"value": "0"
}
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "int_literal"
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "."
},
{
"type": "SYMBOL",
"name": "int_literal"
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "TOKEN",
"content": {
"type": "PATTERN",
"value": "[bgGeEfFcdoxXpsS]"
}
},
{
"type": "BLANK"
}
]
}
]
}
]
}
]
},
"pseudo_compile_time_identifier": {
"type": "TOKEN",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "@"
},
{
"type": "ALIAS",
"content": {
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "PATTERN",
"value": "[A-Z][A-Z0-9_]+"
}
},
"named": true,
"value": "identifier"
}
]
}
},
"identifier": {
"type": "TOKEN",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "@"
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "$"
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "C."
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "JS."
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[a-zA-Zα-ωΑ-Ωµ]"
},
{
"type": "STRING",
"value": "_"
}
]
},
{
"type": "REPEAT",
"content": {
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[a-zA-Zα-ωΑ-Ωµ]"
},
{
"type": "STRING",
"value": "_"
}
]
},
{
"type": "PATTERN",
"value": "[0-9]"
}
]
}
}
]
}
},
"visibility_modifiers": {
"type": "PREC_LEFT",
"value": 0,
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "pub"
},
{
"type": "STRING",
"value": "__global"
}
]
}
},
"mutability_modifiers": {
"type": "PREC_LEFT",
"value": 1,
"content": {
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "mut"
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "static"
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "volatile"
},
{
"type": "BLANK"
}
]
}
]
},
{
"type": "STRING",
"value": "shared"
}
]
}
},
"mutable_identifier": {
"type": "PREC",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "mutability_modifiers"
},
{
"type": "SYMBOL",
"name": "identifier"
}
]
}
},
"mutable_expression": {
"type": "PREC",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "mutability_modifiers"
},
{
"type": "SYMBOL",
"name": "_expression"
}
]
}
},
"identifier_list": {
"type": "PREC",
"value": 2,
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "mutable_identifier"
},
{
"type": "SYMBOL",
"name": "identifier"
}
]
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "mutable_identifier"
},
{
"type": "SYMBOL",
"name": "identifier"
}
]
}
]
}
}
]
}
},
"expression_list": {
"type": "PREC",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "SYMBOL",
"name": "mutable_expression"
}
]
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "SYMBOL",
"name": "mutable_expression"
}
]
}
]
}
}
]
}
},
"expression_without_blocks_list": {
"type": "PREC",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_expression_without_blocks"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "_expression_without_blocks"
}
]
}
}
]
}
},
"sum_type": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "plain_type"
},
{
"type": "REPEAT1",
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "\\s+"
},
{
"type": "BLANK"
}
]
},
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "STRING",
"value": "|"
}
},
{
"type": "SYMBOL",
"name": "plain_type"
}
]
}
}
]
}
},
"plain_type": {
"type": "PREC_RIGHT",
"value": 7,
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_plain_type_without_special"
},
{
"type": "SYMBOL",
"name": "option_type"
},
{
"type": "SYMBOL",
"name": "result_type"
},
{
"type": "SYMBOL",
"name": "multi_return_type"
}
]
}
},
"_plain_type_without_special": {
"type": "PREC_RIGHT",
"value": 7,
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "type_reference_expression"
},
{
"type": "SYMBOL",
"name": "qualified_type"
},
{
"type": "SYMBOL",
"name": "pointer_type"
},
{
"type": "SYMBOL",
"name": "wrong_pointer_type"
},
{
"type": "SYMBOL",
"name": "array_type"
},
{
"type": "SYMBOL",
"name": "fixed_array_type"
},
{
"type": "SYMBOL",
"name": "function_type"
},
{
"type": "SYMBOL",
"name": "generic_type"
},
{
"type": "SYMBOL",
"name": "map_type"
},
{
"type": "SYMBOL",
"name": "channel_type"
},
{
"type": "SYMBOL",
"name": "shared_type"
},
{
"type": "SYMBOL",
"name": "thread_type"
},
{
"type": "SYMBOL",
"name": "atomic_type"
},
{
"type": "SYMBOL",
"name": "anon_struct_type"
}
]
}
},
"anon_struct_type": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "struct"
},
{
"type": "SYMBOL",
"name": "_struct_body"
}
]
},
"multi_return_type": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "("
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "plain_type"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "plain_type"
}
]
}
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": ")"
}
]
},
"result_type": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "!"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "plain_type"
},
{
"type": "BLANK"
}
]
}
]
}
},
"option_type": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "?"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "plain_type"
},
{
"type": "BLANK"
}
]
}
]
}
},
"qualified_type": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "module",
"content": {
"type": "SYMBOL",
"name": "reference_expression"
}
},
{
"type": "STRING",
"value": "."
},
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "type_reference_expression"
}
}
]
},
"fixed_array_type": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "["
},
{
"type": "FIELD",
"name": "size",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "int_literal"
},
{
"type": "SYMBOL",
"name": "reference_expression"
},
{
"type": "SYMBOL",
"name": "selector_expression"
}
]
}
},
{
"type": "STRING",
"value": "]"
},
{
"type": "FIELD",
"name": "element",
"content": {
"type": "SYMBOL",
"name": "plain_type"
}
}
]
},
"array_type": {
"type": "PREC_RIGHT",
"value": 7,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "["
},
{
"type": "STRING",
"value": "]"
},
{
"type": "FIELD",
"name": "element",
"content": {
"type": "SYMBOL",
"name": "plain_type"
}
}
]
}
},
"pointer_type": {
"type": "PREC",
"value": 9,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "&"
},
{
"type": "SYMBOL",
"name": "plain_type"
}
]
}
},
"wrong_pointer_type": {
"type": "PREC",
"value": 9,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "*"
},
{
"type": "SYMBOL",
"name": "plain_type"
}
]
}
},
"map_type": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "map["
},
{
"type": "FIELD",
"name": "key",
"content": {
"type": "SYMBOL",
"name": "plain_type"
}
},
{
"type": "STRING",
"value": "]"
},
{
"type": "FIELD",
"name": "value",
"content": {
"type": "SYMBOL",
"name": "plain_type"
}
}
]
},
"channel_type": {
"type": "PREC_RIGHT",
"value": 7,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "chan"
},
{
"type": "SYMBOL",
"name": "plain_type"
}
]
}
},
"shared_type": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "shared"
},
{
"type": "SYMBOL",
"name": "plain_type"
}
]
},
"thread_type": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "thread"
},
{
"type": "SYMBOL",
"name": "plain_type"
}
]
},
"atomic_type": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "atomic"
},
{
"type": "SYMBOL",
"name": "plain_type"
}
]
},
"generic_type": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "qualified_type"
},
{
"type": "SYMBOL",
"name": "type_reference_expression"
}
]
},
{
"type": "SYMBOL",
"name": "type_parameters"
}
]
},
"function_type": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "fn"
},
{
"type": "FIELD",
"name": "signature",
"content": {
"type": "SYMBOL",
"name": "signature"
}
}
]
}
},
"_statement": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "simple_statement"
},
{
"type": "SYMBOL",
"name": "assert_statement"
},
{
"type": "SYMBOL",
"name": "continue_statement"
},
{
"type": "SYMBOL",
"name": "break_statement"
},
{
"type": "SYMBOL",
"name": "return_statement"
},
{
"type": "SYMBOL",
"name": "asm_statement"
},
{
"type": "SYMBOL",
"name": "goto_statement"
},
{
"type": "SYMBOL",
"name": "labeled_statement"
},
{
"type": "SYMBOL",
"name": "defer_statement"
},
{
"type": "SYMBOL",
"name": "for_statement"
},
{
"type": "SYMBOL",
"name": "compile_time_for_statement"
},
{
"type": "SYMBOL",
"name": "send_statement"
},
{
"type": "SYMBOL",
"name": "block"
},
{
"type": "SYMBOL",
"name": "hash_statement"
},
{
"type": "SYMBOL",
"name": "append_statement"
}
]
},
"simple_statement": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "var_declaration"
},
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "SYMBOL",
"name": "assignment_statement"
},
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "strictly_expression_list"
},
"named": true,
"value": "expression_list"
}
]
},
"assert_statement": {
"type": "PREC_LEFT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "assert"
},
{
"type": "SYMBOL",
"name": "_expression"
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "_expression"
}
]
},
{
"type": "BLANK"
}
]
}
]
}
},
"append_statement": {
"type": "PREC",
"value": 6,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "left",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "STRING",
"value": "<<"
},
{
"type": "FIELD",
"name": "right",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
}
},
"send_statement": {
"type": "PREC_RIGHT",
"value": 7,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "channel",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "STRING",
"value": "<-"
},
{
"type": "FIELD",
"name": "value",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
}
},
"var_declaration": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "var_list",
"content": {
"type": "SYMBOL",
"name": "expression_list"
}
},
{
"type": "STRING",
"value": ":="
},
{
"type": "FIELD",
"name": "expression_list",
"content": {
"type": "SYMBOL",
"name": "expression_list"
}
}
]
}
},
"var_definition_list": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "var_definition"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "var_definition"
}
]
}
}
]
},
"var_definition": {
"type": "PREC",
"value": 8,
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "modifiers",
"content": {
"type": "STRING",
"value": "mut"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
}
]
}
},
"assignment_statement": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "left",
"content": {
"type": "SYMBOL",
"name": "expression_list"
}
},
{
"type": "FIELD",
"name": "operator",
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "*="
},
{
"type": "STRING",
"value": "/="
},
{
"type": "STRING",
"value": "%="
},
{
"type": "STRING",
"value": "<<="
},
{
"type": "STRING",
"value": ">>="
},
{
"type": "STRING",
"value": ">>>="
},
{
"type": "STRING",
"value": "&="
},
{
"type": "STRING",
"value": "&^="
},
{
"type": "STRING",
"value": "+="
},
{
"type": "STRING",
"value": "-="
},
{
"type": "STRING",
"value": "|="
},
{
"type": "STRING",
"value": "^="
},
{
"type": "STRING",
"value": "="
}
]
}
},
{
"type": "FIELD",
"name": "right",
"content": {
"type": "SYMBOL",
"name": "expression_list"
}
}
]
},
"_block_element": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_statement"
},
{
"type": "SYMBOL",
"name": "import_list"
},
{
"type": "SYMBOL",
"name": "_top_level_declaration"
}
]
},
"block": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_block_element"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "STRING",
"value": ";"
}
]
},
{
"type": "BLANK"
}
]
}
]
}
},
{
"type": "STRING",
"value": "}"
}
]
},
"defer_statement": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "defer"
},
{
"type": "SYMBOL",
"name": "block"
}
]
},
"label_reference": {
"type": "SYMBOL",
"name": "identifier"
},
"goto_statement": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "goto"
},
{
"type": "SYMBOL",
"name": "label_reference"
}
]
},
"break_statement": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "break"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "label_reference"
},
{
"type": "BLANK"
}
]
}
]
}
},
"continue_statement": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "continue"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "label_reference"
},
{
"type": "BLANK"
}
]
}
]
}
},
"return_statement": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "return"
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "expression_list",
"content": {
"type": "SYMBOL",
"name": "expression_list"
}
},
{
"type": "BLANK"
}
]
}
]
}
},
"label_definition": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "identifier"
},
{
"type": "STRING",
"value": ":"
}
]
},
"labeled_statement": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "label_definition"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_statement"
},
{
"type": "BLANK"
}
]
}
]
}
},
"compile_time_for_statement": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "$for"
},
{
"type": "SYMBOL",
"name": "range_clause"
},
{
"type": "FIELD",
"name": "body",
"content": {
"type": "SYMBOL",
"name": "block"
}
}
]
},
"for_statement": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "for"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "range_clause"
},
{
"type": "SYMBOL",
"name": "for_clause"
},
{
"type": "SYMBOL",
"name": "is_clause"
},
{
"type": "SYMBOL",
"name": "_expression"
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "body",
"content": {
"type": "SYMBOL",
"name": "block"
}
}
]
},
"is_clause": {
"type": "PREC",
"value": 7,
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "ALIAS",
"content": {
"type": "STRING",
"value": "mut"
},
"named": true,
"value": "mutability_modifiers"
},
{
"type": "BLANK"
}
]
},
{
"type": "SYMBOL",
"name": "is_expression"
}
]
}
},
"range_clause": {
"type": "PREC_LEFT",
"value": 7,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "left",
"content": {
"type": "SYMBOL",
"name": "var_definition_list"
}
},
{
"type": "STRING",
"value": "in"
},
{
"type": "FIELD",
"name": "right",
"content": {
"type": "CHOICE",
"members": [
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "_definite_range"
},
"named": true,
"value": "range"
},
{
"type": "SYMBOL",
"name": "_expression"
}
]
}
}
]
}
},
"for_clause": {
"type": "PREC_LEFT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "initializer",
"content": {
"type": "SYMBOL",
"name": "simple_statement"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": ";"
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "condition",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": ";"
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "update",
"content": {
"type": "SYMBOL",
"name": "simple_statement"
}
},
{
"type": "BLANK"
}
]
}
]
}
},
"_definite_range": {
"type": "PREC",
"value": 5,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "start",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "FIELD",
"name": "operator",
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": ".."
},
{
"type": "STRING",
"value": "..."
}
]
}
},
{
"type": "FIELD",
"name": "end",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
}
]
}
},
"range": {
"type": "PREC",
"value": 5,
"content": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "start",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "operator",
"content": {
"type": "STRING",
"value": ".."
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "end",
"content": {
"type": "SYMBOL",
"name": "_expression"
}
},
{
"type": "BLANK"
}
]
}
]
}
},
"hash_statement": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "#"
},
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "REPEAT1",
"content": {
"type": "PATTERN",
"value": "[^\\\\\\r\\n]"
}
}
}
]
},
"asm_statement": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "asm"
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "modifiers",
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "volatile"
},
{
"type": "STRING",
"value": "goto"
}
]
}
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "arch",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "SYMBOL",
"name": "_content_block"
}
]
},
"_content_block": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{"
},
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "PREC",
"value": 1,
"content": {
"type": "PATTERN",
"value": "[^{}]+"
}
}
},
{
"type": "STRING",
"value": "}"
}
]
},
"attributes": {
"type": "REPEAT1",
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "attribute"
},
{
"type": "CHOICE",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "\n"
},
{
"type": "STRING",
"value": "\r"
},
{
"type": "STRING",
"value": "\r\n"
}
]
},
{
"type": "BLANK"
}
]
}
]
}
},
"attribute": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "["
},
{
"type": "STRING",
"value": "@["
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "attribute_expression"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ";"
},
{
"type": "SYMBOL",
"name": "attribute_expression"
}
]
}
}
]
},
{
"type": "STRING",
"value": "]"
}
]
},
"attribute_expression": {
"type": "PREC",
"value": 10,
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "if_attribute"
},
{
"type": "SYMBOL",
"name": "_plain_attribute"
}
]
}
},
"if_attribute": {
"type": "PREC",
"value": 10,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "if"
},
{
"type": "SYMBOL",
"name": "reference_expression"
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "?"
},
{
"type": "BLANK"
}
]
}
]
}
},
"_plain_attribute": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "literal_attribute"
},
{
"type": "SYMBOL",
"name": "value_attribute"
},
{
"type": "SYMBOL",
"name": "key_value_attribute"
}
]
},
"literal_attribute": {
"type": "PREC",
"value": 10,
"content": {
"type": "SYMBOL",
"name": "literal"
}
},
"value_attribute": {
"type": "PREC",
"value": 10,
"content": {
"type": "FIELD",
"name": "name",
"content": {
"type": "CHOICE",
"members": [
{
"type": "ALIAS",
"content": {
"type": "STRING",
"value": "unsafe"
},
"named": true,
"value": "reference_expression"
},
{
"type": "SYMBOL",
"name": "reference_expression"
}
]
}
}
},
"key_value_attribute": {
"type": "PREC",
"value": 10,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "value_attribute"
},
{
"type": "STRING",
"value": ":"
},
{
"type": "FIELD",
"name": "value",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "literal"
},
{
"type": "SYMBOL",
"name": "identifier"
}
]
}
}
]
}
}
},
"extras": [
{
"type": "PATTERN",
"value": "\\s"
},
{
"type": "SYMBOL",
"name": "line_comment"
},
{
"type": "SYMBOL",
"name": "block_comment"
}
],
"conflicts": [
[
"fixed_array_type",
"_expression_without_blocks"
],
[
"qualified_type",
"_expression_without_blocks"
],
[
"fixed_array_type",
"literal"
],
[
"reference_expression",
"type_reference_expression"
],
[
"is_expression"
],
[
"_expression_without_blocks",
"element_list"
]
],
"precedences": [],
"externals": [],
"inline": [
"_string_literal",
"_top_level_declaration",
"_array"
],
"supertypes": [
"_expression",
"_statement",
"_top_level_declaration",
"_expression_with_blocks"
],
"reserved": {}
}
================================================
FILE: tree_sitter_v/src/node-types.json
================================================
[
{
"type": "_expression",
"named": true,
"subtypes": [
{
"type": "_expression_with_blocks",
"named": true
},
{
"type": "array_creation",
"named": true
},
{
"type": "as_type_cast_expression",
"named": true
},
{
"type": "binary_expression",
"named": true
},
{
"type": "call_expression",
"named": true
},
{
"type": "dec_expression",
"named": true
},
{
"type": "enum_fetch",
"named": true
},
{
"type": "fixed_array_creation",
"named": true
},
{
"type": "function_literal",
"named": true
},
{
"type": "go_expression",
"named": true
},
{
"type": "in_expression",
"named": true
},
{
"type": "inc_expression",
"named": true
},
{
"type": "index_expression",
"named": true
},
{
"type": "is_expression",
"named": true
},
{
"type": "literal",
"named": true
},
{
"type": "option_propagation_expression",
"named": true
},
{
"type": "or_block_expression",
"named": true
},
{
"type": "parenthesized_expression",
"named": true
},
{
"type": "pseudo_compile_time_identifier",
"named": true
},
{
"type": "receive_expression",
"named": true
},
{
"type": "reference_expression",
"named": true
},
{
"type": "result_propagation_expression",
"named": true
},
{
"type": "selector_expression",
"named": true
},
{
"type": "slice_expression",
"named": true
},
{
"type": "spawn_expression",
"named": true
},
{
"type": "unary_expression",
"named": true
}
]
},
{
"type": "_expression_with_blocks",
"named": true,
"subtypes": [
{
"type": "anon_struct_value_expression",
"named": true
},
{
"type": "compile_time_if_expression",
"named": true
},
{
"type": "if_expression",
"named": true
},
{
"type": "lock_expression",
"named": true
},
{
"type": "map_init_expression",
"named": true
},
{
"type": "match_expression",
"named": true
},
{
"type": "select_expression",
"named": true
},
{
"type": "sql_expression",
"named": true
},
{
"type": "type_initializer",
"named": true
},
{
"type": "unsafe_expression",
"named": true
}
]
},
{
"type": "_statement",
"named": true,
"subtypes": [
{
"type": "append_statement",
"named": true
},
{
"type": "asm_statement",
"named": true
},
{
"type": "assert_statement",
"named": true
},
{
"type": "block",
"named": true
},
{
"type": "break_statement",
"named": true
},
{
"type": "compile_time_for_statement",
"named": true
},
{
"type": "continue_statement",
"named": true
},
{
"type": "defer_statement",
"named": true
},
{
"type": "for_statement",
"named": true
},
{
"type": "goto_statement",
"named": true
},
{
"type": "hash_statement",
"named": true
},
{
"type": "labeled_statement",
"named": true
},
{
"type": "return_statement",
"named": true
},
{
"type": "send_statement",
"named": true
},
{
"type": "simple_statement",
"named": true
}
]
},
{
"type": "_top_level_declaration",
"named": true,
"subtypes": [
{
"type": "const_declaration",
"named": true
},
{
"type": "enum_declaration",
"named": true
},
{
"type": "function_declaration",
"named": true
},
{
"type": "global_var_declaration",
"named": true
},
{
"type": "interface_declaration",
"named": true
},
{
"type": "static_method_declaration",
"named": true
},
{
"type": "struct_declaration",
"named": true
},
{
"type": "type_declaration",
"named": true
}
]
},
{
"type": "anon_struct_type",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "struct_field_declaration",
"named": true
},
{
"type": "struct_field_scope",
"named": true
}
]
}
},
{
"type": "anon_struct_value_expression",
"named": true,
"fields": {
"element_list": {
"multiple": false,
"required": false,
"types": [
{
"type": "element_list",
"named": true
}
]
},
"short_element_list": {
"multiple": false,
"required": false,
"types": [
{
"type": "short_element_list",
"named": true
}
]
}
}
},
{
"type": "append_statement",
"named": true,
"fields": {
"left": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"right": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
}
},
{
"type": "argument",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
},
{
"type": "keyed_element",
"named": true
},
{
"type": "mutable_expression",
"named": true
},
{
"type": "spread_expression",
"named": true
}
]
}
},
{
"type": "argument_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "argument",
"named": true
},
{
"type": "short_lambda",
"named": true
}
]
}
},
{
"type": "array_creation",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "_expression",
"named": true
}
]
}
},
{
"type": "array_type",
"named": true,
"fields": {
"element": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
}
},
{
"type": "as_type_cast_expression",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "_expression",
"named": true
},
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "asm_statement",
"named": true,
"fields": {
"arch": {
"multiple": false,
"required": false,
"types": [
{
"type": "identifier",
"named": true
}
]
},
"modifiers": {
"multiple": false,
"required": false,
"types": [
{
"type": "goto",
"named": false
},
{
"type": "volatile",
"named": false
}
]
}
}
},
{
"type": "assert_statement",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
},
{
"type": "assignment_statement",
"named": true,
"fields": {
"left": {
"multiple": false,
"required": true,
"types": [
{
"type": "expression_list",
"named": true
}
]
},
"operator": {
"multiple": false,
"required": true,
"types": [
{
"type": "%=",
"named": false
},
{
"type": "&=",
"named": false
},
{
"type": "&^=",
"named": false
},
{
"type": "*=",
"named": false
},
{
"type": "+=",
"named": false
},
{
"type": "-=",
"named": false
},
{
"type": "/=",
"named": false
},
{
"type": "<<=",
"named": false
},
{
"type": "=",
"named": false
},
{
"type": ">>=",
"named": false
},
{
"type": ">>>=",
"named": false
},
{
"type": "^=",
"named": false
},
{
"type": "|=",
"named": false
}
]
},
"right": {
"multiple": false,
"required": true,
"types": [
{
"type": "expression_list",
"named": true
}
]
}
}
},
{
"type": "atomic_type",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "attribute",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "attribute_expression",
"named": true
}
]
}
},
{
"type": "attribute_expression",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "if_attribute",
"named": true
},
{
"type": "key_value_attribute",
"named": true
},
{
"type": "literal_attribute",
"named": true
},
{
"type": "value_attribute",
"named": true
}
]
}
},
{
"type": "attributes",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "attribute",
"named": true
}
]
}
},
{
"type": "binary_expression",
"named": true,
"fields": {
"left": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"operator": {
"multiple": false,
"required": true,
"types": [
{
"type": "!=",
"named": false
},
{
"type": "%",
"named": false
},
{
"type": "&",
"named": false
},
{
"type": "&&",
"named": false
},
{
"type": "&^",
"named": false
},
{
"type": "*",
"named": false
},
{
"type": "+",
"named": false
},
{
"type": "-",
"named": false
},
{
"type": "/",
"named": false
},
{
"type": "<",
"named": false
},
{
"type": "<<",
"named": false
},
{
"type": "<=",
"named": false
},
{
"type": "==",
"named": false
},
{
"type": ">",
"named": false
},
{
"type": ">=",
"named": false
},
{
"type": ">>",
"named": false
},
{
"type": ">>>",
"named": false
},
{
"type": "^",
"named": false
},
{
"type": "|",
"named": false
},
{
"type": "||",
"named": false
}
]
},
"right": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
}
},
{
"type": "block",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "_statement",
"named": true
},
{
"type": "_top_level_declaration",
"named": true
},
{
"type": "import_list",
"named": true
}
]
}
},
{
"type": "block_comment",
"named": true,
"extra": true,
"fields": {}
},
{
"type": "break_statement",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "label_reference",
"named": true
}
]
}
},
{
"type": "c_string_literal",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "escape_sequence",
"named": true
},
{
"type": "string_interpolation",
"named": true
}
]
}
},
{
"type": "call_expression",
"named": true,
"fields": {
"arguments": {
"multiple": false,
"required": true,
"types": [
{
"type": "argument_list",
"named": true
},
{
"type": "special_argument_list",
"named": true
}
]
},
"function": {
"multiple": false,
"required": false,
"types": [
{
"type": "json.decode",
"named": false
}
]
},
"name": {
"multiple": false,
"required": false,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"type_parameters": {
"multiple": false,
"required": false,
"types": [
{
"type": "type_parameters",
"named": true
}
]
}
}
},
{
"type": "capture",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "mutability_modifiers",
"named": true
},
{
"type": "reference_expression",
"named": true
}
]
}
},
{
"type": "capture_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "capture",
"named": true
}
]
}
},
{
"type": "channel_type",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "compile_time_for_statement",
"named": true,
"fields": {
"body": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
}
},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "range_clause",
"named": true
}
]
}
},
{
"type": "compile_time_if_expression",
"named": true,
"fields": {
"block": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
},
"condition": {
"multiple": true,
"required": true,
"types": [
{
"type": "?",
"named": false
},
{
"type": "_expression",
"named": true
}
]
},
"else_branch": {
"multiple": false,
"required": false,
"types": [
{
"type": "block",
"named": true
},
{
"type": "compile_time_if_expression",
"named": true
}
]
}
}
},
{
"type": "compile_time_selector_expression",
"named": true,
"fields": {
"field": {
"multiple": false,
"required": true,
"types": [
{
"type": "reference_expression",
"named": true
},
{
"type": "selector_expression",
"named": true
}
]
}
}
},
{
"type": "const_declaration",
"named": true,
"fields": {
"attributes": {
"multiple": false,
"required": false,
"types": [
{
"type": "attributes",
"named": true
}
]
}
},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "const_definition",
"named": true
},
{
"type": "visibility_modifiers",
"named": true
}
]
}
},
{
"type": "const_definition",
"named": true,
"fields": {
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
},
"value": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
}
},
{
"type": "continue_statement",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "label_reference",
"named": true
}
]
}
},
{
"type": "dec_expression",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
},
{
"type": "defer_statement",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
}
},
{
"type": "element_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "keyed_element",
"named": true
},
{
"type": "reference_expression",
"named": true
},
{
"type": "spread_expression",
"named": true
}
]
}
},
{
"type": "else_branch",
"named": true,
"fields": {
"block": {
"multiple": false,
"required": false,
"types": [
{
"type": "block",
"named": true
}
]
},
"else_branch": {
"multiple": false,
"required": false,
"types": [
{
"type": "if_expression",
"named": true
}
]
}
}
},
{
"type": "embedded_definition",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "generic_type",
"named": true
},
{
"type": "qualified_type",
"named": true
},
{
"type": "type_reference_expression",
"named": true
}
]
}
},
{
"type": "enum_backed_type",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "enum_declaration",
"named": true,
"fields": {
"attributes": {
"multiple": false,
"required": false,
"types": [
{
"type": "attributes",
"named": true
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "enum_backed_type",
"named": true
},
{
"type": "enum_field_definition",
"named": true
},
{
"type": "visibility_modifiers",
"named": true
}
]
}
},
{
"type": "enum_fetch",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "reference_expression",
"named": true
}
]
}
},
{
"type": "enum_field_definition",
"named": true,
"fields": {
"attributes": {
"multiple": false,
"required": false,
"types": [
{
"type": "attribute",
"named": true
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
},
"value": {
"multiple": false,
"required": false,
"types": [
{
"type": "_expression",
"named": true
}
]
}
}
},
{
"type": "expression_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "_expression",
"named": true
},
{
"type": "mutable_expression",
"named": true
}
]
}
},
{
"type": "field_name",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "reference_expression",
"named": true
}
]
}
},
{
"type": "fixed_array_creation",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "_expression",
"named": true
}
]
}
},
{
"type": "fixed_array_type",
"named": true,
"fields": {
"element": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
},
"size": {
"multiple": false,
"required": true,
"types": [
{
"type": "int_literal",
"named": true
},
{
"type": "reference_expression",
"named": true
},
{
"type": "selector_expression",
"named": true
}
]
}
}
},
{
"type": "for_clause",
"named": true,
"fields": {
"condition": {
"multiple": false,
"required": false,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"initializer": {
"multiple": false,
"required": false,
"types": [
{
"type": "simple_statement",
"named": true
}
]
},
"update": {
"multiple": false,
"required": false,
"types": [
{
"type": "simple_statement",
"named": true
}
]
}
}
},
{
"type": "for_statement",
"named": true,
"fields": {
"body": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
}
},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "_expression",
"named": true
},
{
"type": "for_clause",
"named": true
},
{
"type": "is_clause",
"named": true
},
{
"type": "range_clause",
"named": true
}
]
}
},
{
"type": "format_specifier",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "int_literal",
"named": true
}
]
}
},
{
"type": "function_declaration",
"named": true,
"fields": {
"attributes": {
"multiple": false,
"required": false,
"types": [
{
"type": "attributes",
"named": true
}
]
},
"body": {
"multiple": false,
"required": false,
"types": [
{
"type": "block",
"named": true
}
]
},
"generic_parameters": {
"multiple": false,
"required": false,
"types": [
{
"type": "generic_parameters",
"named": true
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
},
{
"type": "overridable_operator",
"named": true
}
]
},
"receiver": {
"multiple": false,
"required": false,
"types": [
{
"type": "receiver",
"named": true
}
]
},
"signature": {
"multiple": false,
"required": true,
"types": [
{
"type": "signature",
"named": true
}
]
}
},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "visibility_modifiers",
"named": true
}
]
}
},
{
"type": "function_literal",
"named": true,
"fields": {
"body": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
},
"capture_list": {
"multiple": false,
"required": false,
"types": [
{
"type": "capture_list",
"named": true
}
]
},
"generic_parameters": {
"multiple": false,
"required": false,
"types": [
{
"type": "generic_parameters",
"named": true
}
]
},
"signature": {
"multiple": false,
"required": true,
"types": [
{
"type": "signature",
"named": true
}
]
}
}
},
{
"type": "function_type",
"named": true,
"fields": {
"signature": {
"multiple": false,
"required": true,
"types": [
{
"type": "signature",
"named": true
}
]
}
}
},
{
"type": "generic_parameter",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
{
"type": "generic_parameters",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "generic_parameter",
"named": true
}
]
}
},
{
"type": "generic_type",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "qualified_type",
"named": true
},
{
"type": "type_parameters",
"named": true
},
{
"type": "type_reference_expression",
"named": true
}
]
}
},
{
"type": "global_var_declaration",
"named": true,
"fields": {
"attributes": {
"multiple": false,
"required": false,
"types": [
{
"type": "attributes",
"named": true
}
]
}
},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "global_var_definition",
"named": true
}
]
}
},
{
"type": "global_var_definition",
"named": true,
"fields": {
"modifiers": {
"multiple": false,
"required": false,
"types": [
{
"type": "volatile",
"named": false
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
},
"value": {
"multiple": false,
"required": false,
"types": [
{
"type": "_expression",
"named": true
}
]
}
},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "go_expression",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
},
{
"type": "goto_statement",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "label_reference",
"named": true
}
]
}
},
{
"type": "hash_statement",
"named": true,
"fields": {}
},
{
"type": "identifier_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "identifier",
"named": true
},
{
"type": "mutable_identifier",
"named": true
}
]
}
},
{
"type": "if_attribute",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "reference_expression",
"named": true
}
]
}
},
{
"type": "if_expression",
"named": true,
"fields": {
"block": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
},
"condition": {
"multiple": false,
"required": false,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"guard": {
"multiple": false,
"required": false,
"types": [
{
"type": "var_declaration",
"named": true
}
]
}
},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "else_branch",
"named": true
}
]
}
},
{
"type": "implements_clause",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "qualified_type",
"named": true
},
{
"type": "type_reference_expression",
"named": true
}
]
}
},
{
"type": "import_alias",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "import_name",
"named": true
}
]
}
},
{
"type": "import_declaration",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "import_spec",
"named": true
}
]
}
},
{
"type": "import_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "import_declaration",
"named": true
}
]
}
},
{
"type": "import_name",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
{
"type": "import_path",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "import_name",
"named": true
}
]
}
},
{
"type": "import_spec",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "import_alias",
"named": true
},
{
"type": "import_path",
"named": true
},
{
"type": "selective_import_list",
"named": true
}
]
}
},
{
"type": "in_expression",
"named": true,
"fields": {
"left": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"right": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
}
},
{
"type": "inc_expression",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
},
{
"type": "index_expression",
"named": true,
"fields": {
"index": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"operand": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
}
},
{
"type": "interface_declaration",
"named": true,
"fields": {
"attributes": {
"multiple": false,
"required": false,
"types": [
{
"type": "attributes",
"named": true
}
]
},
"generic_parameters": {
"multiple": false,
"required": false,
"types": [
{
"type": "generic_parameters",
"named": true
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "interface_method_definition",
"named": true
},
{
"type": "struct_field_declaration",
"named": true
},
{
"type": "struct_field_scope",
"named": true
},
{
"type": "visibility_modifiers",
"named": true
}
]
}
},
{
"type": "interface_method_definition",
"named": true,
"fields": {
"attributes": {
"multiple": false,
"required": false,
"types": [
{
"type": "attribute",
"named": true
}
]
},
"generic_parameters": {
"multiple": false,
"required": false,
"types": [
{
"type": "generic_parameters",
"named": true
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
},
"signature": {
"multiple": false,
"required": true,
"types": [
{
"type": "signature",
"named": true
}
]
}
}
},
{
"type": "interpreted_string_literal",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "escape_sequence",
"named": true
},
{
"type": "string_interpolation",
"named": true
}
]
}
},
{
"type": "is_clause",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "is_expression",
"named": true
},
{
"type": "mutability_modifiers",
"named": true
}
]
}
},
{
"type": "is_expression",
"named": true,
"fields": {
"left": {
"multiple": true,
"required": true,
"types": [
{
"type": "_expression",
"named": true
},
{
"type": "mutability_modifiers",
"named": true
}
]
},
"right": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
}
},
{
"type": "key_value_attribute",
"named": true,
"fields": {
"value": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
},
{
"type": "literal",
"named": true
}
]
}
},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "value_attribute",
"named": true
}
]
}
},
{
"type": "keyed_element",
"named": true,
"fields": {
"key": {
"multiple": false,
"required": true,
"types": [
{
"type": "field_name",
"named": true
}
]
},
"value": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
}
},
{
"type": "label_definition",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
{
"type": "label_reference",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
{
"type": "labeled_statement",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "_statement",
"named": true
},
{
"type": "label_definition",
"named": true
}
]
}
},
{
"type": "line_comment",
"named": true,
"extra": true,
"fields": {}
},
{
"type": "literal",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "c_string_literal",
"named": true
},
{
"type": "false",
"named": true
},
{
"type": "float_literal",
"named": true
},
{
"type": "int_literal",
"named": true
},
{
"type": "interpreted_string_literal",
"named": true
},
{
"type": "nil",
"named": true
},
{
"type": "none",
"named": true
},
{
"type": "raw_string_literal",
"named": true
},
{
"type": "rune_literal",
"named": true
},
{
"type": "true",
"named": true
}
]
}
},
{
"type": "literal_attribute",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "literal",
"named": true
}
]
}
},
{
"type": "lock_expression",
"named": true,
"fields": {
"body": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
},
"locked_variables": {
"multiple": false,
"required": false,
"types": [
{
"type": "expression_list",
"named": true
}
]
}
}
},
{
"type": "map_init_expression",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "map_keyed_element",
"named": true
}
]
}
},
{
"type": "map_keyed_element",
"named": true,
"fields": {
"key": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"value": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
}
},
{
"type": "map_type",
"named": true,
"fields": {
"key": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
},
"value": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
}
},
{
"type": "match_arm",
"named": true,
"fields": {
"block": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
},
"value": {
"multiple": false,
"required": true,
"types": [
{
"type": "match_expression_list",
"named": true
}
]
}
}
},
{
"type": "match_arm_type",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "match_arms",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "match_arm",
"named": true
},
{
"type": "match_else_arm_clause",
"named": true
}
]
}
},
{
"type": "match_else_arm_clause",
"named": true,
"fields": {
"block": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
}
}
},
{
"type": "match_expression",
"named": true,
"fields": {
"condition": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
},
{
"type": "mutable_expression",
"named": true
}
]
}
},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "match_arms",
"named": true
}
]
}
},
{
"type": "match_expression_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "array_creation",
"named": true
},
{
"type": "as_type_cast_expression",
"named": true
},
{
"type": "binary_expression",
"named": true
},
{
"type": "call_expression",
"named": true
},
{
"type": "dec_expression",
"named": true
},
{
"type": "enum_fetch",
"named": true
},
{
"type": "fixed_array_creation",
"named": true
},
{
"type": "function_literal",
"named": true
},
{
"type": "go_expression",
"named": true
},
{
"type": "in_expression",
"named": true
},
{
"type": "inc_expression",
"named": true
},
{
"type": "index_expression",
"named": true
},
{
"type": "is_expression",
"named": true
},
{
"type": "literal",
"named": true
},
{
"type": "match_arm_type",
"named": true
},
{
"type": "option_propagation_expression",
"named": true
},
{
"type": "or_block_expression",
"named": true
},
{
"type": "parenthesized_expression",
"named": true
},
{
"type": "pseudo_compile_time_identifier",
"named": true
},
{
"type": "range",
"named": true
},
{
"type": "receive_expression",
"named": true
},
{
"type": "reference_expression",
"named": true
},
{
"type": "result_propagation_expression",
"named": true
},
{
"type": "selector_expression",
"named": true
},
{
"type": "slice_expression",
"named": true
},
{
"type": "spawn_expression",
"named": true
},
{
"type": "unary_expression",
"named": true
}
]
}
},
{
"type": "module_clause",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "attributes",
"named": true
},
{
"type": "identifier",
"named": true
}
]
}
},
{
"type": "multi_return_type",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "mutability_modifiers",
"named": true,
"fields": {}
},
{
"type": "mutable_expression",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "_expression",
"named": true
},
{
"type": "mutability_modifiers",
"named": true
}
]
}
},
{
"type": "mutable_identifier",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "identifier",
"named": true
},
{
"type": "mutability_modifiers",
"named": true
}
]
}
},
{
"type": "option_propagation_expression",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
},
{
"type": "option_type",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "or_block",
"named": true,
"fields": {
"block": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
}
}
},
{
"type": "or_block_expression",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "_expression",
"named": true
},
{
"type": "or_block",
"named": true
}
]
}
},
{
"type": "overridable_operator",
"named": true,
"fields": {}
},
{
"type": "parameter_declaration",
"named": true,
"fields": {
"mutability": {
"multiple": false,
"required": false,
"types": [
{
"type": "mutability_modifiers",
"named": true
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
},
"type": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
},
"variadic": {
"multiple": false,
"required": false,
"types": [
{
"type": "...",
"named": false
}
]
}
}
},
{
"type": "parameter_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "parameter_declaration",
"named": true
},
{
"type": "variadic_parameter",
"named": true
}
]
}
},
{
"type": "parenthesized_expression",
"named": true,
"fields": {
"expression": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
}
},
{
"type": "plain_type",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "anon_struct_type",
"named": true
},
{
"type": "array_type",
"named": true
},
{
"type": "atomic_type",
"named": true
},
{
"type": "channel_type",
"named": true
},
{
"type": "fixed_array_type",
"named": true
},
{
"type": "function_type",
"named": true
},
{
"type": "generic_type",
"named": true
},
{
"type": "map_type",
"named": true
},
{
"type": "multi_return_type",
"named": true
},
{
"type": "option_type",
"named": true
},
{
"type": "pointer_type",
"named": true
},
{
"type": "qualified_type",
"named": true
},
{
"type": "result_type",
"named": true
},
{
"type": "shared_type",
"named": true
},
{
"type": "thread_type",
"named": true
},
{
"type": "type_reference_expression",
"named": true
},
{
"type": "wrong_pointer_type",
"named": true
}
]
}
},
{
"type": "pointer_type",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "qualified_type",
"named": true,
"fields": {
"module": {
"multiple": false,
"required": true,
"types": [
{
"type": "reference_expression",
"named": true
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "type_reference_expression",
"named": true
}
]
}
}
},
{
"type": "range",
"named": true,
"fields": {
"end": {
"multiple": false,
"required": false,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"operator": {
"multiple": false,
"required": true,
"types": [
{
"type": "..",
"named": false
},
{
"type": "...",
"named": false
}
]
},
"start": {
"multiple": false,
"required": false,
"types": [
{
"type": "_expression",
"named": true
}
]
}
}
},
{
"type": "range_clause",
"named": true,
"fields": {
"left": {
"multiple": false,
"required": true,
"types": [
{
"type": "var_definition_list",
"named": true
}
]
},
"right": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
},
{
"type": "range",
"named": true
}
]
}
}
},
{
"type": "raw_string_literal",
"named": true,
"fields": {}
},
{
"type": "receive_expression",
"named": true,
"fields": {
"operand": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"operator": {
"multiple": false,
"required": true,
"types": [
{
"type": "<-",
"named": false
}
]
}
}
},
{
"type": "receiver",
"named": true,
"fields": {
"mutability": {
"multiple": false,
"required": false,
"types": [
{
"type": "mutability_modifiers",
"named": true
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
},
"type": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
}
},
{
"type": "reference_expression",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
{
"type": "result_propagation_expression",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
},
{
"type": "result_type",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "return_statement",
"named": true,
"fields": {
"expression_list": {
"multiple": false,
"required": false,
"types": [
{
"type": "expression_list",
"named": true
}
]
}
}
},
{
"type": "select_arm",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "block",
"named": true
},
{
"type": "select_arm_statement",
"named": true
}
]
}
},
{
"type": "select_arm_statement",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "expression_list",
"named": true
},
{
"type": "send_statement",
"named": true
},
{
"type": "var_declaration",
"named": true
}
]
}
},
{
"type": "select_else_arn_clause",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
}
},
{
"type": "select_expression",
"named": true,
"fields": {
"selected_variables": {
"multiple": false,
"required": false,
"types": [
{
"type": "expression_list",
"named": true
}
]
}
},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "select_arm",
"named": true
},
{
"type": "select_else_arn_clause",
"named": true
}
]
}
},
{
"type": "selective_import_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "reference_expression",
"named": true
}
]
}
},
{
"type": "selector_expression",
"named": true,
"fields": {
"field": {
"multiple": false,
"required": true,
"types": [
{
"type": "compile_time_selector_expression",
"named": true
},
{
"type": "reference_expression",
"named": true
}
]
},
"operand": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
}
},
{
"type": "send_statement",
"named": true,
"fields": {
"channel": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"value": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
}
},
{
"type": "shared_type",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "shebang",
"named": true,
"fields": {}
},
{
"type": "short_element_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "element",
"named": true
}
]
}
},
{
"type": "short_lambda",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "array_creation",
"named": true
},
{
"type": "as_type_cast_expression",
"named": true
},
{
"type": "binary_expression",
"named": true
},
{
"type": "call_expression",
"named": true
},
{
"type": "dec_expression",
"named": true
},
{
"type": "enum_fetch",
"named": true
},
{
"type": "fixed_array_creation",
"named": true
},
{
"type": "function_literal",
"named": true
},
{
"type": "go_expression",
"named": true
},
{
"type": "in_expression",
"named": true
},
{
"type": "inc_expression",
"named": true
},
{
"type": "index_expression",
"named": true
},
{
"type": "is_expression",
"named": true
},
{
"type": "literal",
"named": true
},
{
"type": "option_propagation_expression",
"named": true
},
{
"type": "or_block_expression",
"named": true
},
{
"type": "parenthesized_expression",
"named": true
},
{
"type": "pseudo_compile_time_identifier",
"named": true
},
{
"type": "receive_expression",
"named": true
},
{
"type": "reference_expression",
"named": true
},
{
"type": "result_propagation_expression",
"named": true
},
{
"type": "selector_expression",
"named": true
},
{
"type": "slice_expression",
"named": true
},
{
"type": "spawn_expression",
"named": true
},
{
"type": "unary_expression",
"named": true
}
]
}
},
{
"type": "signature",
"named": true,
"fields": {
"parameters": {
"multiple": false,
"required": true,
"types": [
{
"type": "parameter_list",
"named": true
},
{
"type": "type_parameter_list",
"named": true
}
]
},
"result": {
"multiple": false,
"required": false,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
}
},
{
"type": "simple_statement",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
},
{
"type": "assignment_statement",
"named": true
},
{
"type": "expression_list",
"named": true
},
{
"type": "var_declaration",
"named": true
}
]
}
},
{
"type": "slice_expression",
"named": true,
"fields": {
"operand": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "range",
"named": true
}
]
}
},
{
"type": "source_file",
"named": true,
"root": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "_statement",
"named": true
},
{
"type": "_top_level_declaration",
"named": true
},
{
"type": "import_list",
"named": true
},
{
"type": "module_clause",
"named": true
},
{
"type": "shebang",
"named": true
}
]
}
},
{
"type": "spawn_expression",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
},
{
"type": "special_argument_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "_expression",
"named": true
},
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "spread_expression",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
}
},
{
"type": "sql_expression",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
{
"type": "static_method_declaration",
"named": true,
"fields": {
"attributes": {
"multiple": false,
"required": false,
"types": [
{
"type": "attributes",
"named": true
}
]
},
"body": {
"multiple": false,
"required": false,
"types": [
{
"type": "block",
"named": true
}
]
},
"generic_parameters": {
"multiple": false,
"required": false,
"types": [
{
"type": "generic_parameters",
"named": true
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
},
{
"type": "overridable_operator",
"named": true
}
]
},
"signature": {
"multiple": false,
"required": true,
"types": [
{
"type": "signature",
"named": true
}
]
},
"static_receiver": {
"multiple": false,
"required": true,
"types": [
{
"type": "static_receiver",
"named": true
}
]
}
},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "visibility_modifiers",
"named": true
}
]
}
},
{
"type": "static_receiver",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "reference_expression",
"named": true
}
]
}
},
{
"type": "string_interpolation",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "format_specifier",
"named": true
},
{
"type": "interpolation_closing",
"named": true
},
{
"type": "interpolation_expression",
"named": true
},
{
"type": "interpolation_opening",
"named": true
}
]
}
},
{
"type": "struct_declaration",
"named": true,
"fields": {
"attributes": {
"multiple": false,
"required": false,
"types": [
{
"type": "attributes",
"named": true
}
]
},
"generic_parameters": {
"multiple": false,
"required": false,
"types": [
{
"type": "generic_parameters",
"named": true
}
]
},
"implements": {
"multiple": false,
"required": false,
"types": [
{
"type": "implements_clause",
"named": true
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "struct_field_declaration",
"named": true
},
{
"type": "struct_field_scope",
"named": true
},
{
"type": "visibility_modifiers",
"named": true
}
]
}
},
{
"type": "struct_field_declaration",
"named": true,
"fields": {
"attributes": {
"multiple": false,
"required": false,
"types": [
{
"type": "attribute",
"named": true
}
]
},
"default_value": {
"multiple": false,
"required": false,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"name": {
"multiple": false,
"required": false,
"types": [
{
"type": "identifier",
"named": true
}
]
},
"type": {
"multiple": false,
"required": false,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "embedded_definition",
"named": true
}
]
}
},
{
"type": "struct_field_scope",
"named": true,
"fields": {}
},
{
"type": "sum_type",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "thread_type",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "type_declaration",
"named": true,
"fields": {
"generic_parameters": {
"multiple": false,
"required": false,
"types": [
{
"type": "generic_parameters",
"named": true
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
},
"type": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
},
{
"type": "sum_type",
"named": true
}
]
}
},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "visibility_modifiers",
"named": true
}
]
}
},
{
"type": "type_initializer",
"named": true,
"fields": {
"body": {
"multiple": false,
"required": true,
"types": [
{
"type": "type_initializer_body",
"named": true
}
]
},
"type": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
}
},
{
"type": "type_initializer_body",
"named": true,
"fields": {
"element_list": {
"multiple": false,
"required": false,
"types": [
{
"type": "element_list",
"named": true
}
]
},
"short_element_list": {
"multiple": false,
"required": false,
"types": [
{
"type": "short_element_list",
"named": true
}
]
}
}
},
{
"type": "type_parameter_declaration",
"named": true,
"fields": {
"type": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
},
"variadic": {
"multiple": false,
"required": false,
"types": [
{
"type": "...",
"named": false
}
]
}
},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "mutability_modifiers",
"named": true
}
]
}
},
{
"type": "type_parameter_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "type_parameter_declaration",
"named": true
}
]
}
},
{
"type": "type_parameters",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "type_reference_expression",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
{
"type": "unary_expression",
"named": true,
"fields": {
"operand": {
"multiple": false,
"required": true,
"types": [
{
"type": "_expression",
"named": true
}
]
},
"operator": {
"multiple": false,
"required": true,
"types": [
{
"type": "!",
"named": false
},
{
"type": "&",
"named": false
},
{
"type": "*",
"named": false
},
{
"type": "+",
"named": false
},
{
"type": "-",
"named": false
},
{
"type": "^",
"named": false
},
{
"type": "~",
"named": false
}
]
}
}
},
{
"type": "unsafe_expression",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
}
},
{
"type": "value_attribute",
"named": true,
"fields": {
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "reference_expression",
"named": true
}
]
}
}
},
{
"type": "var_declaration",
"named": true,
"fields": {
"expression_list": {
"multiple": false,
"required": true,
"types": [
{
"type": "expression_list",
"named": true
}
]
},
"var_list": {
"multiple": false,
"required": true,
"types": [
{
"type": "expression_list",
"named": true
},
{
"type": "identifier_list",
"named": true
}
]
}
}
},
{
"type": "var_definition",
"named": true,
"fields": {
"modifiers": {
"multiple": false,
"required": false,
"types": [
{
"type": "mut",
"named": false
}
]
},
"name": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
}
},
{
"type": "var_definition_list",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "var_definition",
"named": true
}
]
}
},
{
"type": "variadic_parameter",
"named": true,
"fields": {}
},
{
"type": "visibility_modifiers",
"named": true,
"fields": {}
},
{
"type": "wrong_pointer_type",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "plain_type",
"named": true
}
]
}
},
{
"type": "\n",
"named": false
},
{
"type": "\r",
"named": false
},
{
"type": "\r\n",
"named": false
},
{
"type": "!",
"named": false
},
{
"type": "!=",
"named": false
},
{
"type": "!in",
"named": false
},
{
"type": "!is",
"named": false
},
{
"type": "\"",
"named": false
},
{
"type": "#",
"named": false
},
{
"type": "#!",
"named": false
},
{
"type": "#[",
"named": false
},
{
"type": "$",
"named": false
},
{
"type": "$(",
"named": false
},
{
"type": "$else",
"named": false
},
{
"type": "$for",
"named": false
},
{
"type": "$if",
"named": false
},
{
"type": "%",
"named": false
},
{
"type": "%=",
"named": false
},
{
"type": "&",
"named": false
},
{
"type": "&&",
"named": false
},
{
"type": "&=",
"named": false
},
{
"type": "&^",
"named": false
},
{
"type": "&^=",
"named": false
},
{
"type": "'",
"named": false
},
{
"type": "(",
"named": false
},
{
"type": ")",
"named": false
},
{
"type": "*",
"named": false
},
{
"type": "*/",
"named": false
},
{
"type": "*=",
"named": false
},
{
"type": "+",
"named": false
},
{
"type": "++",
"named": false
},
{
"type": "+=",
"named": false
},
{
"type": ",",
"named": false
},
{
"type": "-",
"named": false
},
{
"type": "--",
"named": false
},
{
"type": "-=",
"named": false
},
{
"type": ".",
"named": false
},
{
"type": "..",
"named": false
},
{
"type": "...",
"named": false
},
{
"type": "/",
"named": false
},
{
"type": "/*",
"named": false
},
{
"type": "//",
"named": false
},
{
"type": "/=",
"named": false
},
{
"type": "0",
"named": false
},
{
"type": ":",
"named": false
},
{
"type": ":=",
"named": false
},
{
"type": ";",
"named": false
},
{
"type": "<",
"named": false
},
{
"type": "<-",
"named": false
},
{
"type": "<<",
"named": false
},
{
"type": "<<=",
"named": false
},
{
"type": "<=",
"named": false
},
{
"type": "=",
"named": false
},
{
"type": "==",
"named": false
},
{
"type": ">",
"named": false
},
{
"type": ">=",
"named": false
},
{
"type": ">>",
"named": false
},
{
"type": ">>=",
"named": false
},
{
"type": ">>>",
"named": false
},
{
"type": ">>>=",
"named": false
},
{
"type": "?",
"named": false
},
{
"type": "?.",
"named": false
},
{
"type": "@[",
"named": false
},
{
"type": "[",
"named": false
},
{
"type": "]",
"named": false
},
{
"type": "^",
"named": false
},
{
"type": "^=",
"named": false
},
{
"type": "__global",
"named": false
},
{
"type": "as",
"named": false
},
{
"type": "asm",
"named": false
},
{
"type": "assert",
"named": false
},
{
"type": "atomic",
"named": false
},
{
"type": "break",
"named": false
},
{
"type": "c\"",
"named": false
},
{
"type": "c'",
"named": false
},
{
"type": "chan",
"named": false
},
{
"type": "const",
"named": false
},
{
"type": "continue",
"named": false
},
{
"type": "defer",
"named": false
},
{
"type": "else",
"named": false
},
{
"type": "enum",
"named": false
},
{
"type": "escape_sequence",
"named": true
},
{
"type": "false",
"named": true
},
{
"type": "float_literal",
"named": true
},
{
"type": "fn",
"named": false
},
{
"type": "for",
"named": false
},
{
"type": "go",
"named": false
},
{
"type": "goto",
"named": false
},
{
"type": "identifier",
"named": true
},
{
"type": "if",
"named": false
},
{
"type": "implements",
"named": false
},
{
"type": "import",
"named": false
},
{
"type": "in",
"named": false
},
{
"type": "int_literal",
"named": true
},
{
"type": "interface",
"named": false
},
{
"type": "interpolation_closing",
"named": true
},
{
"type": "interpolation_opening",
"named": true
},
{
"type": "is",
"named": false
},
{
"type": "json.decode",
"named": false
},
{
"type": "lock",
"named": false
},
{
"type": "map[",
"named": false
},
{
"type": "match",
"named": false
},
{
"type": "module",
"named": false
},
{
"type": "mut",
"named": false
},
{
"type": "nil",
"named": true
},
{
"type": "none",
"named": true
},
{
"type": "or",
"named": false
},
{
"type": "pseudo_compile_time_identifier",
"named": true
},
{
"type": "pub",
"named": false
},
{
"type": "r\"",
"named": false
},
{
"type": "r'",
"named": false
},
{
"type": "return",
"named": false
},
{
"type": "rlock",
"named": false
},
{
"type": "rune_literal",
"named": true
},
{
"type": "select",
"named": false
},
{
"type": "shared",
"named": false
},
{
"type": "spawn",
"named": false
},
{
"type": "sql",
"named": false
},
{
"type": "static",
"named": false
},
{
"type": "struct",
"named": false
},
{
"type": "thread",
"named": false
},
{
"type": "true",
"named": true
},
{
"type": "type",
"named": false
},
{
"type": "union",
"named": false
},
{
"type": "unsafe",
"named": false
},
{
"type": "volatile",
"named": false
},
{
"type": "{",
"named": false
},
{
"type": "|",
"named": false
},
{
"type": "|=",
"named": false
},
{
"type": "||",
"named": false
},
{
"type": "}",
"named": false
},
{
"type": "~",
"named": false
}
]
================================================
FILE: tree_sitter_v/src/parser.c
================================================
[File too large to display: 11.8 MB]
================================================
FILE: tree_sitter_v/src/tree_sitter/alloc.h
================================================
#ifndef TREE_SITTER_ALLOC_H_
#define TREE_SITTER_ALLOC_H_
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
// Allow clients to override allocation functions
#ifdef TREE_SITTER_REUSE_ALLOCATOR
extern void *(*ts_current_malloc)(size_t size);
extern void *(*ts_current_calloc)(size_t count, size_t size);
extern void *(*ts_current_realloc)(void *ptr, size_t size);
extern void (*ts_current_free)(void *ptr);
#ifndef ts_malloc
#define ts_malloc ts_current_malloc
#endif
#ifndef ts_calloc
#define ts_calloc ts_current_calloc
#endif
#ifndef ts_realloc
#define ts_realloc ts_current_realloc
#endif
#ifndef ts_free
#define ts_free ts_current_free
#endif
#else
#ifndef ts_malloc
#define ts_malloc malloc
#endif
#ifndef ts_calloc
#define ts_calloc calloc
#endif
#ifndef ts_realloc
#define ts_realloc realloc
#endif
#ifndef ts_free
#define ts_free free
#endif
#endif
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_ALLOC_H_
================================================
FILE: tree_sitter_v/src/tree_sitter/array.h
================================================
#ifndef TREE_SITTER_ARRAY_H_
#define TREE_SITTER_ARRAY_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "./alloc.h"
#include
#include
#include
#include
#include
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4101)
#elif defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#endif
#define Array(T) \
struct { \
T *contents; \
uint32_t size; \
uint32_t capacity; \
}
/// Initialize an array.
#define array_init(self) \
((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
/// Create an empty array.
#define array_new() \
{ NULL, 0, 0 }
/// Get a pointer to the element at a given `index` in the array.
#define array_get(self, _index) \
(assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
/// Get a pointer to the first element in the array.
#define array_front(self) array_get(self, 0)
/// Get a pointer to the last element in the array.
#define array_back(self) array_get(self, (self)->size - 1)
/// Clear the array, setting its size to zero. Note that this does not free any
/// memory allocated for the array's contents.
#define array_clear(self) ((self)->size = 0)
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
/// less than the array's current capacity, this function has no effect.
#define array_reserve(self, new_capacity) \
_array__reserve((Array *)(self), array_elem_size(self), new_capacity)
/// Free any memory allocated for this array. Note that this does not free any
/// memory allocated for the array's contents.
#define array_delete(self) _array__delete((Array *)(self))
/// Push a new `element` onto the end of the array.
#define array_push(self, element) \
(_array__grow((Array *)(self), 1, array_elem_size(self)), \
(self)->contents[(self)->size++] = (element))
/// Increase the array's size by `count` elements.
/// New elements are zero-initialized.
#define array_grow_by(self, count) \
do { \
if ((count) == 0) break; \
_array__grow((Array *)(self), count, array_elem_size(self)); \
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
(self)->size += (count); \
} while (0)
/// Append all elements from one array to the end of another.
#define array_push_all(self, other) \
array_extend((self), (other)->size, (other)->contents)
/// Append `count` elements to the end of the array, reading their values from the
/// `contents` pointer.
#define array_extend(self, count, contents) \
_array__splice( \
(Array *)(self), array_elem_size(self), (self)->size, \
0, count, contents \
)
/// Remove `old_count` elements from the array starting at the given `index`. At
/// the same index, insert `new_count` new elements, reading their values from the
/// `new_contents` pointer.
#define array_splice(self, _index, old_count, new_count, new_contents) \
_array__splice( \
(Array *)(self), array_elem_size(self), _index, \
old_count, new_count, new_contents \
)
/// Insert one `element` into the array at the given `index`.
#define array_insert(self, _index, element) \
_array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element))
/// Remove one element from the array at the given `index`.
#define array_erase(self, _index) \
_array__erase((Array *)(self), array_elem_size(self), _index)
/// Pop the last element off the array, returning the element by value.
#define array_pop(self) ((self)->contents[--(self)->size])
/// Assign the contents of one array to another, reallocating if necessary.
#define array_assign(self, other) \
_array__assign((Array *)(self), (const Array *)(other), array_elem_size(self))
/// Swap one array with another
#define array_swap(self, other) \
_array__swap((Array *)(self), (Array *)(other))
/// Get the size of the array contents
#define array_elem_size(self) (sizeof *(self)->contents)
/// Search a sorted array for a given `needle` value, using the given `compare`
/// callback to determine the order.
///
/// If an existing element is found to be equal to `needle`, then the `index`
/// out-parameter is set to the existing value's index, and the `exists`
/// out-parameter is set to true. Otherwise, `index` is set to an index where
/// `needle` should be inserted in order to preserve the sorting, and `exists`
/// is set to false.
#define array_search_sorted_with(self, compare, needle, _index, _exists) \
_array__search_sorted(self, 0, compare, , needle, _index, _exists)
/// Search a sorted array for a given `needle` value, using integer comparisons
/// of a given struct field (specified with a leading dot) to determine the order.
///
/// See also `array_search_sorted_with`.
#define array_search_sorted_by(self, field, needle, _index, _exists) \
_array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
/// Insert a given `value` into a sorted array, using the given `compare`
/// callback to determine the order.
#define array_insert_sorted_with(self, compare, value) \
do { \
unsigned _index, _exists; \
array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
if (!_exists) array_insert(self, _index, value); \
} while (0)
/// Insert a given `value` into a sorted array, using integer comparisons of
/// a given struct field (specified with a leading dot) to determine the order.
///
/// See also `array_search_sorted_by`.
#define array_insert_sorted_by(self, field, value) \
do { \
unsigned _index, _exists; \
array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
if (!_exists) array_insert(self, _index, value); \
} while (0)
// Private
typedef Array(void) Array;
/// This is not what you're looking for, see `array_delete`.
static inline void _array__delete(Array *self) {
if (self->contents) {
ts_free(self->contents);
self->contents = NULL;
self->size = 0;
self->capacity = 0;
}
}
/// This is not what you're looking for, see `array_erase`.
static inline void _array__erase(Array *self, size_t element_size,
uint32_t index) {
assert(index < self->size);
char *contents = (char *)self->contents;
memmove(contents + index * element_size, contents + (index + 1) * element_size,
(self->size - index - 1) * element_size);
self->size--;
}
/// This is not what you're looking for, see `array_reserve`.
static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) {
if (new_capacity > self->capacity) {
if (self->contents) {
self->contents = ts_realloc(self->contents, new_capacity * element_size);
} else {
self->contents = ts_malloc(new_capacity * element_size);
}
self->capacity = new_capacity;
}
}
/// This is not what you're looking for, see `array_assign`.
static inline void _array__assign(Array *self, const Array *other, size_t element_size) {
_array__reserve(self, element_size, other->size);
self->size = other->size;
memcpy(self->contents, other->contents, self->size * element_size);
}
/// This is not what you're looking for, see `array_swap`.
static inline void _array__swap(Array *self, Array *other) {
Array swap = *other;
*other = *self;
*self = swap;
}
/// This is not what you're looking for, see `array_push` or `array_grow_by`.
static inline void _array__grow(Array *self, uint32_t count, size_t element_size) {
uint32_t new_size = self->size + count;
if (new_size > self->capacity) {
uint32_t new_capacity = self->capacity * 2;
if (new_capacity < 8) new_capacity = 8;
if (new_capacity < new_size) new_capacity = new_size;
_array__reserve(self, element_size, new_capacity);
}
}
/// This is not what you're looking for, see `array_splice`.
static inline void _array__splice(Array *self, size_t element_size,
uint32_t index, uint32_t old_count,
uint32_t new_count, const void *elements) {
uint32_t new_size = self->size + new_count - old_count;
uint32_t old_end = index + old_count;
uint32_t new_end = index + new_count;
assert(old_end <= self->size);
_array__reserve(self, element_size, new_size);
char *contents = (char *)self->contents;
if (self->size > old_end) {
memmove(
contents + new_end * element_size,
contents + old_end * element_size,
(self->size - old_end) * element_size
);
}
if (new_count > 0) {
if (elements) {
memcpy(
(contents + index * element_size),
elements,
new_count * element_size
);
} else {
memset(
(contents + index * element_size),
0,
new_count * element_size
);
}
}
self->size += new_count - old_count;
}
/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
do { \
*(_index) = start; \
*(_exists) = false; \
uint32_t size = (self)->size - *(_index); \
if (size == 0) break; \
int comparison; \
while (size > 1) { \
uint32_t half_size = size / 2; \
uint32_t mid_index = *(_index) + half_size; \
comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
if (comparison <= 0) *(_index) = mid_index; \
size -= half_size; \
} \
comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
if (comparison == 0) *(_exists) = true; \
else if (comparison < 0) *(_index) += 1; \
} while (0)
/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
/// parameter by reference in order to work with the generic sorting function above.
#define _compare_int(a, b) ((int)*(a) - (int)(b))
#ifdef _MSC_VER
#pragma warning(pop)
#elif defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_ARRAY_H_
================================================
FILE: tree_sitter_v/src/tree_sitter/parser.h
================================================
#ifndef TREE_SITTER_PARSER_H_
#define TREE_SITTER_PARSER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#define ts_builtin_sym_error ((TSSymbol)-1)
#define ts_builtin_sym_end 0
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
#ifndef TREE_SITTER_API_H_
typedef uint16_t TSStateId;
typedef uint16_t TSSymbol;
typedef uint16_t TSFieldId;
typedef struct TSLanguage TSLanguage;
typedef struct TSLanguageMetadata {
uint8_t major_version;
uint8_t minor_version;
uint8_t patch_version;
} TSLanguageMetadata;
#endif
typedef struct {
TSFieldId field_id;
uint8_t child_index;
bool inherited;
} TSFieldMapEntry;
// Used to index the field and supertype maps.
typedef struct {
uint16_t index;
uint16_t length;
} TSMapSlice;
typedef struct {
bool visible;
bool named;
bool supertype;
} TSSymbolMetadata;
typedef struct TSLexer TSLexer;
struct TSLexer {
int32_t lookahead;
TSSymbol result_symbol;
void (*advance)(TSLexer *, bool);
void (*mark_end)(TSLexer *);
uint32_t (*get_column)(TSLexer *);
bool (*is_at_included_range_start)(const TSLexer *);
bool (*eof)(const TSLexer *);
void (*log)(const TSLexer *, const char *, ...);
};
typedef enum {
TSParseActionTypeShift,
TSParseActionTypeReduce,
TSParseActionTypeAccept,
TSParseActionTypeRecover,
} TSParseActionType;
typedef union {
struct {
uint8_t type;
TSStateId state;
bool extra;
bool repetition;
} shift;
struct {
uint8_t type;
uint8_t child_count;
TSSymbol symbol;
int16_t dynamic_precedence;
uint16_t production_id;
} reduce;
uint8_t type;
} TSParseAction;
typedef struct {
uint16_t lex_state;
uint16_t external_lex_state;
} TSLexMode;
typedef struct {
uint16_t lex_state;
uint16_t external_lex_state;
uint16_t reserved_word_set_id;
} TSLexerMode;
typedef union {
TSParseAction action;
struct {
uint8_t count;
bool reusable;
} entry;
} TSParseActionEntry;
typedef struct {
int32_t start;
int32_t end;
} TSCharacterRange;
struct TSLanguage {
uint32_t abi_version;
uint32_t symbol_count;
uint32_t alias_count;
uint32_t token_count;
uint32_t external_token_count;
uint32_t state_count;
uint32_t large_state_count;
uint32_t production_id_count;
uint32_t field_count;
uint16_t max_alias_sequence_length;
const uint16_t *parse_table;
const uint16_t *small_parse_table;
const uint32_t *small_parse_table_map;
const TSParseActionEntry *parse_actions;
const char * const *symbol_names;
const char * const *field_names;
const TSMapSlice *field_map_slices;
const TSFieldMapEntry *field_map_entries;
const TSSymbolMetadata *symbol_metadata;
const TSSymbol *public_symbol_map;
const uint16_t *alias_map;
const TSSymbol *alias_sequences;
const TSLexerMode *lex_modes;
bool (*lex_fn)(TSLexer *, TSStateId);
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
TSSymbol keyword_capture_token;
struct {
const bool *states;
const TSSymbol *symbol_map;
void *(*create)(void);
void (*destroy)(void *);
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
unsigned (*serialize)(void *, char *);
void (*deserialize)(void *, const char *, unsigned);
} external_scanner;
const TSStateId *primary_state_ids;
const char *name;
const TSSymbol *reserved_words;
uint16_t max_reserved_word_set_size;
uint32_t supertype_count;
const TSSymbol *supertype_symbols;
const TSMapSlice *supertype_map_slices;
const TSSymbol *supertype_map_entries;
TSLanguageMetadata metadata;
};
static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
uint32_t index = 0;
uint32_t size = len - index;
while (size > 1) {
uint32_t half_size = size / 2;
uint32_t mid_index = index + half_size;
const TSCharacterRange *range = &ranges[mid_index];
if (lookahead >= range->start && lookahead <= range->end) {
return true;
} else if (lookahead > range->end) {
index = mid_index;
}
size -= half_size;
}
const TSCharacterRange *range = &ranges[index];
return (lookahead >= range->start && lookahead <= range->end);
}
/*
* Lexer Macros
*/
#ifdef _MSC_VER
#define UNUSED __pragma(warning(suppress : 4101))
#else
#define UNUSED __attribute__((unused))
#endif
#define START_LEXER() \
bool result = false; \
bool skip = false; \
UNUSED \
bool eof = false; \
int32_t lookahead; \
goto start; \
next_state: \
lexer->advance(lexer, skip); \
start: \
skip = false; \
lookahead = lexer->lookahead;
#define ADVANCE(state_value) \
{ \
state = state_value; \
goto next_state; \
}
#define ADVANCE_MAP(...) \
{ \
static const uint16_t map[] = { __VA_ARGS__ }; \
for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
if (map[i] == lookahead) { \
state = map[i + 1]; \
goto next_state; \
} \
} \
}
#define SKIP(state_value) \
{ \
skip = true; \
state = state_value; \
goto next_state; \
}
#define ACCEPT_TOKEN(symbol_value) \
result = true; \
lexer->result_symbol = symbol_value; \
lexer->mark_end(lexer);
#define END_STATE() return result;
/*
* Parse Table Macros
*/
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
#define STATE(id) id
#define ACTIONS(id) id
#define SHIFT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = (state_value) \
} \
}}
#define SHIFT_REPEAT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = (state_value), \
.repetition = true \
} \
}}
#define SHIFT_EXTRA() \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.extra = true \
} \
}}
#define REDUCE(symbol_name, children, precedence, prod_id) \
{{ \
.reduce = { \
.type = TSParseActionTypeReduce, \
.symbol = symbol_name, \
.child_count = children, \
.dynamic_precedence = precedence, \
.production_id = prod_id \
}, \
}}
#define RECOVER() \
{{ \
.type = TSParseActionTypeRecover \
}}
#define ACCEPT_INPUT() \
{{ \
.type = TSParseActionTypeAccept \
}}
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_PARSER_H_
================================================
FILE: tree_sitter_v/test/corpus/anon_struct.txt
================================================
================================================================================
Simple anon struct
================================================================================
struct Foo {
inner struct {
name string
}
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(anon_struct_type
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))))))))
================================================================================
Anon struct with modifiers
================================================================================
struct Foo {
inner struct {
pub:
name string
mut:
age int
}
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(anon_struct_type
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))))))))
================================================================================
Anon struct as param type
================================================================================
fn func(arg struct { foo string }) {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(anon_struct_type
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))))))))
(block)))
================================================================================
Anon struct value with short element list
================================================================================
a := struct { 'abc' }
--------------------------------------------------------------------------------
(source_file
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(anon_struct_value_expression
(short_element_list
(element
(literal
(interpreted_string_literal)))))))))
================================================================================
Anon struct value with keyed element list
================================================================================
a := struct {
foo: 'abc'
bar: 'def'
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(anon_struct_value_expression
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(interpreted_string_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(interpreted_string_literal)))))))))
================================================
FILE: tree_sitter_v/test/corpus/array_creation.txt
================================================
================================================================================
Simple array creation
================================================================================
[1, 2, 3]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(array_creation
(literal
(int_literal))
(literal
(int_literal))
(literal
(int_literal)))))
================================================================================
Multiline array creation
================================================================================
[
1,
2,
3
]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(array_creation
(literal
(int_literal))
(literal
(int_literal))
(literal
(int_literal)))))
================================================================================
Multiline array creation with trailing comma
================================================================================
[
1,
2,
3,
]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(array_creation
(literal
(int_literal))
(literal
(int_literal))
(literal
(int_literal)))))
================================================================================
Simple array creation with trailing comma
================================================================================
[1, 2, 3, ]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(array_creation
(literal
(int_literal))
(literal
(int_literal))
(literal
(int_literal)))))
================================================================================
Empty array creation
================================================================================
[]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(array_creation)))
================================================================================
Fixed array creation
================================================================================
[1, 2, 3]!
--------------------------------------------------------------------------------
(source_file
(simple_statement
(fixed_array_creation
(literal
(int_literal))
(literal
(int_literal))
(literal
(int_literal)))))
================================================
FILE: tree_sitter_v/test/corpus/assert_statement.txt
================================================
================================================================================
Simple assert statement
================================================================================
assert 100
--------------------------------------------------------------------------------
(source_file
(assert_statement
(literal
(int_literal))))
================================================================================
Simple assert statement with condition
================================================================================
assert a > b
--------------------------------------------------------------------------------
(source_file
(assert_statement
(binary_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))))
================================================================================
Assert statement with message
================================================================================
assert a > b, 'a should be greater than b'
assert a > b, 'a' + 'should be greater than' + 'b'
assert a > b, '${a} should be greater than ${b}'
assert a > b, '${a} should' + 'be greater than ${b}'
assert a > b, a.str()
assert a > b, a.str() + b.str()
--------------------------------------------------------------------------------
(source_file
(assert_statement
(binary_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(literal
(interpreted_string_literal)))
(assert_statement
(binary_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(binary_expression
(binary_expression
(literal
(interpreted_string_literal))
(literal
(interpreted_string_literal)))
(literal
(interpreted_string_literal))))
(assert_statement
(binary_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(interpolation_closing))
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(interpolation_closing)))))
(assert_statement
(binary_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(binary_expression
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(interpolation_closing))))
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(interpolation_closing))))))
(assert_statement
(binary_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list)))
(assert_statement
(binary_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(binary_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list))
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list)))))
================================================
FILE: tree_sitter_v/test/corpus/attributes.txt
================================================
================================================================================
Simple attribute
================================================================================
[name]
struct Foo {}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(identifier)))
================================================================================
Simple attributes
================================================================================
[name]
[name1]
@[name2]
struct Foo {}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier)))))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier)))))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(identifier)))
================================================================================
If attribute
================================================================================
[if name]
[if name1 ?]
struct Foo {}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(attributes
(attribute
(attribute_expression
(if_attribute
(reference_expression
(identifier)))))
(attribute
(attribute_expression
(if_attribute
(reference_expression
(identifier))))))
(identifier)))
================================================================================
Literal attribute
================================================================================
['hello']
[100]
[true]
struct Foo {}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(attributes
(attribute
(attribute_expression
(literal_attribute
(literal
(interpreted_string_literal)))))
(attribute
(attribute_expression
(literal_attribute
(literal
(int_literal)))))
(attribute
(attribute_expression
(literal_attribute
(literal
(true))))))
(identifier)))
================================================================================
Key value attribute
================================================================================
[key: value]
[key: 'hello']
@[key: 100]
@[key: true]
struct Foo {}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(attributes
(attribute
(attribute_expression
(key_value_attribute
(value_attribute
(reference_expression
(identifier)))
(identifier))))
(attribute
(attribute_expression
(key_value_attribute
(value_attribute
(reference_expression
(identifier)))
(literal
(interpreted_string_literal)))))
(attribute
(attribute_expression
(key_value_attribute
(value_attribute
(reference_expression
(identifier)))
(literal
(int_literal)))))
(attribute
(attribute_expression
(key_value_attribute
(value_attribute
(reference_expression
(identifier)))
(literal
(true))))))
(identifier)))
================================================
FILE: tree_sitter_v/test/corpus/bitshift_left.txt
================================================
================================================================================
Bitshift as expression
================================================================================
a := 10 << 2
--------------------------------------------------------------------------------
(source_file
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(binary_expression
(literal
(int_literal))
(literal
(int_literal)))))))
================================================================================
Bitshift as append statement
================================================================================
arr << 100
--------------------------------------------------------------------------------
(source_file
(append_statement
(reference_expression
(identifier))
(literal
(int_literal))))
================================================
FILE: tree_sitter_v/test/corpus/call_expression.txt
================================================
================================================================================
Simple call expression
================================================================================
foo()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list))))
================================================================================
Simple call expression with argument
================================================================================
foo(100)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))))))
================================================================================
Simple call expression with Option propagation
================================================================================
foo(100)?
--------------------------------------------------------------------------------
(source_file
(simple_statement
(option_propagation_expression
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal))))))))
================================================================================
Simple call expression with Result propagation
================================================================================
foo(100)!
--------------------------------------------------------------------------------
(source_file
(simple_statement
(result_propagation_expression
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal))))))))
================================================================================
Simple call expression with or block
================================================================================
foo(100) or { 100 }
--------------------------------------------------------------------------------
(source_file
(simple_statement
(or_block_expression
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))))
(or_block
(block
(simple_statement
(literal
(int_literal))))))))
================================================================================
Simple call expression with mutable argument
================================================================================
foo(mut name)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(mutable_expression
(mutability_modifiers)
(reference_expression
(identifier))))))))
================================================================================
Simple call expression with arguments
================================================================================
foo(100, 100, 200)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))
(argument
(literal
(int_literal)))
(argument
(literal
(int_literal)))))))
================================================================================
Simple call expression with arguments and trailing comma
================================================================================
foo(100, 100, 200, )
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))
(argument
(literal
(int_literal)))
(argument
(literal
(int_literal)))))))
================================================================================
Simple call expression with arguments on multiple lines
================================================================================
foo(
100,
100,
200
)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))
(argument
(literal
(int_literal)))
(argument
(literal
(int_literal)))))))
================================================================================
Simple call expression with arguments on multiple lines and trailing comma
================================================================================
foo(
100,
100,
200,
)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))
(argument
(literal
(int_literal)))
(argument
(literal
(int_literal)))))))
================================================================================
Simple call expression with last unpacking parameter
================================================================================
foo(100, 100, ...[1, 2, 3])
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))
(argument
(literal
(int_literal)))
(argument
(spread_expression
(array_creation
(literal
(int_literal))
(literal
(int_literal))
(literal
(int_literal)))))))))
================================================================================
Simple call expression with key value argument
================================================================================
foo(name: value)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))))))
================================================================================
Simple call expression with key value arguments
================================================================================
foo(name: value, name2: value2, name3: value3)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))))))
================================================================================
Simple call expression with plain and key value arguments
================================================================================
foo(plain, plain2, name: value, name2: value2, name3: value3)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(reference_expression
(identifier)))
(argument
(reference_expression
(identifier)))
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))))))
================================================================================
Simple call expression with key value arguments on multiple lines
================================================================================
foo(
name: value,
name2: value2,
name3: value3
)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))))))
================================================================================
Simple call expression with key value arguments on multiple lines without commas
================================================================================
foo(
name: value
name2: value2
name3: value3
)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))))))
================================================================================
Simple call expression with key value arguments on multiple lines without commas with plain arg
================================================================================
foo(
plain,
name: value
name2: value2
name3: value3
)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(reference_expression
(identifier)))
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))
(argument
(keyed_element
(field_name
(reference_expression
(identifier)))
(reference_expression
(identifier))))))))
================================================================================
Generic function call with type parameter
================================================================================
foo[int]()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(type_parameters
(plain_type
(type_reference_expression
(identifier))))
(argument_list))))
================================================================================
Generic function call with several type parameters
================================================================================
foo[int, string]()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(type_parameters
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))
(argument_list))))
================================================================================
Qualified call expression
================================================================================
bar.foo()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list))))
================================================================================
Qualified call expression with two selectors
================================================================================
bar.name.foo()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(selector_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(reference_expression
(identifier)))
(argument_list))))
================================================================================
Qualified call expression with index expression
================================================================================
bar.name[0].foo()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(selector_expression
(index_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(literal
(int_literal)))
(reference_expression
(identifier)))
(argument_list))))
================================================================================
Qualified call expression with call expression
================================================================================
bar.name().foo()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(selector_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list))
(reference_expression
(identifier)))
(argument_list))))
================================================================================
Call expression with ...
================================================================================
foo(...name)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(spread_expression
(reference_expression
(identifier))))))))
================================================================================
Call short lambda
================================================================================
foo(|| println(''))
foo(|a| println(a))
foo(|a, b| println('${a}${b}'))
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(short_lambda
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal)))))))))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(short_lambda
(reference_expression
(identifier))
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(reference_expression
(identifier)))))))))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(short_lambda
(reference_expression
(identifier))
(reference_expression
(identifier))
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(interpolation_closing))
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(interpolation_closing))))))))))))
================================================
FILE: tree_sitter_v/test/corpus/channels.txt
================================================
================================================================================
Receive expression
================================================================================
cache = <-cache_chan
--------------------------------------------------------------------------------
(source_file
(simple_statement
(assignment_statement
(expression_list
(reference_expression
(identifier)))
(expression_list
(receive_expression
(reference_expression
(identifier)))))))
================================================================================
Receive expression with propagation
================================================================================
cache = <-cache_chan!
--------------------------------------------------------------------------------
(source_file
(simple_statement
(assignment_statement
(expression_list
(reference_expression
(identifier)))
(expression_list
(receive_expression
(result_propagation_expression
(reference_expression
(identifier))))))))
================================================================================
Receive expression with or block
================================================================================
cache = <-cache_chan or { return }
--------------------------------------------------------------------------------
(source_file
(simple_statement
(assignment_statement
(expression_list
(reference_expression
(identifier)))
(expression_list
(or_block_expression
(receive_expression
(reference_expression
(identifier)))
(or_block
(block
(return_statement))))))))
================================================================================
Send statement
================================================================================
cache <- cache_chan
--------------------------------------------------------------------------------
(source_file
(send_statement
(reference_expression
(identifier))
(reference_expression
(identifier))))
================================================================================
Send statement with propagation
================================================================================
cache <- cache_chan!
--------------------------------------------------------------------------------
(source_file
(send_statement
(reference_expression
(identifier))
(result_propagation_expression
(reference_expression
(identifier)))))
================================================================================
Send statement with or block
================================================================================
cache <- cache_chan or { return }
--------------------------------------------------------------------------------
(source_file
(send_statement
(reference_expression
(identifier))
(or_block_expression
(reference_expression
(identifier))
(or_block
(block
(return_statement))))))
================================================
FILE: tree_sitter_v/test/corpus/comments.txt
================================================
================================================================================
Empty comment
================================================================================
//
/**/
/***/
module foo
--------------------------------------------------------------------------------
(source_file
(line_comment)
(block_comment)
(block_comment)
(module_clause
(identifier)))
================================================================================
Line comment
================================================================================
// Line comment
// Line comment
module foo
--------------------------------------------------------------------------------
(source_file
(line_comment)
(line_comment)
(module_clause
(identifier)))
================================================================================
Multiline comment
================================================================================
/*
Multiline comment
Multiline comment
*/
/*foo**/
module foo
/**foo*/
/**foo**/
--------------------------------------------------------------------------------
(source_file
(block_comment)
(block_comment)
(module_clause
(identifier))
(block_comment)
(block_comment))
================================================================================
Multiline nested comment
================================================================================
/*
/* Nested with ending asterisks **/
/** Nested with starting asterisks */
/* Nested */
*/
// comment /* comment */
module main
/* Recuresively nested
> Level 1
/*
>> Level 2
/*
>>> Level 3
/* Nested */
<<<
*/
<<
*/
<
*/
foo := 'abc'
/**********************************************************************
* Comment
* /*** Nested ***/
**********************************************************************/
--------------------------------------------------------------------------------
(source_file
(block_comment
(block_comment)
(block_comment)
(block_comment))
(line_comment)
(module_clause
(identifier))
(block_comment
(block_comment
(block_comment
(block_comment))))
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(literal
(interpreted_string_literal)))))
(block_comment
(block_comment)))
================================================
FILE: tree_sitter_v/test/corpus/compile_time.txt
================================================
================================================================================
Compile-time call expression
================================================================================
$embed_file('stubs/arrays.v', .zlib)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal)))
(argument
(enum_fetch
(reference_expression
(identifier))))))))
================================================
FILE: tree_sitter_v/test/corpus/compile_time_selector_expression.txt
================================================
================================================================================
Simple compile time selector
================================================================================
name.$(some)
name.$(some.other)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(selector_expression
(reference_expression
(identifier))
(compile_time_selector_expression
(reference_expression
(identifier)))))
(simple_statement
(selector_expression
(reference_expression
(identifier))
(compile_time_selector_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))))))
================================================
FILE: tree_sitter_v/test/corpus/const_declaration.txt
================================================
================================================================================
Simple constant
================================================================================
const name = 100
--------------------------------------------------------------------------------
(source_file
(const_declaration
(const_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Several constants
================================================================================
const name = 100
const other = 100
--------------------------------------------------------------------------------
(source_file
(const_declaration
(const_definition
(identifier)
(literal
(int_literal))))
(const_declaration
(const_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Several constants with visibility modifiers
================================================================================
const name = 100
pub const other = 100
--------------------------------------------------------------------------------
(source_file
(const_declaration
(const_definition
(identifier)
(literal
(int_literal))))
(const_declaration
(visibility_modifiers)
(const_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Multiline constant
================================================================================
const (
name = 100
)
--------------------------------------------------------------------------------
(source_file
(const_declaration
(const_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Multiline constant in one line with error
================================================================================
const ( name = 100 )
--------------------------------------------------------------------------------
(source_file
(const_declaration
(ERROR
(const_definition
(identifier)
(literal
(int_literal))))))
================================================================================
Multiline constants
================================================================================
const (
name = 100
other = 100
)
--------------------------------------------------------------------------------
(source_file
(const_declaration
(const_definition
(identifier)
(literal
(int_literal)))
(const_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Multiline constants with visibility modifiers
================================================================================
pub const (
name = 100
other = 100
)
--------------------------------------------------------------------------------
(source_file
(const_declaration
(visibility_modifiers)
(const_definition
(identifier)
(literal
(int_literal)))
(const_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Simple constant with attribute
================================================================================
[attr]
const name = 100
--------------------------------------------------------------------------------
(source_file
(const_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(const_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Multiline constants with attributes
================================================================================
[attr]
[attr2]
pub const (
name = 100
other = 100
)
--------------------------------------------------------------------------------
(source_file
(const_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier)))))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(visibility_modifiers)
(const_definition
(identifier)
(literal
(int_literal)))
(const_definition
(identifier)
(literal
(int_literal)))))
================================================
FILE: tree_sitter_v/test/corpus/enum_declaration.txt
================================================
================================================================================
Simple enum
================================================================================
enum Colors {
red
green
}
--------------------------------------------------------------------------------
(source_file
(enum_declaration
(identifier)
(enum_field_definition
(identifier))
(enum_field_definition
(identifier))))
================================================================================
Simple empty enum
================================================================================
enum Colors {}
--------------------------------------------------------------------------------
(source_file
(enum_declaration
(identifier)))
================================================================================
Simple public enum
================================================================================
pub enum Colors {
red
green
}
--------------------------------------------------------------------------------
(source_file
(enum_declaration
(visibility_modifiers)
(identifier)
(enum_field_definition
(identifier))
(enum_field_definition
(identifier))))
================================================================================
Enum with backed type
================================================================================
enum Colors as u8 {
red
green
}
--------------------------------------------------------------------------------
(source_file
(enum_declaration
(identifier)
(enum_backed_type
(plain_type
(type_reference_expression
(identifier))))
(enum_field_definition
(identifier))
(enum_field_definition
(identifier))))
================================================================================
Enum with field value
================================================================================
enum Colors {
red
green = 2
}
--------------------------------------------------------------------------------
(source_file
(enum_declaration
(identifier)
(enum_field_definition
(identifier))
(enum_field_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Enum with fields values
================================================================================
enum Colors {
red = 1
green = 2
}
--------------------------------------------------------------------------------
(source_file
(enum_declaration
(identifier)
(enum_field_definition
(identifier)
(literal
(int_literal)))
(enum_field_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Enum with fields bitshift values
================================================================================
enum Colors {
red = 1 << 1
green = 2 << 2
}
--------------------------------------------------------------------------------
(source_file
(enum_declaration
(identifier)
(enum_field_definition
(identifier)
(binary_expression
(literal
(int_literal))
(literal
(int_literal))))
(enum_field_definition
(identifier)
(binary_expression
(literal
(int_literal))
(literal
(int_literal))))))
================================================================================
Enum with fields attributes
================================================================================
enum Colors {
red [attr]
green [attr]
}
--------------------------------------------------------------------------------
(source_file
(enum_declaration
(identifier)
(enum_field_definition
(identifier)
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(enum_field_definition
(identifier)
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))))
================================================================================
Enum with fields attributes and values
================================================================================
enum Colors {
red = 1 [attr]
green = 2 [attr]
}
--------------------------------------------------------------------------------
(source_file
(enum_declaration
(identifier)
(enum_field_definition
(identifier)
(literal
(int_literal))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(enum_field_definition
(identifier)
(literal
(int_literal))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))))
================================================
FILE: tree_sitter_v/test/corpus/enum_fetch.txt
================================================
================================================================================
Simple enum fetch
================================================================================
.red
--------------------------------------------------------------------------------
(source_file
(simple_statement
(enum_fetch
(reference_expression
(identifier)))))
================================================================================
Simple enum fetch in condition
================================================================================
a == .red
--------------------------------------------------------------------------------
(source_file
(simple_statement
(binary_expression
(reference_expression
(identifier))
(enum_fetch
(reference_expression
(identifier))))))
================================================
FILE: tree_sitter_v/test/corpus/error_propagation.txt
================================================
================================================================================
Or block
================================================================================
foo or {}
foo() or {}
foo[1] or {}
if true {} else {} or {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(or_block_expression
(reference_expression
(identifier))
(or_block
(block))))
(simple_statement
(or_block_expression
(call_expression
(reference_expression
(identifier))
(argument_list))
(or_block
(block))))
(simple_statement
(or_block_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(or_block
(block))))
(simple_statement
(or_block_expression
(if_expression
(literal
(true))
(block)
(else_branch
(block)))
(or_block
(block)))))
================================================================================
! propagation
================================================================================
foo!
foo()!
foo[1]!
if true {} else {}!
--------------------------------------------------------------------------------
(source_file
(simple_statement
(result_propagation_expression
(reference_expression
(identifier))))
(simple_statement
(result_propagation_expression
(call_expression
(reference_expression
(identifier))
(argument_list))))
(simple_statement
(result_propagation_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))))
(simple_statement
(result_propagation_expression
(if_expression
(literal
(true))
(block)
(else_branch
(block))))))
================================================================================
? propagation
================================================================================
foo?
foo()?
foo[1]?
if true {} else {}?
--------------------------------------------------------------------------------
(source_file
(simple_statement
(option_propagation_expression
(reference_expression
(identifier))))
(simple_statement
(option_propagation_expression
(call_expression
(reference_expression
(identifier))
(argument_list))))
(simple_statement
(option_propagation_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))))
(simple_statement
(option_propagation_expression
(if_expression
(literal
(true))
(block)
(else_branch
(block))))))
================================================
FILE: tree_sitter_v/test/corpus/expression_list.txt
================================================
================================================================================
Simple expression list
================================================================================
a, b
--------------------------------------------------------------------------------
(source_file
(simple_statement
(expression_list
(reference_expression
(identifier))
(reference_expression
(identifier)))))
================================================================================
Expression list inside if
================================================================================
if s.no_inner {
return '!'
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(block
(return_statement
(expression_list
(literal
(interpreted_string_literal))))))))
================================================
FILE: tree_sitter_v/test/corpus/for_statement.txt
================================================
================================================================================
Simple for in statement with two variables
================================================================================
for _, v in a {
}
--------------------------------------------------------------------------------
(source_file
(for_statement
(range_clause
(var_definition_list
(var_definition
(identifier))
(var_definition
(identifier)))
(reference_expression
(identifier)))
(block)))
================================================================================
Simple for in statement with one variable
================================================================================
for v in a {
}
--------------------------------------------------------------------------------
(source_file
(for_statement
(range_clause
(var_definition_list
(var_definition
(identifier)))
(reference_expression
(identifier)))
(block)))
================================================================================
Simple for in statement with range
================================================================================
for i in 0 .. 10 {
}
--------------------------------------------------------------------------------
(source_file
(for_statement
(range_clause
(var_definition_list
(var_definition
(identifier)))
(range
(literal
(int_literal))
(literal
(int_literal))))
(block)))
================================================================================
Simple for is statement
================================================================================
for v is a {
}
for v !is Bar {
}
--------------------------------------------------------------------------------
(source_file
(for_statement
(is_clause
(is_expression
(reference_expression
(identifier))
(plain_type
(type_reference_expression
(identifier)))))
(block))
(for_statement
(is_clause
(is_expression
(reference_expression
(identifier))
(plain_type
(type_reference_expression
(identifier)))))
(block)))
================================================================================
Simple for mut is statement
================================================================================
for mut v is Foo {
}
--------------------------------------------------------------------------------
(source_file
(for_statement
(is_clause
(mutability_modifiers)
(is_expression
(reference_expression
(identifier))
(plain_type
(type_reference_expression
(identifier)))))
(block)))
================================================================================
Simple C for statement
================================================================================
for i := 0; i < 10; i++ {
}
--------------------------------------------------------------------------------
(source_file
(for_statement
(for_clause
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(literal
(int_literal)))))
(binary_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(simple_statement
(inc_expression
(reference_expression
(identifier)))))
(block)))
================================================================================
Simple C for statement without last statement
================================================================================
for i := 0; i < 10; {
}
--------------------------------------------------------------------------------
(source_file
(for_statement
(for_clause
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(literal
(int_literal)))))
(binary_expression
(reference_expression
(identifier))
(literal
(int_literal))))
(block)))
================================================================================
Simple C for statement without all statements
================================================================================
for ;; {
}
--------------------------------------------------------------------------------
(source_file
(for_statement
(for_clause)
(block)))
================================================================================
Simple infinite for statement
================================================================================
for {
}
--------------------------------------------------------------------------------
(source_file
(for_statement
(block)))
================================================================================
Simple condition for statement
================================================================================
for a > 100 {
}
--------------------------------------------------------------------------------
(source_file
(for_statement
(binary_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(block)))
================================================================================
Compile-time for statement
================================================================================
$for field in T.fields {
}
--------------------------------------------------------------------------------
(source_file
(compile_time_for_statement
(range_clause
(var_definition_list
(var_definition
(identifier)))
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier))))
(block)))
================================================
FILE: tree_sitter_v/test/corpus/function_declaration.txt
================================================
================================================================================
Simple function
================================================================================
fn foo() {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Simple function with param
================================================================================
fn foo(param int) {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
(block)))
================================================================================
Simple function with params
================================================================================
fn foo(param int, param2 string) {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
(block)))
================================================================================
Simple function with params and trailing comma error
================================================================================
fn foo(param int, param2 string,) {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(ERROR)))
(block)))
================================================================================
Simple function with params and last variadic param
================================================================================
fn foo(param int, param2 string, variadic ...[]string) {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(parameter_declaration
(identifier)
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
================================================================================
Simple function with mutable and shared params
================================================================================
fn foo(mut param int, shared param2 string) {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(parameter_declaration
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
(block)))
================================================================================
Simple function with return type
================================================================================
fn foo(mut param int) string {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(type_reference_expression
(identifier))))
(block)))
================================================================================
Simple function with Result return type
================================================================================
fn foo(mut param int) !string {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(result_type
(plain_type
(type_reference_expression
(identifier))))))
(block)))
================================================================================
Simple function with Option return type
================================================================================
fn foo(mut param int) ?string {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(option_type
(plain_type
(type_reference_expression
(identifier))))))
(block)))
================================================================================
Simple function with several return type
================================================================================
fn foo(mut param int) (string, int) {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(multi_return_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))
(block)))
================================================================================
Simple function with several return type and trailing comma
================================================================================
fn foo(mut param int) (string, int,) {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(multi_return_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))
(block)))
================================================================================
Simple function with empty brace return type
================================================================================
fn foo(mut param int) () {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(multi_return_type
(plain_type
(type_reference_expression
(MISSING identifier))))))
(block)))
================================================================================
Simple function with Result of several return type
================================================================================
fn foo(mut param int) !(string, int) {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(result_type
(plain_type
(multi_return_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
================================================================================
Simple function with Option of several return type
================================================================================
fn foo(mut param int) ?(string, int) {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list
(parameter_declaration
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(option_type
(plain_type
(multi_return_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
================================================================================
Simple function without block
================================================================================
fn foo()
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list))))
================================================================================
Simple C function without block
================================================================================
fn C.foo()
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list))))
================================================================================
Simple JS function without block
================================================================================
fn JS.foo()
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list))))
================================================================================
Simple function with attribute
================================================================================
[unsafe]
fn foo() {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression)))))
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Simple function with attributes
================================================================================
[unsafe]
[other]
fn foo() {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression))))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Simple function with attributes and comment
================================================================================
// foo is a functions
[unsafe]
[other]
fn foo() {}
--------------------------------------------------------------------------------
(source_file
(line_comment)
(function_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression))))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Simple public function
================================================================================
pub fn foo() {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(visibility_modifiers)
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Simple generic function
================================================================================
fn foo[T]() {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(generic_parameters
(generic_parameter
(identifier)))
(signature
(parameter_list))
(block)))
================================================================================
Generic function with several generic parameters
================================================================================
fn foo[T, U]() {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(generic_parameters
(generic_parameter
(identifier))
(generic_parameter
(identifier)))
(signature
(parameter_list))
(block)))
================================================================================
Generic function with several generic parameters and trailing comma
================================================================================
fn foo[T, U, ]() {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(generic_parameters
(generic_parameter
(identifier))
(generic_parameter
(identifier)))
(signature
(parameter_list))
(block)))
================================================================================
All in one function
================================================================================
// comment
[unsafe; other]
pub fn C.foo[T, U, ](foo string, age ...int) !(int, string, ) {}
--------------------------------------------------------------------------------
(source_file
(line_comment)
(function_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression)))
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(visibility_modifiers)
(identifier)
(generic_parameters
(generic_parameter
(identifier))
(generic_parameter
(identifier)))
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(result_type
(plain_type
(multi_return_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
================================================
FILE: tree_sitter_v/test/corpus/function_literal.txt
================================================
================================================================================
Simple function literal
================================================================================
fn () {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list))
(block))))
================================================================================
Simple function literal with parameters
================================================================================
fn (a string, b int) {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
(block))))
================================================================================
Simple function literal with return type
================================================================================
fn (a string, b int) Foo {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(type_reference_expression
(identifier))))
(block))))
================================================================================
Simple function literal with capture
================================================================================
fn [cap] () {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(capture_list
(capture
(reference_expression
(identifier))))
(signature
(parameter_list))
(block))))
================================================================================
Simple function literal with captures
================================================================================
fn [cap, cap2] () {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(capture_list
(capture
(reference_expression
(identifier)))
(capture
(reference_expression
(identifier))))
(signature
(parameter_list))
(block))))
================================================================================
Simple function literal with captures with trailing comma
================================================================================
fn [cap, cap2, ] () {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(capture_list
(capture
(reference_expression
(identifier)))
(capture
(reference_expression
(identifier))))
(signature
(parameter_list))
(block))))
================================================================================
Simple function literal with mutable captures
================================================================================
fn [mut cap, cap2, shared cap3] () {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(capture_list
(capture
(mutability_modifiers)
(reference_expression
(identifier)))
(capture
(reference_expression
(identifier)))
(capture
(mutability_modifiers)
(reference_expression
(identifier))))
(signature
(parameter_list))
(block))))
================================================================================
Simple function literal with call
================================================================================
fn () {}()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(function_literal
(signature
(parameter_list))
(block))
(argument_list))))
================================================================================
Simple function literal as parameter
================================================================================
app.handle(fn (req &Req, res Res) ? {
return
})
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(function_literal
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(pointer_type
(plain_type
(type_reference_expression
(identifier))))))
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(option_type)))
(block
(return_statement))))))))
================================================================================
Real-world function literal
================================================================================
import nedpals.vex.ctx
import time
import context
import picohttpparser
pub struct Config {
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response)
err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_err_cb
user_data voidptr = unsafe { nil }
timeout_secs int = 8
max_headers int = 100
}
fn main() {
a := fn (a i64) i64 {
return 100
}(100)
if ctx.err() is none {
go fn (mut ctx TimerContext, dur time.Duration) {
ctx.cancel(true, deadline_exceeded)
}(mut ctx, dur)
}
cancel_fn := fn [mut ctx] () {
ctx.cancel(true, canceled)
}
}
fn test_with_value() {
f := fn (ctx context.Context, key context.Key) &Value {
if value := ctx.value(key) {
match value {
Value {
return value
}
else {}
}
}
return not_found_value
}
key := 'language'
value := &Value{
val: 'VAL'
}
ctx := context.with_value(context.background(), key, value)
assert value == f(ctx, key)
assert not_found_value == f(ctx, 'color')
}
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier))
(import_name
(identifier))
(import_name
(identifier)))))
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))))
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))))
(import_declaration
(import_spec
(import_path
(import_name
(identifier))))))
(struct_declaration
(visibility_modifiers)
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(function_type
(signature
(type_parameter_list
(type_parameter_declaration
(plain_type
(type_reference_expression
(identifier))))
(type_parameter_declaration
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))))
(type_parameter_declaration
(mutability_modifiers)
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier))))))))))
(struct_field_declaration
(identifier)
(plain_type
(function_type
(signature
(type_parameter_list
(type_parameter_declaration
(plain_type
(type_reference_expression
(identifier))))
(type_parameter_declaration
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))))
(type_parameter_declaration
(mutability_modifiers)
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))))
(type_parameter_declaration
(plain_type
(type_reference_expression
(identifier))))))))
(reference_expression
(identifier)))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(unsafe_expression
(block
(simple_statement
(literal
(nil))))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(literal
(int_literal)))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(literal
(int_literal))))
(function_declaration
(identifier)
(signature
(parameter_list))
(block
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(call_expression
(function_literal
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(type_reference_expression
(identifier))))
(block
(return_statement
(expression_list
(literal
(int_literal))))))
(argument_list
(argument
(literal
(int_literal))))))))
(simple_statement
(if_expression
(is_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list))
(plain_type
(type_reference_expression
(identifier))))
(block
(simple_statement
(go_expression
(call_expression
(function_literal
(signature
(parameter_list
(parameter_declaration
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(parameter_declaration
(identifier)
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))))))
(block
(simple_statement
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(true)))
(argument
(reference_expression
(identifier))))))))
(argument_list
(argument
(mutable_expression
(mutability_modifiers)
(reference_expression
(identifier))))
(argument
(reference_expression
(identifier))))))))))
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(function_literal
(capture_list
(capture
(mutability_modifiers)
(reference_expression
(identifier))))
(signature
(parameter_list))
(block
(simple_statement
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(true)))
(argument
(reference_expression
(identifier)))))))))))))
(function_declaration
(identifier)
(signature
(parameter_list))
(block
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(function_literal
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))))
(parameter_declaration
(identifier)
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier))))))
(plain_type
(pointer_type
(plain_type
(type_reference_expression
(identifier))))))
(block
(simple_statement
(if_expression
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(reference_expression
(identifier)))))))
(block
(simple_statement
(match_expression
(reference_expression
(identifier))
(match_arms
(match_arm
(match_expression_list
(reference_expression
(identifier)))
(block
(return_statement
(expression_list
(reference_expression
(identifier))))))
(match_else_arm_clause
(block))))))))
(return_statement
(expression_list
(reference_expression
(identifier)))))))))
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(literal
(interpreted_string_literal)))))
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(type_initializer
(plain_type
(pointer_type
(plain_type
(type_reference_expression
(identifier)))))
(type_initializer_body
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(interpreted_string_literal)))))))))
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list)))
(argument
(reference_expression
(identifier)))
(argument
(reference_expression
(identifier))))))))
(assert_statement
(binary_expression
(reference_expression
(identifier))
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(reference_expression
(identifier)))
(argument
(reference_expression
(identifier)))))))
(assert_statement
(binary_expression
(reference_expression
(identifier))
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(reference_expression
(identifier)))
(argument
(literal
(interpreted_string_literal))))))))))
================================================
FILE: tree_sitter_v/test/corpus/generics.txt
================================================
================================================================================
Generic type
================================================================================
fn foo() Foo[int] {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list)
(plain_type
(generic_type
(type_reference_expression
(identifier))
(type_parameters
(plain_type
(type_reference_expression
(identifier)))))))
(block)))
================================================================================
Generic type with two types
================================================================================
fn foo() Foo[int, foo.Bar] {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list)
(plain_type
(generic_type
(type_reference_expression
(identifier))
(type_parameters
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier))))))))
(block)))
================================================================================
Generic qualified type
================================================================================
fn foo() bar.Foo[int] {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list)
(plain_type
(generic_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))
(type_parameters
(plain_type
(type_reference_expression
(identifier)))))))
(block)))
================================================================================
Generic struct
================================================================================
struct Foo[T] {}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(generic_parameters
(generic_parameter
(identifier)))))
================================================================================
Generic struct instantiation
================================================================================
Foo[int]{}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(generic_type
(type_reference_expression
(identifier))
(type_parameters
(plain_type
(type_reference_expression
(identifier))))))
(type_initializer_body))))
================================================================================
Generic qualified struct instantiation
================================================================================
bar.Foo[int]{}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(generic_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))
(type_parameters
(plain_type
(type_reference_expression
(identifier))))))
(type_initializer_body))))
================================================================================
Generic function call
================================================================================
foo[int]()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(type_parameters
(plain_type
(type_reference_expression
(identifier))))
(argument_list))))
================================================================================
Generic function call with nested generics
================================================================================
foo[int, Bar[string]]()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(type_parameters
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(generic_type
(type_reference_expression
(identifier))
(type_parameters
(plain_type
(type_reference_expression
(identifier)))))))
(argument_list))))
================================================================================
Type alias with generic type
================================================================================
type Foo[T] = Bar[T]
--------------------------------------------------------------------------------
(source_file
(type_declaration
(identifier)
(generic_parameters
(generic_parameter
(identifier)))
(plain_type
(generic_type
(type_reference_expression
(identifier))
(type_parameters
(plain_type
(type_reference_expression
(identifier))))))))
================================================
FILE: tree_sitter_v/test/corpus/global_var_declaration.txt
================================================
================================================================================
Simple global variable
================================================================================
__global g_var = 0
--------------------------------------------------------------------------------
(source_file
(global_var_declaration
(global_var_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Simple global variable with type
================================================================================
__global g_var int
--------------------------------------------------------------------------------
(source_file
(global_var_declaration
(global_var_definition
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Simple global variable with complex type
================================================================================
__global g_var map[string]int
--------------------------------------------------------------------------------
(source_file
(global_var_declaration
(global_var_definition
(identifier)
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))))
================================================================================
Multiline global variable
================================================================================
__global (
g_var = 100
)
--------------------------------------------------------------------------------
(source_file
(global_var_declaration
(global_var_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Multiline global variables
================================================================================
__global (
g_var = 100
g_var2 = 200
g_var3 = 300
)
--------------------------------------------------------------------------------
(source_file
(global_var_declaration
(global_var_definition
(identifier)
(literal
(int_literal)))
(global_var_definition
(identifier)
(literal
(int_literal)))
(global_var_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Multiline global variables with type and initializer
================================================================================
__global (
g_var = 100
g_var2 int
g_var3 string
g_var4 = true
)
--------------------------------------------------------------------------------
(source_file
(global_var_declaration
(global_var_definition
(identifier)
(literal
(int_literal)))
(global_var_definition
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(global_var_definition
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(global_var_definition
(identifier)
(literal
(true)))))
================================================================================
Empty multiline global variables
================================================================================
__global ()
--------------------------------------------------------------------------------
(source_file
(global_var_declaration))
================================================================================
Simple global variable with attribute
================================================================================
[attr]
__global g_var = 0
--------------------------------------------------------------------------------
(source_file
(global_var_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(global_var_definition
(identifier)
(literal
(int_literal)))))
================================================================================
Multiline global variables with attributes
================================================================================
[attr]
[attr2]
__global (
g_var = 100
g_var2 = 200
g_var3 = 300
)
--------------------------------------------------------------------------------
(source_file
(global_var_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier)))))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(global_var_definition
(identifier)
(literal
(int_literal)))
(global_var_definition
(identifier)
(literal
(int_literal)))
(global_var_definition
(identifier)
(literal
(int_literal)))))
================================================
FILE: tree_sitter_v/test/corpus/hash_statement.txt
================================================
================================================================================
Hash statement
================================================================================
$if tinyc {
#flag -I @VEXEROOT/thirdparty/libgc/include
#flag -L @VEXEROOT/thirdparty/tcc/lib
#flag -lgc
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(compile_time_if_expression
(reference_expression
(identifier))
(block
(hash_statement)
(hash_statement)
(hash_statement)))))
================================================
FILE: tree_sitter_v/test/corpus/if_expression.txt
================================================
================================================================================
Simple if expression
================================================================================
if true {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(literal
(true))
(block))))
================================================================================
Simple if expression with braces
================================================================================
if (true) {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(parenthesized_expression
(literal
(true)))
(block))))
================================================================================
Simple if expression with else
================================================================================
if true {} else {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(literal
(true))
(block)
(else_branch
(block)))))
================================================================================
Simple if expression with else if
================================================================================
if true {} else if false {} else {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(literal
(true))
(block)
(else_branch
(if_expression
(literal
(false))
(block)
(else_branch
(block)))))))
================================================================================
If guard
================================================================================
if a := foo() {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(call_expression
(reference_expression
(identifier))
(argument_list))))
(block))))
================================================================================
Compile-time if expression
================================================================================
$if macos {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(compile_time_if_expression
(reference_expression
(identifier))
(block))))
================================================================================
Compile-time if expression with else
================================================================================
$if macos {} $else {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(compile_time_if_expression
(reference_expression
(identifier))
(block)
(block))))
================================================================================
Compile-time if expression with else if
================================================================================
$if macos {
println(1)
} $else $if linux {
println(1)
} $else {
println(1)
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(compile_time_if_expression
(reference_expression
(identifier))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))))))
(compile_time_if_expression
(reference_expression
(identifier))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))))))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))))))))))
================================================================================
Compile-time if expression with ?
================================================================================
$if some_define ? {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(compile_time_if_expression
(option_propagation_expression
(reference_expression
(identifier)))
(block))))
================================================================================
Simple block
================================================================================
if true { f();g();h(); }
if true { f()
g()
h(); }
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(literal
(true))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list)))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list)))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list))))))
(simple_statement
(if_expression
(literal
(true))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list)))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list)))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list)))))))
================================================
FILE: tree_sitter_v/test/corpus/imports.txt
================================================
================================================================================
Simple import
================================================================================
import foo
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))))))
================================================================================
Simple import list
================================================================================
import foo
import bar
import baz
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))))
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))))
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))))))
================================================================================
Import with alias
================================================================================
import foo as bar
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))
(import_alias
(import_name
(identifier)))))))
================================================================================
Import list with alias
================================================================================
import foo as bar
import baz as qux
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))
(import_alias
(import_name
(identifier)))))
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))
(import_alias
(import_name
(identifier)))))))
================================================================================
Import list with alias and no alias
================================================================================
import foo as bar
import baz
import qux as quux
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))
(import_alias
(import_name
(identifier)))))
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))))
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))
(import_alias
(import_name
(identifier)))))))
================================================================================
Import with fqn
================================================================================
import foo.bar.baz
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier))
(import_name
(identifier))
(import_name
(identifier)))))))
================================================================================
Import with fqn and alias
================================================================================
import foo.bar.baz as qux
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier))
(import_name
(identifier))
(import_name
(identifier)))
(import_alias
(import_name
(identifier)))))))
================================================================================
Simple selective import
================================================================================
import foo { bar }
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))
(selective_import_list
(reference_expression
(identifier)))))))
================================================================================
Selective import with several items
================================================================================
import foo { Bar, baz, qux }
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))
(selective_import_list
(reference_expression
(identifier))
(reference_expression
(identifier))
(reference_expression
(identifier)))))))
================================================================================
Selective import with several items and trailing comma
================================================================================
import foo { bar, baz, qux, }
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))
(selective_import_list
(reference_expression
(identifier))
(reference_expression
(identifier))
(reference_expression
(identifier)))))))
================================================================================
Selective import with multiline comma separated several items
================================================================================
import foo {
bar,
baz,
qux,
}
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))
(selective_import_list
(reference_expression
(identifier))
(reference_expression
(identifier))
(reference_expression
(identifier)))))))
================================================================================
Selective import with multiline new line separated several items
================================================================================
import foo {
bar
baz
Qux
}
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))
(selective_import_list
(reference_expression
(identifier))
(reference_expression
(identifier))
(reference_expression
(identifier)))))))
================================================================================
Selective import with several items and alias
================================================================================
import foo as bar { bar, Baz, qux, }
--------------------------------------------------------------------------------
(source_file
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))
(import_alias
(import_name
(identifier)))
(selective_import_list
(reference_expression
(identifier))
(reference_expression
(identifier))
(reference_expression
(identifier)))))))
================================================
FILE: tree_sitter_v/test/corpus/in_expression.txt
================================================
================================================================================
Simple in expression
================================================================================
10 in [1, 2, 3]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(in_expression
(literal
(int_literal))
(array_creation
(literal
(int_literal))
(literal
(int_literal))
(literal
(int_literal))))))
================================================================================
Simple not in expression
================================================================================
10 !in [1, 2, 3]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(in_expression
(literal
(int_literal))
(array_creation
(literal
(int_literal))
(literal
(int_literal))
(literal
(int_literal))))))
================================================
FILE: tree_sitter_v/test/corpus/interface_declaration.txt
================================================
================================================================================
Simple interface
================================================================================
interface Foo {
name string
do() string
}
--------------------------------------------------------------------------------
(source_file
(interface_declaration
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(interface_method_definition
(identifier)
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier)))))))
================================================================================
Simple public interface
================================================================================
pub interface Foo {
name string
do() string
}
--------------------------------------------------------------------------------
(source_file
(interface_declaration
(visibility_modifiers)
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(interface_method_definition
(identifier)
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier)))))))
================================================================================
Simple interface with several methods
================================================================================
interface Foo {
do() string
foo(name string) ?string
}
--------------------------------------------------------------------------------
(source_file
(interface_declaration
(identifier)
(interface_method_definition
(identifier)
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier)))))
(interface_method_definition
(identifier)
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(option_type
(plain_type
(type_reference_expression
(identifier)))))))))
================================================================================
Simple interface with scopes
================================================================================
interface Foo {
mut:
name string
mut:
do() string
}
--------------------------------------------------------------------------------
(source_file
(interface_declaration
(identifier)
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_scope)
(interface_method_definition
(identifier)
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier)))))))
================================================================================
Interface with field with default type and attribute
================================================================================
interface Foo {
name string = '' [json: 'Name']
do() string
}
--------------------------------------------------------------------------------
(source_file
(interface_declaration
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(literal
(interpreted_string_literal))
(attribute
(attribute_expression
(key_value_attribute
(value_attribute
(reference_expression
(identifier)))
(literal
(interpreted_string_literal))))))
(interface_method_definition
(identifier)
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier)))))))
================================================================================
Simple interface with attributes
================================================================================
[attr]
[attr2]
interface Foo {
name string
do() string
}
--------------------------------------------------------------------------------
(source_file
(interface_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier)))))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(interface_method_definition
(identifier)
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier)))))))
================================================================================
Simple interface with attributes and comment
================================================================================
// Foo is a simple interface
[attr]
[attr2]
interface Foo {
name string
do() string
}
--------------------------------------------------------------------------------
(source_file
(line_comment)
(interface_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier)))))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(interface_method_definition
(identifier)
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier)))))))
================================================================================
Interface embedding
================================================================================
interface Foo {
Embedded
}
--------------------------------------------------------------------------------
(source_file
(interface_declaration
(identifier)
(struct_field_declaration
(embedded_definition
(type_reference_expression
(identifier))))))
================================================================================
Interface with several embedded interfaces
================================================================================
interface Foo {
Embedded
Embedded2
}
--------------------------------------------------------------------------------
(source_file
(interface_declaration
(identifier)
(struct_field_declaration
(embedded_definition
(type_reference_expression
(identifier))))
(struct_field_declaration
(embedded_definition
(type_reference_expression
(identifier))))))
================================================================================
Generic interface
================================================================================
interface Foo[T] {
name T
do() T
}
--------------------------------------------------------------------------------
(source_file
(interface_declaration
(identifier)
(generic_parameters
(generic_parameter
(identifier)))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(interface_method_definition
(identifier)
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier)))))))
================================================
FILE: tree_sitter_v/test/corpus/is_as_expression.txt
================================================
================================================================================
Simple is expression
================================================================================
if a is []Any {
} else if a is map[string]Any {
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(is_expression
(reference_expression
(identifier))
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))
(block)
(else_branch
(if_expression
(is_expression
(reference_expression
(identifier))
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))
(block))))))
================================================================================
Is expression with qualified type
================================================================================
if a is foo.Bar {
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(is_expression
(reference_expression
(identifier))
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))))
(block))))
================================================================================
Is expression with mut
================================================================================
if mut run is Block {
println()
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(is_expression
(mutability_modifiers)
(reference_expression
(identifier))
(plain_type
(type_reference_expression
(identifier))))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list)))))))
================================================================================
Not is expression with mut
================================================================================
if mut run !is Block {
println()
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(is_expression
(mutability_modifiers)
(reference_expression
(identifier))
(plain_type
(type_reference_expression
(identifier))))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list)))))))
================================================================================
Is expression with selector expression
================================================================================
if element is psi.StructDeclaration {
return
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(is_expression
(reference_expression
(identifier))
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))))
(block
(return_statement)))))
================================================================================
Is and as expressions
================================================================================
// array returns `Any` as an array.
pub fn (a Any) array() []Any {
if a is []Any {
return a
} else if a is map[string]Any {
mut arr := []Any{}
for _, v in a {
arr << v
}
return arr
}
return [a]
}
fn test_parse_compact_text() {
toml_doc := toml.parse_text(toml_text) or { panic(err) }
title := toml_doc.value('title')
assert title == toml.Any('TOML Example')
assert title as string == 'TOML Example'
database := toml_doc.value('database') as map[string]toml.Any
db_serv := database[1] or {
panic('could not access "server" index in "database" variable')
}
assert db_serv as string == '192.168.1.1'
assert toml_doc.value('owner.name') as string == 'Tom Preston-Werner'
assert toml_doc.value('database.server') as string == '192.168.1.1'
database_ports := toml_doc.value('database.ports') as []toml.Any
assert database_ports[0] as i64 == 8000
assert database_ports[1] as i64 == 8001
assert database_ports[2] as i64 == 8002
assert database_ports[0].int() == 8000
assert database_ports[1].int() == 8001
assert database_ports[2].int() == 8002
assert toml_doc.value('database.connection_max') as i64 == 5000
assert toml_doc.value('database.enabled') as bool == true
assert toml_doc.value('servers.alpha.ip').string() == '10.0.0.1'
assert toml_doc.value('servers.alpha.dc').string() == 'eqdc10'
assert toml_doc.value('servers.beta.ip').string() == '10.0.0.2'
assert toml_doc.value('servers.beta.dc').string() == 'eqdc10'
clients_data := (toml_doc.value('clients.data') as []toml.Any)
// dump(clients_data)
// assert false
gamma_delta_array := clients_data[0] as []toml.Any
digits_array := clients_data[1] as []toml.Any
assert gamma_delta_array[0].string() == 'gamma'
assert gamma_delta_array[1].string() == 'delta'
assert digits_array[0].int() == 1
assert digits_array[1].int() == 2
clients_hosts := (toml_doc.value('clients.hosts') as []toml.Any).as_strings()
assert clients_hosts[0] == 'alpha'
assert clients_hosts[1] == 'omega'
}
--------------------------------------------------------------------------------
(source_file
(line_comment)
(function_declaration
(visibility_modifiers)
(receiver
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(identifier)
(signature
(parameter_list)
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))
(block
(simple_statement
(if_expression
(is_expression
(reference_expression
(identifier))
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))
(block
(return_statement
(expression_list
(reference_expression
(identifier)))))
(else_branch
(if_expression
(is_expression
(reference_expression
(identifier))
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))
(block
(simple_statement
(var_declaration
(expression_list
(mutable_expression
(mutability_modifiers)
(reference_expression
(identifier))))
(expression_list
(type_initializer
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(type_initializer_body)))))
(for_statement
(range_clause
(var_definition_list
(var_definition
(identifier))
(var_definition
(identifier)))
(reference_expression
(identifier)))
(block
(append_statement
(reference_expression
(identifier))
(reference_expression
(identifier)))))
(return_statement
(expression_list
(reference_expression
(identifier)))))))))
(return_statement
(expression_list
(array_creation
(reference_expression
(identifier)))))))
(function_declaration
(identifier)
(signature
(parameter_list))
(block
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(or_block_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(reference_expression
(identifier)))))
(or_block
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(reference_expression
(identifier))))))))))))
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal))))))))
(assert_statement
(binary_expression
(reference_expression
(identifier))
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))))
(assert_statement
(binary_expression
(as_type_cast_expression
(reference_expression
(identifier))
(plain_type
(type_reference_expression
(identifier))))
(literal
(interpreted_string_literal))))
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(as_type_cast_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier))))))))))
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(or_block_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(or_block
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal))))))))))))
(assert_statement
(binary_expression
(as_type_cast_expression
(reference_expression
(identifier))
(plain_type
(type_reference_expression
(identifier))))
(literal
(interpreted_string_literal))))
(assert_statement
(binary_expression
(as_type_cast_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))
(plain_type
(type_reference_expression
(identifier))))
(literal
(interpreted_string_literal))))
(assert_statement
(binary_expression
(as_type_cast_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))
(plain_type
(type_reference_expression
(identifier))))
(literal
(interpreted_string_literal))))
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(as_type_cast_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))
(plain_type
(array_type
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier))))))))))
(assert_statement
(binary_expression
(as_type_cast_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(plain_type
(type_reference_expression
(identifier))))
(literal
(int_literal))))
(assert_statement
(binary_expression
(as_type_cast_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(plain_type
(type_reference_expression
(identifier))))
(literal
(int_literal))))
(assert_statement
(binary_expression
(as_type_cast_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(plain_type
(type_reference_expression
(identifier))))
(literal
(int_literal))))
(assert_statement
(binary_expression
(call_expression
(selector_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(reference_expression
(identifier)))
(argument_list))
(literal
(int_literal))))
(assert_statement
(binary_expression
(call_expression
(selector_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(reference_expression
(identifier)))
(argument_list))
(literal
(int_literal))))
(assert_statement
(binary_expression
(call_expression
(selector_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(reference_expression
(identifier)))
(argument_list))
(literal
(int_literal))))
(assert_statement
(binary_expression
(as_type_cast_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))
(plain_type
(type_reference_expression
(identifier))))
(literal
(int_literal))))
(assert_statement
(binary_expression
(as_type_cast_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))
(plain_type
(type_reference_expression
(identifier))))
(literal
(true))))
(assert_statement
(binary_expression
(call_expression
(selector_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))
(reference_expression
(identifier)))
(argument_list))
(literal
(interpreted_string_literal))))
(assert_statement
(binary_expression
(call_expression
(selector_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))
(reference_expression
(identifier)))
(argument_list))
(literal
(interpreted_string_literal))))
(assert_statement
(binary_expression
(call_expression
(selector_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))
(reference_expression
(identifier)))
(argument_list))
(literal
(interpreted_string_literal))))
(assert_statement
(binary_expression
(call_expression
(selector_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))
(reference_expression
(identifier)))
(argument_list))
(literal
(interpreted_string_literal))))
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(parenthesized_expression
(as_type_cast_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))
(plain_type
(array_type
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))))))))))
(line_comment)
(line_comment)
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(as_type_cast_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(plain_type
(array_type
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier))))))))))
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(as_type_cast_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(plain_type
(array_type
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier))))))))))
(assert_statement
(binary_expression
(call_expression
(selector_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(reference_expression
(identifier)))
(argument_list))
(literal
(interpreted_string_literal))))
(assert_statement
(binary_expression
(call_expression
(selector_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(reference_expression
(identifier)))
(argument_list))
(literal
(interpreted_string_literal))))
(assert_statement
(binary_expression
(call_expression
(selector_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(reference_expression
(identifier)))
(argument_list))
(literal
(int_literal))))
(assert_statement
(binary_expression
(call_expression
(selector_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(reference_expression
(identifier)))
(argument_list))
(literal
(int_literal))))
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(call_expression
(selector_expression
(parenthesized_expression
(as_type_cast_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(literal
(interpreted_string_literal)))))
(plain_type
(array_type
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier))))))))
(reference_expression
(identifier)))
(argument_list)))))
(assert_statement
(binary_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(literal
(interpreted_string_literal))))
(assert_statement
(binary_expression
(index_expression
(reference_expression
(identifier))
(literal
(int_literal)))
(literal
(interpreted_string_literal)))))))
================================================
FILE: tree_sitter_v/test/corpus/json_call.txt
================================================
================================================================================
Simple json.decode call expression
================================================================================
a := json.decode([]Foo, data)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(call_expression
(special_argument_list
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(reference_expression
(identifier))))))))
================================================================================
Simple json.decode call expression with array
================================================================================
json.decode([]Foo, data)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(special_argument_list
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(reference_expression
(identifier))))))
================================================================================
Simple json.decode call expression with map
================================================================================
json.decode(map[string]Foo, data)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(special_argument_list
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier)))))
(reference_expression
(identifier))))))
================================================================================
Simple json.encode call expression
================================================================================
json.encode(data)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list
(argument
(reference_expression
(identifier)))))))
================================================
FILE: tree_sitter_v/test/corpus/labeled_statement.txt
================================================
================================================================================
Simple labeled for
================================================================================
label: for i in 0 .. 10 {
println(i)
}
--------------------------------------------------------------------------------
(source_file
(labeled_statement
(label_definition
(identifier))
(for_statement
(range_clause
(var_definition_list
(var_definition
(identifier)))
(range
(literal
(int_literal))
(literal
(int_literal))))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(reference_expression
(identifier))))))))))
================================================================================
Break/continue/goto with label
================================================================================
label: for i in 0 .. 10 {
break label
continue label
goto label
}
--------------------------------------------------------------------------------
(source_file
(labeled_statement
(label_definition
(identifier))
(for_statement
(range_clause
(var_definition_list
(var_definition
(identifier)))
(range
(literal
(int_literal))
(literal
(int_literal))))
(block
(break_statement
(label_reference
(identifier)))
(continue_statement
(label_reference
(identifier)))
(goto_statement
(label_reference
(identifier)))))))
================================================
FILE: tree_sitter_v/test/corpus/lock_expression.txt
================================================
================================================================================
Simple lock expression
================================================================================
lock a {
return
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(lock_expression
(expression_list
(reference_expression
(identifier)))
(block
(return_statement)))))
================================================================================
Simple rlock expression
================================================================================
rlock a {
return
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(lock_expression
(expression_list
(reference_expression
(identifier)))
(block
(return_statement)))))
================================================================================
Simple lock expression with several expressions
================================================================================
rlock a, b.name, c.other.foo {
return
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(lock_expression
(expression_list
(reference_expression
(identifier))
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(selector_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(reference_expression
(identifier))))
(block
(return_statement)))))
================================================
FILE: tree_sitter_v/test/corpus/map_init_expression.txt
================================================
================================================================================
Simple map init expression
================================================================================
{
'0': 1
1: 2
true: 3
Foo.name: 4
.name: 5
1 + 3: 6
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(map_init_expression
(map_keyed_element
(literal
(interpreted_string_literal))
(literal
(int_literal)))
(map_keyed_element
(literal
(int_literal))
(literal
(int_literal)))
(map_keyed_element
(literal
(true))
(literal
(int_literal)))
(map_keyed_element
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(literal
(int_literal)))
(map_keyed_element
(enum_fetch
(reference_expression
(identifier)))
(literal
(int_literal)))
(map_keyed_element
(binary_expression
(literal
(int_literal))
(literal
(int_literal)))
(literal
(int_literal))))))
================================================================================
Empty map initialization
================================================================================
foo({})
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(map_init_expression))))))
================================================
FILE: tree_sitter_v/test/corpus/match_expression.txt
================================================
================================================================================
Simple match expression
================================================================================
match age {
100 {}
200 {}
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(match_expression
(reference_expression
(identifier))
(match_arms
(match_arm
(match_expression_list
(literal
(int_literal)))
(block))
(match_arm
(match_expression_list
(literal
(int_literal)))
(block))))))
================================================================================
Simple match expression with else
================================================================================
match age {
100 {}
else {}
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(match_expression
(reference_expression
(identifier))
(match_arms
(match_arm
(match_expression_list
(literal
(int_literal)))
(block))
(match_else_arm_clause
(block))))))
================================================================================
Simple match expression with several else
================================================================================
match age {
100 {}
200 {}
else {}
else {}
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(match_expression
(reference_expression
(identifier))
(match_arms
(match_arm
(match_expression_list
(literal
(int_literal)))
(block))
(match_arm
(match_expression_list
(literal
(int_literal)))
(block))
(match_else_arm_clause
(block))
(match_else_arm_clause
(block))))))
================================================================================
Simple match expression with mutable expression
================================================================================
match mut age {
100 {}
200 {}
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(match_expression
(mutable_expression
(mutability_modifiers)
(reference_expression
(identifier)))
(match_arms
(match_arm
(match_expression_list
(literal
(int_literal)))
(block))
(match_arm
(match_expression_list
(literal
(int_literal)))
(block))))))
================================================================================
Simple match expression with several expression in case
================================================================================
match mut age {
100, 200 {}
200, 300 {}
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(match_expression
(mutable_expression
(mutability_modifiers)
(reference_expression
(identifier)))
(match_arms
(match_arm
(match_expression_list
(literal
(int_literal))
(literal
(int_literal)))
(block))
(match_arm
(match_expression_list
(literal
(int_literal))
(literal
(int_literal)))
(block))))))
================================================================================
Simple match expression with range
================================================================================
match b {
33, 35...39, 42 { true }
else { false }
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(match_expression
(reference_expression
(identifier))
(match_arms
(match_arm
(match_expression_list
(literal
(int_literal))
(range
(literal
(int_literal))
(literal
(int_literal)))
(literal
(int_literal)))
(block
(simple_statement
(literal
(true)))))
(match_else_arm_clause
(block
(simple_statement
(literal
(false)))))))))
================================================================================
Simple match expression for types
================================================================================
match age {
[]string {}
int {}
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(match_expression
(reference_expression
(identifier))
(match_arms
(match_arm
(match_expression_list
(in_expression
(type_initializer
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(type_initializer_body))
(reference_expression
(identifier))))
(block))))))
================================================================================
Simple match expression for other types
================================================================================
match age {
map[int]string {}
&int {}
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(match_expression
(reference_expression
(identifier))
(match_arms
(match_arm
(match_expression_list
(binary_expression
(type_initializer
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier)))))
(type_initializer_body))
(reference_expression
(identifier))))
(block))))))
================================================
FILE: tree_sitter_v/test/corpus/method_declaration.txt
================================================
================================================================================
Simple method
================================================================================
fn (f Foo) foo() {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(receiver
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Simple method with mutable receiver
================================================================================
fn (mut f Foo) foo() {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(receiver
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Simple method with shared receiver
================================================================================
fn (shared f Foo) foo() {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(receiver
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Simple method with pointer receiver
================================================================================
fn (f &Foo) foo() {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(receiver
(identifier)
(plain_type
(pointer_type
(plain_type
(type_reference_expression
(identifier))))))
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Overload method
================================================================================
fn (f Foo) == (o Foo) {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(receiver
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(overridable_operator)
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
(block)))
================================================================================
Method with mutable receiver and parameters
================================================================================
fn (mut r ResolveProcessor) execute(element PsiElement) bool {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(receiver
(mutability_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(identifier)
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(type_reference_expression
(identifier))))
(block)))
================================================================================
Method with array parameter
================================================================================
pub fn new_stubs_index(sinks []StubIndexSink) {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(visibility_modifiers)
(identifier)
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
================================================
FILE: tree_sitter_v/test/corpus/module_clause.txt
================================================
================================================================================
Simple module clause
================================================================================
module main
--------------------------------------------------------------------------------
(source_file
(module_clause
(identifier)))
================================================================================
Simple module clause with comment
================================================================================
// This is a comment
// with two lines
module main
--------------------------------------------------------------------------------
(source_file
(line_comment)
(line_comment)
(module_clause
(identifier)))
================================================================================
Module clause with attributes
================================================================================
[module_attribute]
module main
--------------------------------------------------------------------------------
(source_file
(module_clause
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(identifier)))
================================================================================
Module clause with attributes and comments
================================================================================
// This is a comment
// with two lines
[module_attribute]
module main
--------------------------------------------------------------------------------
(source_file
(line_comment)
(line_comment)
(module_clause
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(identifier)))
================================================
FILE: tree_sitter_v/test/corpus/safe_access.txt
================================================
================================================================================
Simple safe access
================================================================================
foo?.name
--------------------------------------------------------------------------------
(source_file
(simple_statement
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))))
================================================
FILE: tree_sitter_v/test/corpus/select_expression.txt
================================================
================================================================================
Simple select expression
================================================================================
select {
c <- x {
println(100)
}
_ := <-quit {
println(100)
}
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(select_expression
(select_arm
(select_arm_statement
(send_statement
(reference_expression
(identifier))
(reference_expression
(identifier))))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal))))))))
(select_arm
(select_arm_statement
(var_declaration
(identifier_list
(identifier))
(expression_list
(receive_expression
(reference_expression
(identifier))))))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))))))))))
================================================================================
Simple select expression with timeout branch
================================================================================
select {
2 * time.second {
println(100)
}
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(select_expression
(select_arm
(select_arm_statement
(expression_list
(binary_expression
(literal
(int_literal))
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier))))))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))))))))))
================================================================================
Simple select expression with else branch
================================================================================
select {
c <- x {
println(100)
}
_ := <-quit {
println(200)
}
else {
println(300)
}
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(select_expression
(select_arm
(select_arm_statement
(send_statement
(reference_expression
(identifier))
(reference_expression
(identifier))))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal))))))))
(select_arm
(select_arm_statement
(var_declaration
(identifier_list
(identifier))
(expression_list
(receive_expression
(reference_expression
(identifier))))))
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal))))))))
(select_else_arn_clause
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))))))))))
================================================================================
Simple select expression as an expression
================================================================================
if select {
ch <- a {
// ...
}
} {
// channel was open
} else {
// channel is closed
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(if_expression
(select_expression
(select_arm
(select_arm_statement
(send_statement
(reference_expression
(identifier))
(reference_expression
(identifier))))
(block
(line_comment))))
(block
(line_comment))
(else_branch
(block
(line_comment))))))
================================================
FILE: tree_sitter_v/test/corpus/shebang.txt
================================================
================================================================================
Shebang
================================================================================
#!/usr/bin/env -S v
foo := 'foo'
--------------------------------------------------------------------------------
(source_file
(shebang)
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(literal
(interpreted_string_literal))))))
================================================
FILE: tree_sitter_v/test/corpus/slice_expression.txt
================================================
================================================================================
Simple slice expression
================================================================================
name[0..10]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(slice_expression
(reference_expression
(identifier))
(range
(literal
(int_literal))
(literal
(int_literal))))))
================================================================================
Slice expression with only left index
================================================================================
name[0..]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(slice_expression
(reference_expression
(identifier))
(range
(literal
(int_literal))))))
================================================================================
Slice expression with only right index
================================================================================
name[..10]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(slice_expression
(reference_expression
(identifier))
(range
(literal
(int_literal))))))
================================================================================
Slice expression without indices
================================================================================
name[..]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(slice_expression
(reference_expression
(identifier))
(range))))
================================================================================
Slice expression with binary expression indices
================================================================================
name[10 + 4..20 + 10]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(slice_expression
(reference_expression
(identifier))
(range
(binary_expression
(literal
(int_literal))
(literal
(int_literal)))
(binary_expression
(literal
(int_literal))
(literal
(int_literal)))))))
================================================================================
Safe slice expression
================================================================================
name#[0..10]
--------------------------------------------------------------------------------
(source_file
(simple_statement
(slice_expression
(reference_expression
(identifier))
(range
(literal
(int_literal))
(literal
(int_literal))))))
================================================
FILE: tree_sitter_v/test/corpus/source_file.txt
================================================
================================================================================
Simple source file
================================================================================
fn main() {}
--------------------------------------------------------------------------------
(source_file
(function_declaration
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Source file with module
================================================================================
module main
fn main() {}
--------------------------------------------------------------------------------
(source_file
(module_clause
(identifier))
(function_declaration
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Source file with top level statements
================================================================================
module main
println(100)
--------------------------------------------------------------------------------
(source_file
(module_clause
(identifier))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(int_literal)))))))
================================================================================
Source file with module clause and imports
================================================================================
module main
import bar
import foo as f
import foo.bar as fb
import foo.bar.baz as fbb { Foo, Bar }
--------------------------------------------------------------------------------
(source_file
(module_clause
(identifier))
(import_list
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))))
(import_declaration
(import_spec
(import_path
(import_name
(identifier)))
(import_alias
(import_name
(identifier)))))
(import_declaration
(import_spec
(import_path
(import_name
(identifier))
(import_name
(identifier)))
(import_alias
(import_name
(identifier)))))
(import_declaration
(import_spec
(import_path
(import_name
(identifier))
(import_name
(identifier))
(import_name
(identifier)))
(import_alias
(import_name
(identifier)))
(selective_import_list
(reference_expression
(identifier))
(reference_expression
(identifier)))))))
================================================
FILE: tree_sitter_v/test/corpus/spawn_expression.txt
================================================
================================================================================
Simple spawn expression
================================================================================
spawn foo()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(spawn_expression
(call_expression
(reference_expression
(identifier))
(argument_list)))))
================================================================================
Simple spawn expression with function literal
================================================================================
spawn fn () {
return
}()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(spawn_expression
(call_expression
(function_literal
(signature
(parameter_list))
(block
(return_statement)))
(argument_list)))))
================================================================================
Simple spawn expression with assignment
================================================================================
a := spawn foo()
--------------------------------------------------------------------------------
(source_file
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(spawn_expression
(call_expression
(reference_expression
(identifier))
(argument_list)))))))
================================================
FILE: tree_sitter_v/test/corpus/special_call_expression.txt
================================================
================================================================================
json.decode call
================================================================================
json.decode([]User, data)
--------------------------------------------------------------------------------
(source_file
(simple_statement
(call_expression
(special_argument_list
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(reference_expression
(identifier))))))
================================================================================
json.decode call with propagate
================================================================================
json.decode([]User, data)!
--------------------------------------------------------------------------------
(source_file
(simple_statement
(result_propagation_expression
(call_expression
(special_argument_list
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(reference_expression
(identifier)))))))
================================================================================
json.decode call with or block
================================================================================
json.decode([]User, data) or {
return
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(or_block_expression
(call_expression
(special_argument_list
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(reference_expression
(identifier))))
(or_block
(block
(return_statement))))))
================================================
FILE: tree_sitter_v/test/corpus/static_method_declaration.txt
================================================
================================================================================
Simple static method
================================================================================
fn Foo.method() {}
--------------------------------------------------------------------------------
(source_file
(static_method_declaration
(static_receiver
(reference_expression
(identifier)))
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Static method with attributes
================================================================================
[unsafe]
fn Foo.method() {}
--------------------------------------------------------------------------------
(source_file
(static_method_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression)))))
(static_receiver
(reference_expression
(identifier)))
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Public static method
================================================================================
pub fn Foo.method() {}
--------------------------------------------------------------------------------
(source_file
(static_method_declaration
(visibility_modifiers)
(static_receiver
(reference_expression
(identifier)))
(identifier)
(signature
(parameter_list))
(block)))
================================================================================
Static method with generics
================================================================================
fn Foo.method[T]() {}
fn Foo.method[T, R]() {}
--------------------------------------------------------------------------------
(source_file
(static_method_declaration
(static_receiver
(reference_expression
(identifier)))
(identifier)
(generic_parameters
(generic_parameter
(identifier)))
(signature
(parameter_list))
(block))
(static_method_declaration
(static_receiver
(reference_expression
(identifier)))
(identifier)
(generic_parameters
(generic_parameter
(identifier))
(generic_parameter
(identifier)))
(signature
(parameter_list))
(block)))
================================================
FILE: tree_sitter_v/test/corpus/string_literal.txt
================================================
================================================================================
Empty string
================================================================================
''
c''
r''
""
c""
r""
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(interpreted_string_literal)))
(simple_statement
(literal
(c_string_literal)))
(simple_statement
(literal
(raw_string_literal)))
(simple_statement
(literal
(interpreted_string_literal)))
(simple_statement
(literal
(c_string_literal)))
(simple_statement
(literal
(raw_string_literal))))
================================================================================
Comments string
================================================================================
'//'
'/**/'
c'//'
c'/**/'
r'//'
r'/**/'
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(interpreted_string_literal)))
(simple_statement
(literal
(interpreted_string_literal)))
(simple_statement
(literal
(c_string_literal)))
(simple_statement
(literal
(c_string_literal)))
(simple_statement
(literal
(raw_string_literal)))
(simple_statement
(literal
(raw_string_literal))))
================================================================================
Simple string interpolation
================================================================================
'Hello, ${name}!'
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(interpolation_closing))))))
================================================================================
Several string interpolations
================================================================================
'Hello, ${name} and ${other}!'
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(interpolation_closing))
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(interpolation_closing))))))
================================================================================
Simple escaped string interpolation
================================================================================
'Hello, \${name}!'
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(interpreted_string_literal
(escape_sequence)))))
================================================================================
String interpolation at start
================================================================================
'${name}!'
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(interpolation_closing))))))
================================================================================
String interpolation at end
================================================================================
'${name}'
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(interpolation_closing))))))
================================================================================
String interpolation with complex expression
================================================================================
'Hello, ${name.foo()}!'
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(call_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(argument_list)))
(interpolation_closing))))))
================================================================================
String interpolation with complex expressions and format specifiers
================================================================================
x := 123.4567
println('[${x:.2}]')
println('[${x:10}]')
println('[${int(x):-10}]')
println('[${int(x):010}]')
println('[${int(x):b}]')
println('[${int(x):o}]')
println('[${int(x):X}]')
println('[${10.0000:.2}]')
println('[${10.0000:+.2f}]')
--------------------------------------------------------------------------------
(source_file
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(literal
(float_literal)))))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(format_specifier
(int_literal))
(interpolation_closing))))))))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(reference_expression
(identifier)))
(format_specifier
(int_literal))
(interpolation_closing))))))))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(reference_expression
(identifier))))))
(format_specifier
(int_literal))
(interpolation_closing))))))))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(reference_expression
(identifier))))))
(format_specifier
(int_literal))
(interpolation_closing))))))))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(reference_expression
(identifier))))))
(format_specifier)
(interpolation_closing))))))))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(reference_expression
(identifier))))))
(format_specifier)
(interpolation_closing))))))))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(reference_expression
(identifier))))))
(format_specifier)
(interpolation_closing))))))))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(literal
(float_literal)))
(format_specifier
(int_literal))
(interpolation_closing))))))))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list
(argument
(literal
(interpreted_string_literal
(string_interpolation
(interpolation_opening)
(interpolation_expression
(literal
(float_literal)))
(format_specifier
(int_literal))
(interpolation_closing)))))))))
================================================================================
C string literal
================================================================================
c'Hello, World!'
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(c_string_literal))))
================================================================================
Raw string literal
================================================================================
r'Hello, World!'
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(raw_string_literal))))
================================================================================
Raw string literal with interpolation
================================================================================
r'Hello, ${name}!'
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(raw_string_literal))))
================================================================================
String literal with escape sequences
================================================================================
'Hello, \''
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(interpreted_string_literal
(escape_sequence)))))
================================================================================
String literal with identifier tokens
================================================================================
'$Hello'
'C.Hello $World'
'@Hello
$
JS.World'
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(interpreted_string_literal)))
(simple_statement
(literal
(interpreted_string_literal)))
(simple_statement
(literal
(interpreted_string_literal))))
================================================================================
String literal with interpolation after \n
================================================================================
'v fmt failed:\n\n${res.output}'
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(interpreted_string_literal
(escape_sequence)
(escape_sequence)
(string_interpolation
(interpolation_opening)
(interpolation_expression
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier))))
(interpolation_closing))))))
================================================================================
Raw String literal with \
================================================================================
r'\'
r"\"
--------------------------------------------------------------------------------
(source_file
(simple_statement
(literal
(raw_string_literal)))
(simple_statement
(literal
(raw_string_literal))))
================================================
FILE: tree_sitter_v/test/corpus/struct_declaration.txt
================================================
================================================================================
Simple struct
================================================================================
struct Foo {
name string
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Simple public struct
================================================================================
pub struct Foo {
name string
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(visibility_modifiers)
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Simple union
================================================================================
union Foo {
name string
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Simple empty struct
================================================================================
struct Foo {}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)))
================================================================================
Simple struct with attribute
================================================================================
[heap]
struct Foo {
name string
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Simple struct with attribute and comment
================================================================================
// This is a comment
[heap]
struct Foo {
name string
}
--------------------------------------------------------------------------------
(source_file
(line_comment)
(struct_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Simple struct with several fields
================================================================================
struct Foo {
name string
age int
other f32
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Simple struct with several fields with different scopes
================================================================================
struct Foo {
name string
mut:
age int
pub:
other f32
pub mut:
other2 bool
__global:
other3 i8
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Simple struct with modifier with space before colon
================================================================================
struct Foo {
mut :
age int
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Simple struct with not ended modifier
================================================================================
struct Foo {
mut
age int
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(ERROR)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Simple struct with several fields and embedding
================================================================================
struct Foo {
Embedded
name string
age int
other f32
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(embedded_definition
(type_reference_expression
(identifier))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Simple struct with several embedding
================================================================================
struct Foo {
Embedded
Embedded2
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(embedded_definition
(type_reference_expression
(identifier))))
(struct_field_declaration
(embedded_definition
(type_reference_expression
(identifier))))))
================================================================================
Simple struct with qualified embedding
================================================================================
struct Foo {
foo.Embedded
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(embedded_definition
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))))))
================================================================================
Simple struct with several embedding and fields
================================================================================
struct Foo {
Embedded
Embedded2
name string
mut:
age int
pub:
other f32
pub mut:
other2 bool
__global:
other3 i8
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(embedded_definition
(type_reference_expression
(identifier))))
(struct_field_declaration
(embedded_definition
(type_reference_expression
(identifier))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Simple struct with fields with default values
================================================================================
struct Foo {
name bool = true
age int = 10
other f32 = 3.14
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(literal
(true)))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(literal
(int_literal)))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(literal
(float_literal)))))
================================================================================
Simple struct with fields with attributes
================================================================================
struct Foo {
name string [attr]
age int [attr]
other f32 [attr; omitempty]
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))))
================================================================================
Simple struct with fields with attributes and default values
================================================================================
struct Foo {
name string [attr]
age int = 10 [attr]
other f32 = 3.14 [attr; omitempty]
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(literal
(int_literal))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(literal
(float_literal))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))))
================================================================================
Simple generic struct
================================================================================
struct Foo[T] {
value T
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(generic_parameters
(generic_parameter
(identifier)))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Generic struct with several generic parameters
================================================================================
struct Foo[T, U, V] {
value T
value2 U
value3 V
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(generic_parameters
(generic_parameter
(identifier))
(generic_parameter
(identifier))
(generic_parameter
(identifier)))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
C struct
================================================================================
[typedef]
struct C.Foo {
name string
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
JS struct
================================================================================
[heap]
struct JS.Foo {
name string
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(attributes
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Struct all in one
================================================================================
struct Hello {
hidden string
pub:
foo string [attr]
bar int = 10
pub mut:
baz int
__global:
blu i64
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(identifier)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(attribute
(attribute_expression
(value_attribute
(reference_expression
(identifier))))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))
(literal
(int_literal)))
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))
(struct_field_scope)
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Struct with single explicit interface implementation
================================================================================
pub struct Foo implements Bar {
name string
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(visibility_modifiers)
(identifier)
(implements_clause
(type_reference_expression
(identifier)))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Struct with multiple explicit interface implementations
================================================================================
pub struct Foo implements Interface1, Interface2, qualified.Interface3 {
name string
}
--------------------------------------------------------------------------------
(source_file
(struct_declaration
(visibility_modifiers)
(identifier)
(implements_clause
(type_reference_expression
(identifier))
(type_reference_expression
(identifier))
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier))))
(struct_field_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier))))))
================================================
FILE: tree_sitter_v/test/corpus/type_declaration.txt
================================================
================================================================================
Simple type declaration
================================================================================
type String = string
--------------------------------------------------------------------------------
(source_file
(type_declaration
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
================================================================================
Simple type declaration with visibility modifier
================================================================================
pub type String = string
--------------------------------------------------------------------------------
(source_file
(type_declaration
(visibility_modifiers)
(identifier)
(plain_type
(type_reference_expression
(identifier)))))
================================================================================
Generic type declaration
================================================================================
type Container[T] = []T
--------------------------------------------------------------------------------
(source_file
(type_declaration
(identifier)
(generic_parameters
(generic_parameter
(identifier)))
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))))
================================================================================
Generic type declaration with several parameters
================================================================================
type Container[T, U] = map[T]U
--------------------------------------------------------------------------------
(source_file
(type_declaration
(identifier)
(generic_parameters
(generic_parameter
(identifier))
(generic_parameter
(identifier)))
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier)))))))
================================================================================
Simple sum type declaration
================================================================================
type Height = string | int
--------------------------------------------------------------------------------
(source_file
(type_declaration
(identifier)
(sum_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Sum type declaration with several types
================================================================================
type Height = string | int | float
--------------------------------------------------------------------------------
(source_file
(type_declaration
(identifier)
(sum_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Sum type declaration with same type
================================================================================
type Height = string | int | []Height
--------------------------------------------------------------------------------
(source_file
(type_declaration
(identifier)
(sum_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))))
================================================================================
Sum type declaration with several types on each line
================================================================================
type Height = string |
int |
float
--------------------------------------------------------------------------------
(source_file
(type_declaration
(identifier)
(sum_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Sum type declaration with several types on each line, first line is empty
================================================================================
type Height =
string |
int |
float
--------------------------------------------------------------------------------
(source_file
(type_declaration
(identifier)
(sum_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))
================================================================================
Sum type declaration with several types on each line and trailing pipe
================================================================================
type Height =
string |
int |
float
--------------------------------------------------------------------------------
(source_file
(type_declaration
(identifier)
(sum_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))
================================================
FILE: tree_sitter_v/test/corpus/type_initializer.txt
================================================
================================================================================
Simple type initializer
================================================================================
Foo{}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(type_reference_expression
(identifier)))
(type_initializer_body))))
================================================================================
Array type initializer
================================================================================
[]int{}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(type_initializer_body))))
================================================================================
Array type initializer with field
================================================================================
[]int{cap: 100}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(type_initializer_body
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal))))))))
================================================================================
Array type initializer with fields
================================================================================
[]int{len: 0, cap: 100}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(type_initializer_body
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal))))))))
================================================================================
Array type initializer with all fields
================================================================================
[]int{len: 0, cap: 100, init: index * 100}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(type_initializer_body
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(binary_expression
(reference_expression
(identifier))
(literal
(int_literal)))))))))
================================================================================
Simple type initializer with field
================================================================================
Foo{
age: 100
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(type_reference_expression
(identifier)))
(type_initializer_body
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal))))))))
================================================================================
Simple type initializer with fields
================================================================================
Foo{
age: 100
name: "John"
weight: 100.0
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(type_reference_expression
(identifier)))
(type_initializer_body
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(interpreted_string_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(float_literal))))))))
================================================================================
Simple type initializer with unpacking
================================================================================
Foo{
...boo
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(type_reference_expression
(identifier)))
(type_initializer_body
(element_list
(spread_expression
(reference_expression
(identifier))))))))
================================================================================
Simple type initializer with unpacking and other fields
================================================================================
Foo{
...boo
age: 100
name: "John"
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(type_reference_expression
(identifier)))
(type_initializer_body
(element_list
(spread_expression
(reference_expression
(identifier)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(interpreted_string_literal))))))))
================================================================================
Simple type initializer with just value
================================================================================
Foo{name}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(type_reference_expression
(identifier)))
(type_initializer_body
(element_list
(reference_expression
(identifier)))))))
================================================================================
Simple type initializer with just values
================================================================================
Foo{name, other, 100}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(type_reference_expression
(identifier)))
(type_initializer_body
(short_element_list
(element
(reference_expression
(identifier)))
(element
(reference_expression
(identifier)))
(element
(literal
(int_literal))))))))
================================================================================
Type initializer for embedded struct
================================================================================
Foo{
Bar: Bar{
name: "John"
age: 100
}
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(type_reference_expression
(identifier)))
(type_initializer_body
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(type_initializer
(plain_type
(type_reference_expression
(identifier)))
(type_initializer_body
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(interpreted_string_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal))))))))))))
================================================================================
Type initializer as field for type initializer
================================================================================
Foo{
name: "John"
age: 100
other: Bar{
name: "John"
age: 100
}
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(type_reference_expression
(identifier)))
(type_initializer_body
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(interpreted_string_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(type_initializer
(plain_type
(type_reference_expression
(identifier)))
(type_initializer_body
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(interpreted_string_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal))))))))))))
================================================================================
Generic type initializer
================================================================================
Foo[int, string]{
name: "John"
age: 100
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(generic_type
(type_reference_expression
(identifier))
(type_parameters
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))
(type_initializer_body
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(interpreted_string_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal))))))))
================================================================================
Type initializer for C structs
================================================================================
C.Foo{
name: "John"
age: 100
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(type_reference_expression
(identifier)))
(type_initializer_body
(element_list
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(interpreted_string_literal)))
(keyed_element
(field_name
(reference_expression
(identifier)))
(literal
(int_literal))))))))
================================================================================
Type initializer for Channel type
================================================================================
chan f64{}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(channel_type
(plain_type
(type_reference_expression
(identifier)))))
(type_initializer_body))))
================================================================================
Type initializer for Map type
================================================================================
map[string]int{}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier)))))
(type_initializer_body))))
================================================================================
Qualified type initializer
================================================================================
psi.StubIndexSink{}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier))))
(type_initializer_body))))
================================================================================
Qualified type initializer for pointer
================================================================================
&psi.StubIndexSink{}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(type_initializer
(plain_type
(pointer_type
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier))))))
(type_initializer_body))))
================================================================================
Deep array initialization (TODO)
================================================================================
[][]u8{len: 20, cap: 20}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(index_expression
(array_creation)
(reference_expression
(MISSING identifier))))
(simple_statement
(reference_expression
(identifier)))
(simple_statement
(map_init_expression
(map_keyed_element
(reference_expression
(identifier))
(literal
(int_literal)))
(map_keyed_element
(reference_expression
(identifier))
(literal
(int_literal))))))
================================================
FILE: tree_sitter_v/test/corpus/types.txt
================================================
================================================================================
Simple types
================================================================================
fn () int {}
fn () string {}
fn () f32 {}
fn () f64 {}
fn () Foo {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier))))
(block))))
================================================================================
Binded types
================================================================================
fn () C.int {}
fn () C.Type {}
fn () C.Foo {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(type_reference_expression
(identifier))))
(block))))
================================================================================
Qualified types
================================================================================
fn () mod.Foo {}
fn () mod.string {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))))
(block))))
================================================================================
Pointer type
================================================================================
fn () &string {}
fn () &&Foo {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(pointer_type
(plain_type
(type_reference_expression
(identifier))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(pointer_type
(plain_type
(pointer_type
(plain_type
(type_reference_expression
(identifier))))))))
(block))))
================================================================================
Wrong pointer type
================================================================================
fn () *string {}
fn () **Foo {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(wrong_pointer_type
(plain_type
(type_reference_expression
(identifier))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(wrong_pointer_type
(plain_type
(wrong_pointer_type
(plain_type
(type_reference_expression
(identifier))))))))
(block))))
================================================================================
Array type
================================================================================
fn () []string {}
fn () [][]Foo {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(array_type
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))))
(block))))
================================================================================
Fixed array type
================================================================================
fn () [4]string {}
fn () [2][5]Foo {}
fn () [const_value][5]Foo {}
fn () [psi.const_value][5]Foo {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(fixed_array_type
(int_literal)
(plain_type
(type_reference_expression
(identifier))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(fixed_array_type
(int_literal)
(plain_type
(fixed_array_type
(int_literal)
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(fixed_array_type
(reference_expression
(identifier))
(plain_type
(fixed_array_type
(int_literal)
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(fixed_array_type
(selector_expression
(reference_expression
(identifier))
(reference_expression
(identifier)))
(plain_type
(fixed_array_type
(int_literal)
(plain_type
(type_reference_expression
(identifier))))))))
(block))))
================================================================================
Function type
================================================================================
fn () fn () {}
fn () fn (string) {}
fn () fn (int, bool) []string {}
fn () fn (int, bool) fn (int, bool) []string {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(function_type
(signature
(parameter_list)))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(function_type
(signature
(type_parameter_list
(type_parameter_declaration
(plain_type
(type_reference_expression
(identifier)))))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(function_type
(signature
(type_parameter_list
(type_parameter_declaration
(plain_type
(type_reference_expression
(identifier))))
(type_parameter_declaration
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(function_type
(signature
(type_parameter_list
(type_parameter_declaration
(plain_type
(type_reference_expression
(identifier))))
(type_parameter_declaration
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(function_type
(signature
(type_parameter_list
(type_parameter_declaration
(plain_type
(type_reference_expression
(identifier))))
(type_parameter_declaration
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))))))))
(block))))
================================================================================
Generic type
================================================================================
fn () Foo[string] {}
fn () mod.Bar[string, int] {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(generic_type
(type_reference_expression
(identifier))
(type_parameters
(plain_type
(type_reference_expression
(identifier)))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(generic_type
(qualified_type
(reference_expression
(identifier))
(type_reference_expression
(identifier)))
(type_parameters
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier)))))))
(block))))
================================================================================
Map type
================================================================================
fn () map[string]int {}
fn () map[Foo][]int {}
fn () map[[]Foo]map[Foo][]int {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(map_type
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))))))
(block))))
================================================================================
Channel type
================================================================================
fn () chan int {}
fn () chan []string {}
fn () chan map[Foo]string {}
fn () chan chan string {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(channel_type
(plain_type
(type_reference_expression
(identifier))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(channel_type
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(channel_type
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(channel_type
(plain_type
(channel_type
(plain_type
(type_reference_expression
(identifier))))))))
(block))))
================================================================================
Shared type
================================================================================
fn () shared int {}
fn () shared []string {}
fn () shared map[Foo]string {}
fn () shared chan string {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(shared_type
(plain_type
(type_reference_expression
(identifier))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(shared_type
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(shared_type
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(shared_type
(plain_type
(channel_type
(plain_type
(type_reference_expression
(identifier))))))))
(block))))
================================================================================
Thread type
================================================================================
fn () thread int {}
fn () thread []string {}
fn () thread map[Foo]string {}
fn () thread thread string {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(thread_type
(plain_type
(type_reference_expression
(identifier))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(thread_type
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(thread_type
(plain_type
(map_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(thread_type
(plain_type
(thread_type
(plain_type
(type_reference_expression
(identifier))))))))
(block))))
================================================================================
Option type
================================================================================
fn (p ?int) {}
fn () ?int {}
fn () ?(int, string) {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list
(parameter_declaration
(identifier)
(plain_type
(option_type
(plain_type
(type_reference_expression
(identifier))))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(option_type
(plain_type
(type_reference_expression
(identifier))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(option_type
(plain_type
(multi_return_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))))
(block))))
================================================================================
Result type
================================================================================
fn () !int {}
fn () !(int, string) {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(result_type
(plain_type
(type_reference_expression
(identifier))))))
(block)))
(simple_statement
(function_literal
(signature
(parameter_list)
(plain_type
(result_type
(plain_type
(multi_return_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))))
(block))))
================================================================================
Type union type
================================================================================
pub type Any = Null
| []Any
| bool
| f32
| f64
--------------------------------------------------------------------------------
(source_file
(type_declaration
(visibility_modifiers)
(identifier)
(sum_type
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(array_type
(plain_type
(type_reference_expression
(identifier)))))
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier)))
(plain_type
(type_reference_expression
(identifier))))))
================================================
FILE: tree_sitter_v/test/corpus/unsafe_expression.txt
================================================
================================================================================
Simple unsafe expression
================================================================================
unsafe {}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(unsafe_expression
(block))))
================================================================================
Simple unsafe expression with several statements
================================================================================
unsafe {
foo()
bar()
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(unsafe_expression
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list)))
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list)))))))
================================================================================
Simple unsafe expression as an expression
================================================================================
a := unsafe {
foo()
}
--------------------------------------------------------------------------------
(source_file
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(unsafe_expression
(block
(simple_statement
(call_expression
(reference_expression
(identifier))
(argument_list)))))))))
================================================
FILE: tree_sitter_v/test/corpus/var_declaration.txt
================================================
================================================================================
Simple var declaration
================================================================================
foo := bar
--------------------------------------------------------------------------------
(source_file
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier)))
(expression_list
(reference_expression
(identifier))))))
================================================================================
Several var declarations
================================================================================
foo, goo, poo := bar, far, tar
--------------------------------------------------------------------------------
(source_file
(simple_statement
(var_declaration
(expression_list
(reference_expression
(identifier))
(reference_expression
(identifier))
(reference_expression
(identifier)))
(expression_list
(reference_expression
(identifier))
(reference_expression
(identifier))
(reference_expression
(identifier))))))
================================================
FILE: tree_sitter_v/tree-sitter.json
================================================
{
"grammars": [
{
"name": "v",
"camelcase": "V",
"scope": "source.v",
"path": ".",
"file-types": [
"v",
"vsh",
"v.mod"
]
}
],
"metadata": {
"version": "0.0.6",
"license": "MIT",
"description": "V grammar for tree-sitter",
"links": {
"repository": "https://github.com/vlang/v-analyzer.git"
}
}
}
================================================
FILE: v.mod
================================================
Module {
name: 'v-analyzer'
description: 'Language server implementation for the V (vlang) programming language'
version: '0.0.6'
license: 'MIT'
dependencies: []
}