Repository: vlang/v-analyzer Branch: main Commit: d5f13c0736f0 Files: 525 Total size: 13.5 MB Directory structure: gitextract_69osocjt/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ ├── config.yml │ │ └── feature-request.yml │ └── workflows/ │ ├── analyzer_tests.yml │ ├── build_ci.yml │ ├── install_ci.yml │ ├── lint.yml │ ├── release.yml │ ├── tree_sitter_v.yml │ ├── version_test.vv │ └── vscode_extension_tests.yml ├── .gitignore ├── .gitmodules ├── .v-analyzer/ │ └── config.toml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.vsh ├── editors/ │ └── code/ │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .gitignore │ ├── .vscodeignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── docs/ │ │ └── troubleshooting.md │ ├── languages/ │ │ ├── v-language-configuration.json │ │ └── vmod-language-configuration.json │ ├── media/ │ │ └── welcome.css │ ├── package.json │ ├── scripts/ │ │ ├── build.js │ │ └── minify_json.js │ ├── src/ │ │ ├── bootstrap.ts │ │ ├── client.ts │ │ ├── commands.ts │ │ ├── ctx.ts │ │ ├── exec.ts │ │ ├── extension.ts │ │ ├── log.ts │ │ ├── lsp_ext.ts │ │ ├── stateUtils.ts │ │ ├── tcp.ts │ │ ├── utils.ts │ │ └── welcome.ts │ ├── syntaxes/ │ │ ├── stree.tmGrammar.json │ │ ├── tests/ │ │ │ ├── accessor.vv │ │ │ ├── comma.vv │ │ │ ├── comment.vv │ │ │ ├── comparison.vv │ │ │ ├── dot.int.vv │ │ │ ├── escape.vv │ │ │ ├── hashtag.vv │ │ │ ├── method.vv │ │ │ ├── numbers.vv │ │ │ ├── optional.vv │ │ │ ├── pubfn.vv │ │ │ ├── string.vv │ │ │ ├── type.vv │ │ │ └── variable.vv │ │ ├── v.mod.tmLanguage.json │ │ └── v.tmLanguage.json │ └── tsconfig.json ├── install.vsh ├── src/ │ ├── analyzer/ │ │ ├── Indexer.v │ │ ├── IndexingManager.v │ │ ├── OpenedFile.v │ │ ├── README.md │ │ ├── index/ │ │ │ ├── FileIndex.v │ │ │ ├── Index.v │ │ │ ├── IndexDeserializer.v │ │ │ ├── IndexSerializer.v │ │ │ ├── IndexingRoot.v │ │ │ ├── IndexingRoot_test.v │ │ │ ├── PerFileIndex.v │ │ │ ├── README.md │ │ │ └── StubTree.v │ │ ├── lang/ │ │ │ └── utils.v │ │ ├── parser/ │ │ │ ├── README.md │ │ │ ├── batch.v │ │ │ └── parser.v │ │ └── psi/ │ │ ├── Argument.v │ │ ├── ArrayCreation.v │ │ ├── AstNode.v │ │ ├── Attribute.v │ │ ├── AttributeExpression.v │ │ ├── Attributes.v │ │ ├── AttributesOwner.v │ │ ├── BinaryExpression.v │ │ ├── Block.v │ │ ├── CallExpression.v │ │ ├── Comment.v │ │ ├── CompileTimeIfExpression.v │ │ ├── ConstantDeclaration.v │ │ ├── ConstantDefinition.v │ │ ├── EmbeddedDefinition.v │ │ ├── EnumDeclaration.v │ │ ├── EnumFieldDeclaration.v │ │ ├── FieldDeclaration.v │ │ ├── FieldName.v │ │ ├── ForStatement.v │ │ ├── FunctionLiteral.v │ │ ├── FunctionOrMethodDeclaration.v │ │ ├── GenericArgumentsOwner.v │ │ ├── GenericParameter.v │ │ ├── GenericParameters.v │ │ ├── GenericParametersOwner.v │ │ ├── GenericTypeArguments.v │ │ ├── GenericTypeInferer.v │ │ ├── GenericTypeReifier.v │ │ ├── GlobalVarDefinition.v │ │ ├── Identifier.v │ │ ├── IfExpression.v │ │ ├── ImportAlias.v │ │ ├── ImportDeclaration.v │ │ ├── ImportList.v │ │ ├── ImportName.v │ │ ├── ImportPath.v │ │ ├── ImportSpec.v │ │ ├── IndexExpression.v │ │ ├── InterfaceDeclaration.v │ │ ├── InterfaceMethodDeclaration.v │ │ ├── KeyedElement.v │ │ ├── Literal.v │ │ ├── MapInitExpression.v │ │ ├── MapKeyedElement.v │ │ ├── MatchExpression.v │ │ ├── ModuleClause.v │ │ ├── MutExpression.v │ │ ├── MutabilityModifiers.v │ │ ├── MutabilityOwner.v │ │ ├── OptionPropagationExpression.v │ │ ├── OrBlockExpression.v │ │ ├── ParameterDeclaration.v │ │ ├── ParameterList.v │ │ ├── PlainType.v │ │ ├── Position.v │ │ ├── PrinterVisitor.v │ │ ├── PsiDocCommentOwner.v │ │ ├── PsiElement.v │ │ ├── PsiElementImpl.v │ │ ├── PsiElementVisitor.v │ │ ├── PsiFile.v │ │ ├── PsiNamedElement.v │ │ ├── PsiReference.v │ │ ├── PsiScopeProcessor.v │ │ ├── PsiTreeWalker.v │ │ ├── PsiTypedElement.v │ │ ├── QualifiedType.v │ │ ├── README.md │ │ ├── Range.v │ │ ├── Receiver.v │ │ ├── RecursiveVisitor.v │ │ ├── ReferenceExpression.v │ │ ├── ReferenceExpressionBase.v │ │ ├── ReferenceImpl.v │ │ ├── ResolveCache.v │ │ ├── ResultPropagationExpression.v │ │ ├── SelectiveImportList.v │ │ ├── SelectorExpression.v │ │ ├── Signature.v │ │ ├── SignatureOwner.v │ │ ├── SliceExpression.v │ │ ├── SourceFile.v │ │ ├── StaticMethodDeclaration.v │ │ ├── StaticReceiver.v │ │ ├── StringLiteral.v │ │ ├── StructDeclaration.v │ │ ├── StructFieldScope.v │ │ ├── StubBase.v │ │ ├── StubBasedPsiElement.v │ │ ├── StubElement.v │ │ ├── StubIndex.v │ │ ├── StubIndexSink.v │ │ ├── StubList.v │ │ ├── StubbedElementTypeImpl.v │ │ ├── TextRange.v │ │ ├── TreeWalker.v │ │ ├── TypeAliasDeclaration.v │ │ ├── TypeCache.v │ │ ├── TypeInferer.v │ │ ├── TypeInitializer.v │ │ ├── TypeReferenceExpression.v │ │ ├── UnaryExpression.v │ │ ├── UnsafeExpression.v │ │ ├── ValueAttribute.v │ │ ├── VarDeclaration.v │ │ ├── VarDefinition.v │ │ ├── VisibilityModifiers.v │ │ ├── doc_comment_extractor.v │ │ ├── element_factory.v │ │ ├── search/ │ │ │ ├── ReferencesSearch.v │ │ │ ├── common.v │ │ │ ├── implementations.v │ │ │ ├── implmenttion_methods.v │ │ │ ├── super_methods.v │ │ │ └── supers.v │ │ ├── types/ │ │ │ ├── AliasType.v │ │ │ ├── ArrayType.v │ │ │ ├── BaseNamedType.v │ │ │ ├── BaseType.v │ │ │ ├── ChannelType.v │ │ │ ├── EnumType.v │ │ │ ├── FixedArrayType.v │ │ │ ├── FunctionType.v │ │ │ ├── GenericInstantiationType.v │ │ │ ├── GenericType.v │ │ │ ├── InterfaceType.v │ │ │ ├── MapType.v │ │ │ ├── MultiReturnType.v │ │ │ ├── OptionType.v │ │ │ ├── PointerType.v │ │ │ ├── PrimitiveType.v │ │ │ ├── ResultType.v │ │ │ ├── StructType.v │ │ │ ├── ThreadType.v │ │ │ ├── Type.v │ │ │ ├── TypeVisitor.v │ │ │ ├── UnknownType.v │ │ │ ├── VoidPtrType.v │ │ │ └── helpers.v │ │ ├── types_util.v │ │ ├── utils.v │ │ └── walk.v │ ├── bytes/ │ │ ├── README.md │ │ ├── deserialize.v │ │ ├── serialization_test.v │ │ └── serialize.v │ ├── check-updates.v │ ├── clear-cache.v │ ├── config/ │ │ ├── EditorConfig.v │ │ └── constants.v │ ├── init.v │ ├── jsonrpc/ │ │ ├── README.md │ │ ├── jsonrpc.v │ │ ├── jsonrpc_test.v │ │ ├── server.v │ │ ├── server_test.v │ │ └── server_test_utils/ │ │ └── server_test_utils.v │ ├── loglib/ │ │ ├── ColorMode.v │ │ ├── Entry.v │ │ ├── Formatter.v │ │ ├── LogLevel.v │ │ ├── Logger.v │ │ ├── TextFormatter.v │ │ ├── log.v │ │ └── utils.v │ ├── lsp/ │ │ ├── README.md │ │ ├── capabilities.v │ │ ├── client.v │ │ ├── code_action.v │ │ ├── code_lens.v │ │ ├── color_presentation.v │ │ ├── completion.v │ │ ├── declaration.v │ │ ├── definition.v │ │ ├── diagnostics.v │ │ ├── document_color.v │ │ ├── document_highlight.v │ │ ├── document_link.v │ │ ├── document_symbol.v │ │ ├── ext.v │ │ ├── file_resource.v │ │ ├── folding_range.v │ │ ├── formatting.v │ │ ├── hover.v │ │ ├── implementation.v │ │ ├── initialize.v │ │ ├── inlay_hint.v │ │ ├── log/ │ │ │ ├── log.v │ │ │ └── log_test.v │ │ ├── lsp.v │ │ ├── lsp_test.v │ │ ├── progress.v │ │ ├── references.v │ │ ├── rename.v │ │ ├── semantic_tokens.v │ │ ├── signature_help.v │ │ ├── symbol.v │ │ ├── text_document.v │ │ ├── text_sync.v │ │ ├── window.v │ │ └── workspace.v │ ├── main.v │ ├── metadata/ │ │ ├── metadata.v │ │ └── stubs/ │ │ ├── README.md │ │ ├── arrays.v │ │ ├── attributes/ │ │ │ ├── Attribute.v │ │ │ ├── Deprecated.v │ │ │ ├── DeprecatedAfter.v │ │ │ ├── Flag.v │ │ │ ├── Heap.v │ │ │ ├── Json.v │ │ │ ├── JsonAsNumber.v │ │ │ ├── Manualfree.v │ │ │ ├── Noinit.v │ │ │ ├── Noreturn.v │ │ │ ├── Omitempty.v │ │ │ ├── Table.v │ │ │ └── Unsafe.v │ │ ├── builtin_compile_time.v │ │ ├── c_decl.v │ │ ├── channels.v │ │ ├── compile_time.v │ │ ├── compile_time_constants.v │ │ ├── compile_time_reflection.v │ │ ├── errors.v │ │ ├── implicit.v │ │ ├── primitives.v │ │ ├── threads.v │ │ └── vweb.v │ ├── project/ │ │ ├── flavors/ │ │ │ ├── MacToolchainFlavor.v │ │ │ ├── SymlinkToolchainFlavor.v │ │ │ ├── SysPathToolchainFlavor.v │ │ │ ├── ToolchainFlavor.v │ │ │ ├── UserHomeToolchainFlavor.v │ │ │ ├── VenvToolchainFlavor.v │ │ │ └── WinToolchainFlavor.v │ │ └── project.v │ ├── server/ │ │ ├── BackgroundThread.v │ │ ├── README.md │ │ ├── ResponseWriter.v │ │ ├── code_lens/ │ │ │ └── CodeLensVisitor.v │ │ ├── completion/ │ │ │ ├── CompletionContext.v │ │ │ ├── CompletionProvider.v │ │ │ ├── CompletionResultSet.v │ │ │ └── providers/ │ │ │ ├── AssertCompletionProvider.v │ │ │ ├── AttributesCompletionProvider.v │ │ │ ├── CompileTimeConstantCompletionProvider.v │ │ │ ├── FunctionLikeCompletionProvider.v │ │ │ ├── ImportsCompletionProvider.v │ │ │ ├── InitsCompletionProvider.v │ │ │ ├── JsonAttributeCompletionProvider.v │ │ │ ├── KeywordsCompletionProvider.v │ │ │ ├── LoopKeywordsCompletionProvider.v │ │ │ ├── ModuleNameCompletionProvider.v │ │ │ ├── ModulesImportProvider.v │ │ │ ├── NilKeywordCompletionProvider.v │ │ │ ├── OrBlockExpressionCompletionProvider.v │ │ │ ├── PureBlockExpressionCompletionProvider.v │ │ │ ├── PureBlockStatementCompletionProvider.v │ │ │ ├── ReferenceCompletionProcessor.v │ │ │ ├── ReferenceCompletionProvider.v │ │ │ ├── ReturnCompletionProvider.v │ │ │ ├── StructLiteralCompletion.v │ │ │ └── TopLevelCompletionProvider.v │ │ ├── diagnostics.v │ │ ├── documentation/ │ │ │ ├── keywords_provider.v │ │ │ └── provider.v │ │ ├── features_code_actions.v │ │ ├── features_code_lens.v │ │ ├── features_completion.v │ │ ├── features_definition.v │ │ ├── features_did_change.v │ │ ├── features_did_change_watched_files.v │ │ ├── features_did_close.v │ │ ├── features_did_open.v │ │ ├── features_did_save.v │ │ ├── features_document_highlight.v │ │ ├── features_document_symbol.v │ │ ├── features_execute_command.v │ │ ├── features_folding_range.v │ │ ├── features_formatting.v │ │ ├── features_formatting_test.v │ │ ├── features_hover.v │ │ ├── features_implementation.v │ │ ├── features_inlay_hints.v │ │ ├── features_prepare_rename.v │ │ ├── features_references.v │ │ ├── features_rename.v │ │ ├── features_semantic_tokens.v │ │ ├── features_signature_help.v │ │ ├── features_type_definition.v │ │ ├── features_view_stub_tree.v │ │ ├── features_workspace_symbol.v │ │ ├── file_diff/ │ │ │ └── Diff.v │ │ ├── folding/ │ │ │ └── visitor.v │ │ ├── general.v │ │ ├── hints/ │ │ │ └── InlayHintsVisitor.v │ │ ├── inspections/ │ │ │ ├── Report.v │ │ │ ├── ReportsSource.v │ │ │ └── compiler/ │ │ │ ├── CompilerReportsSource.v │ │ │ └── utils.v │ │ ├── intentions/ │ │ │ ├── AddFlagAttributeIntention.v │ │ │ ├── AddHeapAttributeIntention.v │ │ │ ├── CompilerQuickFix.v │ │ │ ├── ImportModuleQuickFix.v │ │ │ ├── Intention.v │ │ │ ├── MakeMutableQuickFix.v │ │ │ ├── MakePublicIntention.v │ │ │ └── utils.v │ │ ├── language_server.v │ │ ├── progress/ │ │ │ └── progress.v │ │ ├── protocol/ │ │ │ └── Client.v │ │ ├── semantic/ │ │ │ ├── DumbAwareSemanticVisitor.v │ │ │ ├── ResolvingSemanticVisitor.v │ │ │ ├── SemanticToken.v │ │ │ ├── constants.v │ │ │ └── encode.v │ │ ├── setup_test.v │ │ ├── tform/ │ │ │ ├── README.md │ │ │ └── tform.v │ │ └── workspace/ │ │ └── ProjectResolver.v │ ├── streams/ │ │ └── streams.v │ ├── testing/ │ │ ├── Benchmark.v │ │ ├── BenchmarkRunner.v │ │ ├── Test.v │ │ ├── TestFixture.v │ │ ├── Tester.v │ │ └── client/ │ │ └── TestClient.v │ ├── tests/ │ │ ├── analyzer_test.v │ │ ├── bench.v │ │ ├── completion.v │ │ ├── definitions.v │ │ ├── documentation.v │ │ ├── implementations.v │ │ ├── supers.v │ │ ├── testdata/ │ │ │ ├── benchmarks/ │ │ │ │ ├── checker.vv │ │ │ │ └── inlay_hints.vv │ │ │ ├── documentation/ │ │ │ │ ├── rendered.vv │ │ │ │ ├── rendered.vv.md │ │ │ │ ├── stubs.vv │ │ │ │ └── stubs.vv.md │ │ │ └── types/ │ │ │ ├── bool_operators.vv │ │ │ ├── call_expression.vv │ │ │ ├── chan_type.vv │ │ │ ├── constants.vv │ │ │ ├── fields.vv │ │ │ ├── for_loop.vv │ │ │ ├── for_loops.vv │ │ │ ├── function_literal.vv │ │ │ ├── generics.vv │ │ │ ├── if_expression.vv │ │ │ ├── json_decode.vv │ │ │ ├── literals.vv │ │ │ ├── map_init_expression.vv │ │ │ ├── match_expression.vv │ │ │ ├── parameters.vv │ │ │ ├── pointers.vv │ │ │ ├── receiver.vv │ │ │ ├── slice_and_index_expression.vv │ │ │ ├── type_initializer.vv │ │ │ └── unsafe_expression.vv │ │ └── types.v │ ├── tools/ │ │ └── project-checker.v │ ├── up.v │ ├── utils/ │ │ ├── text_utils.v │ │ └── text_utils_test.v │ └── utils.v ├── tree_sitter_v/ │ ├── .gitattributes │ ├── .gitignore │ ├── .prettierrc.js │ ├── LICENSE │ ├── README.md │ ├── bindings/ │ │ ├── bindings.c.v │ │ ├── bindings.h │ │ ├── bindings.v │ │ ├── generate_types.vsh │ │ ├── node_types.v │ │ └── simple_test.v │ ├── examples/ │ │ ├── cursor.v │ │ ├── simple.v │ │ └── with_old_tree.v │ ├── grammar.js │ ├── package.json │ ├── queries/ │ │ ├── helix.highlights.scm │ │ └── highlights.scm │ ├── src/ │ │ ├── grammar.json │ │ ├── node-types.json │ │ ├── parser.c │ │ └── tree_sitter/ │ │ ├── alloc.h │ │ ├── array.h │ │ └── parser.h │ ├── test/ │ │ └── corpus/ │ │ ├── anon_struct.txt │ │ ├── array_creation.txt │ │ ├── assert_statement.txt │ │ ├── attributes.txt │ │ ├── bitshift_left.txt │ │ ├── call_expression.txt │ │ ├── channels.txt │ │ ├── comments.txt │ │ ├── compile_time.txt │ │ ├── compile_time_selector_expression.txt │ │ ├── const_declaration.txt │ │ ├── enum_declaration.txt │ │ ├── enum_fetch.txt │ │ ├── error_propagation.txt │ │ ├── expression_list.txt │ │ ├── for_statement.txt │ │ ├── function_declaration.txt │ │ ├── function_literal.txt │ │ ├── generics.txt │ │ ├── global_var_declaration.txt │ │ ├── hash_statement.txt │ │ ├── if_expression.txt │ │ ├── imports.txt │ │ ├── in_expression.txt │ │ ├── interface_declaration.txt │ │ ├── is_as_expression.txt │ │ ├── json_call.txt │ │ ├── labeled_statement.txt │ │ ├── lock_expression.txt │ │ ├── map_init_expression.txt │ │ ├── match_expression.txt │ │ ├── method_declaration.txt │ │ ├── module_clause.txt │ │ ├── safe_access.txt │ │ ├── select_expression.txt │ │ ├── shebang.txt │ │ ├── slice_expression.txt │ │ ├── source_file.txt │ │ ├── spawn_expression.txt │ │ ├── special_call_expression.txt │ │ ├── static_method_declaration.txt │ │ ├── string_literal.txt │ │ ├── struct_declaration.txt │ │ ├── type_declaration.txt │ │ ├── type_initializer.txt │ │ ├── types.txt │ │ ├── unsafe_expression.txt │ │ └── var_declaration.txt │ └── tree-sitter.json └── v.mod ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.{v,js,json}] indent_style = tab [*.md] max_line_length = 100 ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf *.bat eol=crlf **/*.v linguist-language=V **/*.vv linguist-language=V **/*.vsh linguist-language=V **/v.mod linguist-language=V ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ --- name: 🐛 Bug Report description: Report a bug title: (please, provide bug report summary here) labels: [ bug ] body: - type: textarea id: description attributes: label: Describe the bug description: What is the problem? A clear and concise description of the bug. validations: required: true - type: textarea id: expected attributes: label: Expected Behavior description: What did you expect to happen? validations: required: true - type: textarea id: current attributes: label: Current Behavior description: | What actually happened? Please include full errors, uncaught exceptions, stack traces, and relevant logs. If service/functions responses are relevant, please include wire logs. validations: required: true - type: textarea id: reproduction attributes: label: Reproduction Steps description: | Provide a self-contained, concise snippet of code that can be used to reproduce the issue. For more complex issues provide a repo with the smallest sample that reproduces the bug. Avoid including business logic or unrelated code, it makes diagnosis more difficult. The code sample should be an SSCCE. See http://sscce.org/ for details. In short, please provide a code sample that we can copy/paste, run and reproduce. validations: required: true - type: textarea id: solution attributes: label: Possible Solution description: Suggest a fix/reason for the bug validations: required: false - type: textarea id: context attributes: label: Additional Information/Context description: | Anything else that might be relevant for troubleshooting this bug. Providing context helps us come up with a solution that is most useful in the real world. validations: required: false - type: textarea id: environment attributes: label: Environment details (`v doctor` output) validations: required: true - type: input id: editor-name attributes: label: Editor name validations: required: true - type: input id: v-analyzer-version attributes: label: v-analyzer Version description: | `v-analyzer --version` or in Command Palette: `v-analyzer: Show v-analyzer server version` validations: required: true - type: input id: vs-code-extension-version attributes: label: VS Code Extension Version description: | If you are using the VS Code extension, please provide the version of the extension. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: 💬 General Questions and Answers about v-analyzer url: https://github.com/vlang/v-analyzer/discussions/categories/q-a about: You can ask and answer questions about v-analyzer in the discussions forum. - name: 💬 V Discord Server url: https://discord.gg/vlang about: You can join our Discord server for real time discussion and support ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.yml ================================================ --- name: 🚀 Feature Request description: Suggest an idea for v-analyzer project title: "(short issue description)" labels: [ feature-request ] body: - type: textarea id: description attributes: label: Describe the feature description: A clear and concise description of the feature you are proposing. validations: required: true - type: textarea id: use-case attributes: label: Use Case description: | Why do you need this feature? For example: "I'm always frustrated when..." validations: required: true - type: textarea id: other attributes: label: Other Information description: | Any alternative solutions or features you considered, a more detailed explanation, stack traces, related issues, links for context, etc. validations: required: false ================================================ FILE: .github/workflows/analyzer_tests.yml ================================================ name: Analyzer CI on: push: paths-ignore: - '.github/**' - '!**/analyzer_tests.yml' - '!**/version_test.vv' - 'editors/code/**' - '**/*.md' pull_request: paths-ignore: - '.github/**' - '!**/analyzer_tests.yml' - '!**/version_test.vv' - 'editors/code/**' - '**/*.md' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true jobs: test: strategy: matrix: include: - os: macos-latest cc: clang - os: ubuntu-latest cc: gcc - os: windows-latest cc: gcc fail-fast: false runs-on: ${{ matrix.os }} env: VFLAGS: -cg -cc ${{ matrix.cc }} steps: - name: Install V id: install-v uses: vlang/setup-v@v1.4 with: check-latest: true - name: Checkout v-analyzer uses: actions/checkout@v4 with: submodules: true - name: Run tests run: v test . - name: Install v-analyzer if: runner.os != 'Windows' run: | # Build and install v-analyzer at the head ref of the submitted changes. v build.vsh sudo mv ./bin/v-analyzer /usr/local/bin/v-analyzer v-analyzer --version - name: Verify version # TODO: include Windows if: runner.os != 'Windows' run: v .github/workflows/version_test.vv ================================================ FILE: .github/workflows/build_ci.yml ================================================ name: Build CI on: push: paths-ignore: - '.github/**' - '!**/build_ci.yml' - '**/test/**' - '**/tests/**' - '**/*.md' - '**/test_*.v' - '**/*_test.v' pull_request: paths-ignore: - '.github/**' - '!**/build_ci.yml' - '**/test/**' - '**/tests/**' - '**/*.md' - '**/test_*.v' - '**/*_test.v' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true jobs: build: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] mode: [debug, dev, release] fail-fast: false runs-on: ${{ matrix.os }} steps: - name: Install V id: install-v uses: vlang/setup-v@v1.4 with: check-latest: true - name: Checkout v-analyzer uses: actions/checkout@v4 with: submodules: true - name: Build run: v build.vsh ${{ matrix.mode }} - name: Check if the build is successful run: ./bin/v-analyzer --version ================================================ FILE: .github/workflows/install_ci.yml ================================================ name: Install CI on: push: pull_request: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true jobs: install: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] fail-fast: false runs-on: ${{ matrix.os }} steps: - name: Install V uses: vlang/setup-v@v1.4 with: check-latest: true - name: Install via webscript run: v download -RD https://raw.githubusercontent.com/vlang/v-analyzer/main/install.vsh - name: Verify installation success run: ~/.config/v-analyzer/bin/v-analyzer --version - name: Checkout v-analyzer uses: actions/checkout@v4 - name: Cleanup and prepare installation shell: bash run: | rm -r ~/.config/v-analyzer/bin/v-analyzer mkdir Downloads mv ./install.vsh Downloads/install.vsh # While the webscript installation step tests the general functionality of direct installation via the web, # regressions related to the install script would only become visible AFTER pushing changes to the main branch. # We reduce the potential for errors by simulating downloading the script and running it separately. - name: Install via local script run: cd Downloads && v install.vsh - name: Verify installation success run: ~/.config/v-analyzer/bin/v-analyzer --version ================================================ FILE: .github/workflows/lint.yml ================================================ name: Lint on: push: paths: - '**.v' - '**.vsh' - '**/lint.yml' pull_request: paths: - '**.v' - '**.vsh' - '**/lint.yml' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true jobs: fmt: runs-on: ubuntu-latest steps: - name: Install V id: install-v uses: vlang/setup-v@v1.4 with: check-latest: true - name: Checkout v-analyzer uses: actions/checkout@v4 - name: Verify formatting run: | set +e v fmt -c . exit_code=$? # Don't fail on internal errors. if [[ $exit_code -ne 0 && $exit_code -ne 5 ]]; then v fmt -diff . exit 1 fi ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: workflow_dispatch: push: branches: - main tags: - '*' paths-ignore: - '**/test/**' - '**/tests/**' - '**/test_*.v' - '**/*_test.v' - '**/*.md' - '.github/**' - '!**/release.yml' permissions: contents: write env: PROJECT_NAME: v-analyzer jobs: build-v-analyzer: strategy: matrix: target: [linux-x86_64, darwin-x86_64, darwin-arm64, windows-x86_64] build_type: [dev, debug, release] include: - target: windows-x86_64 os: windows-latest bin_ext: .exe - target: linux-x86_64 os: ubuntu-latest - target: darwin-x86_64 os: macos-13 - target: darwin-arm64 os: macos-latest - build_type: release cflags: -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -frename-registers -ftree-vectorize fail-fast: false runs-on: ${{ matrix.os }} steps: - name: Install V uses: vlang/setup-v@v1.4 with: check-latest: true - name: Checkout v-analyzer uses: actions/checkout@v4 with: submodules: true - name: Compile ${{ matrix.build_type }} build id: compile env: CFLAGS: ${{ matrix.cflags }} -pipe VFLAGS: ${{ matrix.vflags }} shell: bash run: | v build.vsh ${{ matrix.build_type }} if [[ "${{ matrix.os }}" != "macos-latest" ]]; then strip --strip-unneeded ./bin/v-analyzer${{ matrix.bin_ext }} strip --discard-all ./bin/v-analyzer${{ matrix.bin_ext }} fi if [[ "${{ matrix.build_type }}" != "release" ]]; then echo "SUFFIX=-${{ matrix.build_type }}" >> "$GITHUB_OUTPUT" fi - name: Create artifact env: ARTIFACT_NAME: ${{ env.PROJECT_NAME }}-${{ matrix.target }}${{ steps.compile.outputs.SUFFIX }} shell: bash run: 7z a -tzip ${{ env.ARTIFACT_NAME }}.zip ./bin/v-analyzer${{ matrix.bin_ext }} - name: Upload artifact uses: actions/upload-artifact@v4 env: ARTIFACT_NAME: ${{ env.PROJECT_NAME }}-${{ matrix.target }}${{ steps.compile.outputs.SUFFIX }} with: name: ${{ env.ARTIFACT_NAME }} path: ${{ env.ARTIFACT_NAME }}.zip build-vscode: runs-on: ubuntu-latest steps: - name: Install Nodejs uses: actions/setup-node@v4 with: node-version: 20 - name: Checkout v-analyzer uses: actions/checkout@v4 - name: Compile id: compile shell: bash run: | pushd editors/code version=$(sed -E -n 's/^\s+"version": "([^"]+)".*/\1/gp' package.json) echo "VERSION=$version" >> "$GITHUB_OUTPUT" retry=0 echo "[+] Install dependencies" npm install echo "[+] Package start" set +e while [[ ${retry} < 3 ]]; do if npm run package; then echo "[+] Package done" break else sleep 1 let retry++ echo "[+] Package fail, ${retry} retry" fi done set -e popd - name: Upload artifact uses: actions/upload-artifact@v4 env: ARTIFACT_NAME: vscode-${{ env.PROJECT_NAME }}-${{ steps.compile.outputs.VERSION }} with: name: ${{ env.ARTIFACT_NAME }} path: editors/code/${{ env.ARTIFACT_NAME }}.vsix release: runs-on: ubuntu-latest needs: [build-v-analyzer, build-vscode] steps: - name: Download artifacts uses: actions/download-artifact@v4 with: path: ${{ env.PROJECT_NAME }} merge-multiple: true - name: Update nightly tag if: github.ref_type != 'tag' uses: richardsimko/update-tag@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: nightly - name: Generate development release body if: github.ref_type != 'tag' id: generate_body shell: bash run: | now=$(date -u +'%Y-%m-%d %H:%M:%S UTC') echo "BODY=Generated on $now from commit ${{ github.sha }}." >> "$GITHUB_OUTPUT" - name: Release development version if: github.ref_type != 'tag' uses: ncipollo/release-action@v1 with: artifacts: ${{ env.PROJECT_NAME }}/* tag: nightly body: ${{ steps.generate_body.outputs.BODY }} name: v-analyzer development build allowUpdates: true prerelease: true - name: Release latest version if: github.ref_type == 'tag' uses: ncipollo/release-action@v1 with: artifacts: ${{ env.PROJECT_NAME }}/* allowUpdates: true omitBodyDuringUpdate: true omitNameDuringUpdate: true ================================================ FILE: .github/workflows/tree_sitter_v.yml ================================================ name: Tree-sitter CI on: push: paths: - 'tree_sitter_v/**' - '**/test_tree_sitter_v.yml' pull_request: paths: - 'tree_sitter_v/**' - '**/test_tree_sitter_v.yml' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true jobs: test-grammar: runs-on: ubuntu-latest defaults: run: working-directory: tree_sitter_v steps: - name: Checkout v-analyzer uses: actions/checkout@v4 - name: Install dependencies run: npm update - name: Run tests run: npm run test test-bindings: runs-on: ubuntu-latest steps: - name: Install V id: install-v uses: vlang/setup-v@v1.4 with: check-latest: true - name: Checkout v-analyzer uses: actions/checkout@v4 with: submodules: true - name: Run tests run: v test tree_sitter_v/bindings - name: Run examples run: | cd tree_sitter_v exit_code=0 for example in $(find -wholename '*/examples/*.v'); do v run $example if [ $? -ne 0 ]; then exit_code=1 echo $exit_code echo "Failed to run example \`$example\`" fi done exit $exit_code ================================================ FILE: .github/workflows/version_test.vv ================================================ import os import v.vmod fn test_version() { if os.getenv('CI') != 'true' { eprintln('WARNING: expecting usage in combination with CI workflow.') } for k, v in os.environ() { println('>>> env key: ${k} | value: ${v}') } sha := os.getenv('GITHUB_WORKFLOW_SHA') assert sha.len > 10 git_ref := sha.trim_space()[..7] manifest := vmod.decode(@VMOD_FILE)! assert manifest.name == 'v-analyzer' // Move out of the project directory to ensure that we exclude the possiblity of // deriving the commit reference from v-analyzer's directory at program startup. os.chdir('/tmp/')! analyzer_version := os.execute_opt('v-analyzer --version')!.output.all_after_last(' ').trim_space() assert '${manifest.version}.${git_ref}' == analyzer_version } ================================================ FILE: .github/workflows/vscode_extension_tests.yml ================================================ name: VS Code Extension CI on: push: paths: - 'editors/code/**' - '**/vscode_extension_tests.yml' pull_request: paths: - 'editors/code/**' - '**/vscode_extension_tests.yml' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true jobs: test: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] fail-fast: false name: Test VS Code Extension on ${{ matrix.os }} runs-on: ${{ matrix.os }} steps: - name: Install Nodejs uses: actions/setup-node@v4 with: node-version: 20 - name: Checkout v-analyzer uses: actions/checkout@v4 - name: Install dependencies run: cd editors/code && npm update - name: Run tests run: cd editors/code && npm run test ================================================ FILE: .gitignore ================================================ # Binaries for programs and plugins main v-analyzer **/*.exe **/*.exp **/*.exe~ **/*.so **/*.dylib **/*.dll **/*.log **/*.lib **/*.obj # Ignore binary output folders bin build # Ignore cache folders cache # Ignore common editor/system specific metadata .DS_Store .idea .vscode *.iml # ENV .env # vweb and database *.db # generated executables on unix: install generate_types cmd/v-analyzer/v-analyzer ================================================ FILE: .gitmodules ================================================ [submodule "tree_sitter_v/bindings/core"] path = tree_sitter_v/bindings/core url = https://github.com/tree-sitter/tree-sitter.git ================================================ FILE: .v-analyzer/config.toml ================================================ # Specifies the path to the V installation directory with `v` executable. # If not set, the plugin will try to find it on its own. # Basically, you don't need to set it. #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 = "./cache" # 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: CHANGELOG.md ================================================ # v-analyser Changelog ## [0.0.6] - 2025/02/27 Sixth public release. This release contains mainly installation issue fixes. You can now install it on all platforms, using an uniform command: `v download -RD https://raw.githubusercontent.com/vlang/v-analyzer/main/install.vsh` It also has improvements to the diagnostic output of the build and install scripts. V-analyzer has also been fixed to compile with the latest and stricter V versions. The last notable change is the upgrade of our tree_sitter bindings. ## [0.0.5] - 2024/11/13 Fifth public release. ### New features and enhancements: ∙ analyzer: add module index paths and wrapper module path as code block (#102) ∙ analyzer: add `pub` access modifier to publicly used struct fields (#85) ∙ analyzer: create vtmp directory for check-updates/up commands (#125) ∙ analyzer: fix anonymous functions are self-invoking type mismatch (#48) ∙ analyzer: fix build for paths with spaces (#83) ∙ analyzer: Fix `code_description` (fixes zed-v) (#119) ∙ analyzer: fix doc_comment_extractor (#50) ∙ analyzer: fix vmodules_root check in setup_vpaths (#95) ∙ analyzer: fix work progress shown as 0% when finishing indexing (#84) ∙ analyzer: improve setup, extend client and log messages (#100) ∙ analyzer: merge creation of vtmp directory for check-updates/up commands (#126) ∙ analyzer: move const used to download install.vsh script (#127) ∙ analyzer: reduce nesting in setup_toolchain ∙ analyzer: rework path handling to simplify and reduce load (#86) ∙ analyzer: simplify, improve completion context detection ∙ analyzer: simplify, remove unnecessary abstraction ∙ analyzer: update deprecated ∙ analyzer: update deprecated unix time access (#99) ∙ analyzer: use latest install script when updating (#81) ∙ analyzer: support shebang syntax (#34) ∙ analyzer: add inline struct field comments (#52) ∙ analyzer: use build version aware caching (#57) ∙ analyzer: update parser.c to align with the current development state (#13), fixes highlighting for code in between 2 block comments ∙ build: follow a default directory structure for V projects (#25) ∙ build: update install.vsh to make repeated usage of path expand fn obsolete (#24) ∙ install: add debug and dev binaries install (#60) ∙ install: update the `git clone` options for install.vsh ∙ server: add `range_clause` highlight (#9) ∙ tree_sitter: correctly recognize shebang (#26) ∙ tree_sitter: support `for mut is` clause (#77) ∙ tree_sitter: support short lambda (#56) ∙ tree_sitter: detach shebang from comment (#32) ### Fixes to existing features: ∙ fix: building of IndexingRoot.v ∙ fix: fix behaviour of pascal_case_to_snake_case after V commit 5329a0a67 ∙ fix: fix goto definition for field names. (#135) ∙ fix: fix hanging on vfmt-ing large files on windows (#130) ∙ fix: fix install.vsh ∙ fix: fix module index by making sure to index also src/ and modules/ folders too (#138) ∙ fix: fix raw string with `\` (#64) ∙ fix: fix wrong macos target in release CI (#139) ∙ fix: move tools/project-checker.v to its corresponding directory ∙ fix: remove obsolete v.mod file in metadata submod ∙ fix: resolve compiler complaints (#27) ∙ fix: restore `.v` extensions for metadata/stubs, add test (#74) ∙ fix: src/analyzer/index/IndexingRoot.v ∙ fix: tree-sitter bindings examples, extend workflow to run examples ∙ fix: update npm `generate` script ∙ fix: version regression after eae3f91, add test (#61) ∙ tree_sitter: fix issuse bug (#42) ∙ tree_sitter: fix parser error on unescaped dollar identifier in string literals, add test (#79) ∙ tree_sitter: fix parsing of nested comments, extend tests (#76) ∙ tree_sitter: fix qualified type (#8) ∙ tree_sitter: rewrite comment grammar, detach line- and block comments (#71) ∙ tree_sitter: add sum type to tree node (#87) ### Documentation: ∙ docs: update README.md with more detailed instructions about how to clone the project locally, fix `v check-md` warnings ∙ docs: fix for Neovim LSP/Mason (#122) ∙ docs: fix typo in readme ∙ docs: make submodule info in readme better visible and its commands easier to copy ∙ docs: refine readme before a potential structural update ∙ docs: update readme badges (#38) ∙ docs: update README.md mason install instructions (from https://github.com/v-analyzer/v-analyzer/pull/102) ∙ docs: update workflow path in tree-sitter badge ### Others: ∙ chore: fix typos (#44) ∙ chore: format all the files with the new fmt (#112) ∙ chore: format all the files with the new vfmt (#117) ∙ chore: format all the files with the new vfmt (#120) ∙ chore: format all the files with the new vfmt (#121) ∙ chore: run `v fmt -w install.vsh` ∙ chore: remove obsolete `.editorconfig` file in subdir, format ∙ chore: remove useless `compiler_flag` and copy `.exe` on windows (#108) ∙ chore: run the linter CI for changes made to just .vsh files too ∙ chore: updare editors/code dependencies (#33) ∙ chore: update deprecated `index_last` to `last_index` (#72) ∙ chore: update .gitattributes (#70) ∙ chore: update tree-sitter dependencies (#31) ∙ chore: use `.vv` extension for meta- and testdata files (#53) ∙ ci: add concurrency config (#67) ∙ ci: add linting and formatting automation to tree-sitter_v (#68) ∙ ci: add retry to release/build-vscode (#54) ∙ ci: add step to verify code formatting (#66) ∙ ci: add `.vsix` artifacts to release asset uploads (#47) ∙ ci: change `actions/upload-artifact@v3` to `actions/upload-artifact@v4` (#20) ∙ ci: change `vlang/setup-v@v1.3` to `vlang/setup-v@v1.4` (#19) ∙ ci: extend coverage in workflows ∙ ci: extend release workflow; automate assets uploads on tag creation (#39) ∙ ci: fix binary path in nightly ci (#36) ∙ ci: make sure that install_ci.yml is run for every change. ∙ ci: simplify, cover CI changes (#30) ∙ ci: use dedicated lint workflow to verify formatting (#97) ∙ refactor: decouple tree_sitter grammar and bindings (#37) ∙ refactor: simplify doc_comment_extractor, reduce load (#51) ∙ refactor: simplify grammar for `in/!in` and `is/!is` ∙ refactor: simplify path handling, remove unused utils (#40) ∙ refactor: store project metadata in metadata module (#59) ∙ refactor: update project structure (#69) ∙ tests: add test for the toolchain path setup (#96) ∙ tests: fix paths in bindings test, add test to workflow ∙ tests: fix analyzer test (#92) ∙ tests: update testdata (#45) ∙ tests: update tests to run with `v test` (#46) ∙ tree_sitter: add .prettierignore (#89) ∙ tree_sitter: improve clarity and quality of grammar (#78) ∙ tree_sitter: improve selector expression grammar ∙ tree_sitter: minimal cleanup, add optional `;` support between statements in {} blocks (#88) ∙ tree_sitter: Update dependencies (#18) ∙ tree_sitter: update tree-sitter-cli version to 0.22.2 (#41) ## [0.0.4-beta.1] - 2024/01/09 Forth public release. Note: this is still a beta version, do expect bugs, and report them in our [issues tracker](https://github.com/vlang/v-analyzer/issues) . ### Syntax enhancements & bug fixes: ∙ Update comment rule (#5). ∙ Fix string interpolation. ∙ Fix comment string parse error (https://github.com/v-analyzer/v-analyzer/pull/85). ∙ Fix attribute shading (#2). ∙ Fix `parameters`. ∙ Fix the type descriptions in the primitives.v stub. ∙ Simplify `handle_jsonrpc` (https://github.com/v-analyzer/v-analyzer/pull/86). ### VSCode Extension: ∙ Show the full path to the found v-analyzer binary, when the VSCode extension runs its bootstrap, to make diagnosing problems easier. ∙ Update the vscode extension package to vscode-v-analyzer-0.0.4.vsix ### CI enhancements: ∙ Use `ubuntu-20.04` for building the executables, to be compatible with more Linux distros. ∙ Use `v build.vsh debug` for nightly releases, so the executables produce usable backtraces. ∙ Silence the nightly releases, simplify the .yml script that builds them (https://github.com/v-analyzer/v-analyzer/pull/83). ### Others: ∙ Update README.md to also include instructions for the mason.nvim Neovim package manager (https://github.com/v-analyzer/v-analyzer/pull/90). ∙ Fix notices and warnings with latest V. ∙ Exclude .git/* and `_test.v` files from indexing by the language server, see (https://github.com/v-analyzer/v-analyzer/pull/89). ∙ Use a git submodule for https://github.com/tree-sitter/tree-sitter.git, see (https://github.com/v-analyzer/v-analyzer/pull/81). ∙ Use gcc for building on windows (https://github.com/v-analyzer/v-analyzer/pull/87). ∙ Update build scripts (https://github.com/v-analyzer/v-analyzer/pull/84). ∙ Fix version comparison in install.vsh . ∙ Migrate from https://github.com/v-analyzer/v-analyzer/ to https://github.com/vlang/v-analyzer/ . ## [0.0.3-beta.1] - 2023/12/13 Third public release. ### Syntax enhancements & bug fixes: ∙ Fix support for multiline comments (https://github.com/v-analyzer/v-analyzer/pull/75) ∙ Fix interface ref type highlight (https://github.com/v-analyzer/v-analyzer/pull/76) ∙ Fix support for struct field attributes (https://github.com/v-analyzer/v-analyzer/pull/74) ∙ Fix interface embeds and interface fields (https://github.com/v-analyzer/v-analyzer/pull/78) ∙ Fix `assert cond, message` statements (https://github.com/v-analyzer/v-analyzer/pull/65) ∙ Support @[attribute], fix signature, fix interface highlights ### Language server enhancements: ∙ Enable exit commands, to prevent lingering v-analyzer processes after an editor restart (https://github.com/v-analyzer/v-analyzer/pull/77) ∙ server: fix NO_RESULT_CALLBACK_FOUND in neovim (https://github.com/v-analyzer/v-analyzer/pull/59) ∙ Build the v-analyzer executable on linux as static in release mode, to make it more robust and usable in more distros. ### Others: ∙ docs: add neovim install instructions (https://github.com/v-analyzer/v-analyzer/pull/63) ∙ CI improvements, to make releases easier, and to keep the code quality high. ∙ Update the vscode extension package to vscode-v-analyzer-0.0.3.vsix ∙ Make `v-analyzer --version` show the build commit as well. Note: this is still a beta version, expect bugs and please report them in our [issues tracker](https://github.com/vlang/v-analyzer/issues) . ## [0.0.2-beta.1] - 2023/11/21 Second public release. Small internal improvements to the documentation, ci, build scripts. Fix compilation with latest V. Update https://github.com/v-analyzer/v-tree-sitter from the latest upstream version from https://github.com/tree-sitter/tree-sitter . This is still a beta version, expect bugs and please report them in our [issues tracker](https://github.com/vlang/v-analyzer/issues) . ## [0.0.1-beta.1] - 2023/07/03 First public release. Please note that this is a beta version, so it may contain any bugs. ================================================ FILE: LICENSE ================================================ MIT License 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: README.md ================================================ # v-analyzer [![][badge__vscode_ext]](https://marketplace.visualstudio.com/items?itemName=VOSCA.vscode-v-analyzer) [![][badge__build_ci]](https://github.com/vlang/v-analyzer/actions/workflows/build_ci.yml?query=branch%3Amain) [![][badge__tests_ci]](https://github.com/vlang/v-analyzer/actions/workflows/analyzer_tests.yml?query=branch%3Amain) [![][badge__tree_sitter_ci]](https://github.com/vlang/v-analyzer/actions/workflows/tree_sitter_v.yml?query=branch%3Amain) [![][badge__vscode_ext_ci]](https://github.com/vlang/v-analyzer/actions/workflows/vscode_extension_tests.yml?query=branch%3Amain) Bring IDE features for the V programming language to VS Code, Vim, and other editors. The features provided by v-analyzer include: - code completion/IntelliSense - go to definition, type definition - find all references, document symbol, symbol renaming - types and documentation on hover - inlay hints for types and some construction like `or` block - semantic syntax highlighting - formatting - signature help ## Installation ### Linux, macOS, Windows Note: the following command will download `install.vsh` to the current directory, then run it, and then *delete it*. If there is a pre-existing file with this name, make sure it is safe, when it is overwritten/deleted, or change the current directory (the script itself can be run from anywhere). ```sh v download -RD https://raw.githubusercontent.com/vlang/v-analyzer/main/install.vsh ``` Note: if you get messages about `response does not start with HTTP/`, try going to the main V repository, then do `./v -d use_openssl cmd/tools/vdownload.v` . After that, retry the same command: ```sh v download -RD https://raw.githubusercontent.com/vlang/v-analyzer/main/install.vsh ``` ## Pre-built binaries You can download pre-built binaries from the [release page](https://github.com/vlang/v-analyzer/releases). Currently, we provide binaries for Linux (x64), macOS (x64 and ARM), and Windows (x64). ## Building from source > [!NOTE] > This repository uses Git submodules. > In practice, this means that you either have to: > > ```sh > git clone --filter=blob:none --recursive --shallow-submodules https://github.com/vlang/v-analyzer > ``` > > ... or, if you used just `git clone https://github.com/vlang/v-analyzer`, you can execute below > inside your local `v-analyzer` clone: > > ```sh > git submodule init && git submodule update > ``` > > If you do not do either, the symptom is that when you try to build v-analyzer, you will get a > C compiler message, about `lib.c not found` > [!TIP] > On Windows, use GCC for building, as TCC can run into some issues. Update V to the latest version: ```bash v up ``` You can build a debug or release version of the binary. The debug version will be slower, but faster to compile. ```bash v build.vsh debug ``` ```bash v build.vsh release ``` The compiled binary will be located in the `bin/` folder. ## Setup Add the `bin/` folder to your `$PATH` environment variable to make the `v-analyzer` command easily accessible. You can also specify the path to the binary in your VS Code settings: ```json { "v-analyzer.serverPath": "/path/to/v-analyzer/bin/v-analyzer" } ``` > **Note** > Restart VS Code after changing the settings or PATH. ### Config v-analyzer is configured using global or local config. The global config is located in `~/.config/v-analyzer/config.toml`, changing it will affect all projects. A local config can be created with the `v-analyzer init` command at the root of the project. Once created, it will be in `./.v-analyzer/config.toml`. Each setting in the config has a detailed description. Pay attention to the `custom_vroot` setting, if v-analyzer cannot find where V was installed, then you will need to specify the path to it manually in this field. ## Updating To update `v-analyzer` to the latest version, run: ```bash v-analyzer up ``` You can also update to a nightly version: ```bash v-analyzer up --nightly ``` > **Note** > In the nightly version you will get the latest changes, but they may not be stable! ## VS Code extension The VS Code extension is available via the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=VOSCA.vscode-v-analyzer). The source code for extension is contained in the [`editors/code`](https://github.com/vlang/v-analyzer/tree/main/editors/code) folder of this repository. ## NVIM LSP / Mason For Neovim users, v-analyzer is available via [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#v_analyzer) plugin. It is part of the [mason registry](https://mason-registry.dev/registry/list#v-analyzer) and could be installed with both Neovim plugins: - [mason.nvim](https://github.com/williamboman/mason.nvim) with `:MasonInstall v-analyzer` command - [mason-lspconfig.nvim](https://github.com/williamboman/mason-lspconfig.nvim) with `:LspInstall` command ## Authors - `jsonrpc`, `lsp`, `tree_sitter_v` modules written initially by [VLS authors](https://github.com/vlang/vls) and after that in 2023 it was modified by the [VOSCA](https://github.com/vlang-association). ## Thanks - [VLS](https://github.com/vlang/vls) authors for the initial Language Server implementation! - [vscode-vlang](https://github.com/vlang/vscode-vlang) authors for the first VS Code extension! - [rust-analyzer](https://github.com/rust-lang/rust-analyzer) and [gopls](https://github.com/golang/tools/tree/master/gopls) for the inspiration! - [Tree-sitter](https://github.com/tree-sitter/tree-sitter) authors for the cool parsing library! ## License This project is under the **MIT License**. The full license text can be found in the [LICENSE](https://github.com/vlang/v-analyzer/blob/main/LICENSE) file. [badge__vscode_ext]: https://img.shields.io/badge/VS_Code-extension-1da2e2?logo=visualstudiocode&logoWidth=11&logoColor=959da5&labelColor=333 [badge__build_ci]: https://img.shields.io/github/actions/workflow/status/vlang/v-analyzer/build_ci.yml?style=flat-rounded&branch=main&logo=github&&logoColor=959da5&labelColor=333&label=Build [badge__tests_ci]: https://img.shields.io/github/actions/workflow/status/vlang/v-analyzer/analyzer_tests.yml?style=flat-rounded&branch=main&logo=github&&logoColor=959da5&labelColor=333&label=Analyzer [badge__tree_sitter_ci]: https://img.shields.io/github/actions/workflow/status/vlang/v-analyzer/tree_sitter_v.yml?style=flat-rounded&branch=main&logo=github&&logoColor=959da5&labelColor=333&label=Tree-sitter [badge__vscode_ext_ci]: https://img.shields.io/github/actions/workflow/status/vlang/v-analyzer/vscode_extension_tests.yml?style=flat-rounded&branch=main&logo=github&&logoColor=959da5&labelColor=333&label=VS%20Code%20Extension ================================================ FILE: build.vsh ================================================ #!/usr/bin/env -S v // This script is used to build the v-analyzer binary. // Usage: `v build.vsh [debug|dev|release]` // By default, doing just `v build.vsh` will use debug mode. import os import cli import term import time import src.metadata const vexe = @VEXE const bin_path = './bin/v-analyzer' + $if windows { '.exe' } $else { '' } const build_time = time.now() const build_commit = get_build_commit() const build_datetime = build_time.format_ss() const gcheck = term.bold(term.green('✓')) const ynote = term.bold(term.gray('ⓘ ')) const is_nixos = os.exists('/etc/NIXOS') enum ReleaseMode { release debug dev } fn get_build_commit() string { // In pull requests, GA creates a merge commit, to test the latest changes, // as if they would have been merged in the main branch. However for building // and version checking, we need the commit hash of the actual last change in the PR. committish := os.getenv_opt('GITHUB_WORKFLOW_SHA') or { 'HEAD' } return os.execute('git rev-parse --short ${committish}').output.trim_space() } fn eline(msg string) { eprintln('${term.bold(term.red('[ERROR]'))} ${msg}') } fn detect_build_os() { $if windows { println('${ynote} Detected Windows .') } $if macos { println('${ynote} Detected macOS .') } $if linux { println('${ynote} Detected Linux .') } $if freebsd { println('${ynote} Detected FreeBSD .') } if is_nixos { println('${ynote} NIXOS detected ... The build *should NOT* be static .') } } fn (m ReleaseMode) compile_cmd() string { base_build_cmd := '${os.quoted_path(@VEXE)} ${os.quoted_path(@VMODROOT)} -o ${quoted_path(bin_path)} -no-parallel' cc := if v := os.getenv_opt('CC') { '-cc ${v}' } else { $if windows { // TCC cannot build tree-sitter on Windows. '-cc gcc' } $else $if linux { // GCC is needed for libbacktrace (unwind.h) and tree-sitter support. '-cc gcc' } $else { // Let `-prod` toggle the appropriate production compiler. '' } } cflags := $if cross_compile_macos_arm64 ? { '-cflags "-target arm64-apple-darwin"' } $else $if cross_compile_macos_x86_64 ? { '-cflags "-target x86_64-apple-darwin"' } $else $if linux { if !is_nixos && m == .release { '-cflags -static' } else { '' } } $else { '' } libbacktrace := $if windows { '' } $else { '-d use_libbacktrace' } build_cmd := '${base_build_cmd} ${cc} ${cflags}'.trim_space() mut resulting_cmd := match m { .release { '${build_cmd} -prod' } .debug { '${build_cmd} -g ${libbacktrace}' } .dev { '${build_cmd} -d show_ast_on_hover -g ${libbacktrace}' } } $if !windows { // Treesitter's generated C code uses gotos; // Older V versions of the json codegen generated `if(cond) \nstatement; statement2;` with wrong indentation, instead of blocks; // => Adding the flags below allows v-analyzer to be compiled with -cstrict, and wider range of supported C compilers resulting_cmd += ' -cflags "-Wno-misleading-indentation -Wno-jump-misses-init -Wno-error=jump-misses-init -Wno-typedef-redefinition"' } return resulting_cmd } fn prepare_output_dir() string { output_dir := './bin' if os.exists(output_dir) { return output_dir } os.mkdir(output_dir) or { eline('Failed to create output directory: ${err}') } return output_dir } fn build(mode ReleaseMode, explicit_debug bool) { odir := prepare_output_dir() println('${gcheck} Prepared output directory `${odir}` .') detect_build_os() vexe_version := os.execute('${os.quoted_path(vexe)} version').output.trim_space() println('${ynote} Building with ${vexe_version} .') println('${ynote} Building v-analyzer at commit: ${build_commit} .') println('${ynote} Building start time: ${build_datetime} .') cmd := mode.compile_cmd() println('${ynote} Compiling v-analyzer in ${term.bold(mode.str())} mode, using:') println(cmd) if mode == .release { println('This may take 1-2 minutes... Please wait.') } if !explicit_debug && mode == .debug { println('') println('Note: to build in ${term.bold('release')} mode, run `${term.bold('v build.vsh release')}` .') println(' Release mode is recommended for production use.') println(' At runtime, it is about 30-40% faster than debug mode.') println('') } os.execute_opt(cmd) or { eline('Failed to build v-analyzer') eprintln(err) exit(1) } final_path := abs_path(bin_path) nbytes := os.file_size(final_path) println('${ynote} The binary size in bytes is: ${nbytes:8} .') println('${ynote} The binary is located here: ${term.bold(final_path)} .') elapsed_ms := f64((time.now() - build_time).milliseconds()) println('${gcheck} Successfully built v-analyzer, in ${elapsed_ms / 1000.0:5.3f}s .') } // main program: os.setenv('BUILD_DATETIME', build_datetime, true) os.setenv('BUILD_COMMIT', build_commit, true) mut cmd := cli.Command{ name: 'v-analyzer-builder' version: metadata.manifest.version description: 'Builds the v-analyzer binary.' posix_mode: true execute: fn (_ cli.Command) ! { build(.debug, false) } } // debug builds the v-analyzer binary in debug mode. // This is the default mode. // Thanks to -d use_libbacktrace, the binary will print beautiful stack traces, // which is very useful for debugging. cmd.add_command(cli.Command{ name: 'debug' description: 'Builds the v-analyzer binary in debug mode.' execute: fn (_ cli.Command) ! { build(.debug, true) } }) // dev builds the v-analyzer binary in development mode. // In this mode, additional development features are enabled. cmd.add_command(cli.Command{ name: 'dev' description: 'Builds the v-analyzer binary in development mode.' execute: fn (_ cli.Command) ! { build(.dev, false) } }) // release builds the v-analyzer binary in release mode. // This is the recommended mode for production use. // It is about 30-40% faster than debug mode. cmd.add_command(cli.Command{ name: 'release' description: 'Builds the v-analyzer binary in release mode.' execute: fn (_ cli.Command) ! { build(.release, false) } }) cmd.parse(os.args) ================================================ FILE: editors/code/.editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = tab max_line_length = 90 insert_final_newline = true trim_trailing_whitespace = true [*.{yml,yaml}] indent_style = space indent_size = 2 ================================================ FILE: editors/code/.eslintignore ================================================ node_modules/ dist/ scripts/ ================================================ FILE: editors/code/.eslintrc.json ================================================ { "root": true, "parser": "@typescript-eslint/parser", "parserOptions": { "tsconfigRootDir": ".", "project": ["./tsconfig.json"] }, "plugins": [ "@typescript-eslint" ], "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking" ], "rules": { "prefer-const": "error", "no-array-constructor": "error", "no-new-object": "error", "no-shadow": "error", "no-undef-init": "error", "no-var": "error", "object-shorthand": "error", "prefer-template": "error", // Stylisitc rules "array-bracket-spacing": "error", "brace-style": "error", "block-spacing": "error", "camelcase": "error", "comma-spacing": "error", "eol-last": "error", "func-call-spacing": "error", "quotes": ["error", "single"], "semi": "error" } } ================================================ FILE: editors/code/.gitignore ================================================ .vscode/ipch .idea/ *.code-workspace node_modules/ dist/ *.exe *.tmp.json *.vsix .DS_Store package-lock.json ================================================ FILE: editors/code/.vscodeignore ================================================ .vscode/** .vscode-test/** docs/ node_modules/ scripts/ src/ images/ **/tests/ **/tslint.json **/*.map **/*.tmp.json .eslintignore .gitignore tsconfig.json package-lock.json ================================================ FILE: editors/code/CHANGELOG.md ================================================ # v-analyzer VS Code Extension Changelog ## [0.0.1] - 03.07.2023 First public release. Please note that this is a beta version, so it may contain any bugs. ================================================ FILE: editors/code/LICENSE ================================================ MIT License 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: editors/code/README.md ================================================ # v-analyzer support for Visual Studio Code [![VSCode Extension](https://img.shields.io/badge/VS_Code-extension-25829e?logo=visualstudiocode&logoWidth=10)](https://marketplace.visualstudio.com/items?itemName=VOSCA.vscode-v-analyzer) [![VS Code extension tests](https://github.com/vlang/v-analyzer/actions/workflows/vscode_extension_tests.yml/badge.svg)](https://github.com/vlang/v-analyzer/actions/workflows/vscode_extension_tests.yml) Provides [V programming language](https://vlang.io) and [`v-analyzer`](https://github.com/vlang/v-analyzer) support for Visual Studio Code. It is recommended over and replaces [V extension](https://marketplace.visualstudio.com/items?itemName=vlanguage.vscode-vlang). For most of its functionality, the extension uses [`v-analyzer`](https://github.com/vlang/v-analyzer), which we will refer to as the server to avoid confusion. ## Features - syntax highlighting - code completion - go to definition, type definition - find all references, document symbol, symbol renaming - types and documentation on hover - inlay hints for types and some construction like `or` block - semantic syntax highlighting - formatting - signature help ## Getting started Welcome! 👋🏻 Let's get started setting up **v-analyzer** in VS Code! 1. First of all, make sure you have the latest version of V installed. If you are unsure, run `v up` to update. 2. Now let's install VS Code **v-analyzer** extension: 1. Open the command palette with `Ctrl+Shift+P` or `Cmd+Shift+P` 2. Select `Install Extensions` and choose `v-analyzer`. You can also install the extension manually: 1. Select `Install from VSIX...` 2. Choose pre-built VSIX file from this folder or build it yourself After installation, restart VS Code. 3. Open any project that contains files with `.v` extension. The extension should automatically activate. Upon activation, the extension will try to find `v-analyzer` server, which is the heart of the extension and provides all the smart features. 4. Since `v-analyzer` server is not installed (unless you installed it in advance and added it to PATH, in which case you can skip this step), the extension will prompt you to install it. Click `Install` and wait for the installation to complete. 5. After installing `v-analyzer` server, the extension will prompt you to restart the `v-analyzer` server. Click `Yes` and wait for the restart to complete. 6. When `v-analyzer` server is successfully restarted, it will start to analyze your project as well as the V standard library. 7. Note that if `v-analyzer` server cannot find where the V standard library is stored, an error will be shown. In this case, follow the instructions in the error and specify the path to the V source code folder in the `custom_vroot` field. > **Note** > You need to specify the folder where all the V sources are stored > (e.g. `C:\v\` or `/home/user/v/` and not the folder with the standard library > (e.g. `C:\v\vlib` or `/home/user/v/vlib`)! > After making changes, restart `v-analyzer` using the `v-analyzer: Restart server` > command in the command palette. 8. If the server was able to find all the necessary things, then after a while the indexing will end, and you will be able to use all the features of `v-analyzer`. > **Note** > Indexing can take up to 30 seconds on weak machines, but this is only > done on the first run; then the indexes will be loaded from the cache. You are ready to code in V! 🎉 ## Manual Setup You can install ``v-analyzer`` server manually: Clone the [`v-analyzer`](https://github.com/vlang/v-analyzer) repository, build it and specify the path to the compiled binary. ```json { "v-analyzer.serverPath": "path/to/v-analyzer" } ``` ## Auto save `v-analyzer` uses `v` compiler to analyze code. It calls it every time a file is saved, so you can set up auto-save to get real-time feedback. ```json { "files.autoSave": "afterDelay", "files.autoSaveDelay": 300 } ``` ## Semantic tokens With highlighting based on TextMate grammar, v-analyzer provides semantic highlighting, which allows you to highlight fields, variables, parameters and other elements as different entities. To enable semantic highlighting, make sure the `editor.semanticHighlighting.enabled` setting is set to `true` in the VS Code settings. In the settings, you can also specify colors for each entity type: ```json { "editor.semanticTokenColorCustomizations": { "[Theme Name]": { "rules": { "namespace": "#AFBF7E", "parameter": "#B189F5", "decorator": "#DEBC7E", "typeParameter": "#B189F5", "enumMember": "#72CFD6", "*.global": "#A9B7C6", "function": "#FFC66D", "*.mutable": { "underline": true } } } } } ``` See [all available entity types](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#semanticTokenTypes) in the LSP specification. ## Building from source ```bash npm install npm run package ``` ## License This project is under the **MIT License**. See the [LICENSE](https://github.com/vlang/v-analyzer/blob/main/editors/code/LICENSE) file for the full license text. ================================================ FILE: editors/code/docs/troubleshooting.md ================================================ # Troubleshooting If you suspect that the V extension is not working correctly, please follow the troubleshooting steps below. TODO ================================================ FILE: editors/code/languages/v-language-configuration.json ================================================ { "comments": { "lineComment": "//", "blockComment": ["/*", "*/"] }, "folding": { "markers": { "start": "^\\s*//\\s*#?region\\b", "end": "^\\s*//\\s*#?endregion\\b" } }, "brackets": [ ["{", "}"], ["[", "]"], ["(", ")"] ], "autoClosingPairs": [ ["{", "}"], ["[", "]"], ["(", ")"], ["\"", "\""], ["'", "'"] ], "surroundingPairs": [ ["{", "}"], ["[", "]"], ["(", ")"], ["\"", "\""], ["'", "'"] ] } ================================================ FILE: editors/code/languages/vmod-language-configuration.json ================================================ { "comments": { "lineComment": "//", "blockComment": ["/*", "*/"] }, "folding": { "markers": { "start": "^\\s*//\\s*#?region\\b", "end": "^\\s*//\\s*#?endregion\\b" } }, "brackets": [ ["{", "}"], ["[", "]"], ["(", ")"] ], "autoClosingPairs": [ ["{", "}"], ["[", "]"], ["(", ")"], ["\"", "\""], ["'", "'"] ], "surroundingPairs": [ ["{", "}"], ["[", "]"], ["(", ")"], ["\"", "\""], ["'", "'"] ] } ================================================ FILE: editors/code/media/welcome.css ================================================ /*--------------------------------------------------------- * Copyright 2020 The Go Authors. All rights reserved. * Licensed under the MIT License. See LICENSE in the project root for license information. *--------------------------------------------------------*/ .Content { max-width: 60rem; font-size: 1rem; line-height: 1.5rem; margin: auto; } .Header { align-items: center; display: flex; border-bottom: 0.0625rem solid #ccc; margin-bottom: 2rem; padding-bottom: 1.25rem; } .Header-logo { width: 12rem; margin-right: 3rem; } .Header-title { font-size: 1.75rem; } .Announcement { display: flex; flex-direction: row; align-items: center; font-style: italic; padding: 1rem; } .Announcement-image { height: 2rem; flex: 0; margin-right: 1.5rem; } .Cards { display: flex; flex-direction: column; flex-wrap: wrap; } .Card { max-width: 40rem; flex: 1; } .Card-inner { display: flex; flex: 1; flex-direction: column; padding: 0rem; } .Card-title { font-size: 1.5rem; font-weight: 500; } .Card-content { margin: 0; } a:link, a:visited { text-decoration: none; } .Command:hover, a:hover { text-decoration: underline; } @media (min-width: 40rem) { .Header-links { list-style: none; padding: 0; } .Header-links li { float: left; } .Header-links li:not(:first-child):before { content: "|"; color: #ccc; padding: 0 1rem; } .Cards { flex-direction: row; justify-content: space-between; } .Card:not(:last-child) { margin-right: 4rem; } } ================================================ FILE: editors/code/package.json ================================================ { "name": "vscode-v-analyzer", "displayName": "v-analyzer", "description": "V language support (syntax highlighting, formatter, language server) for Visual Studio Code.", "publisher": "VOSCA", "icon": "icons/icon.png", "version": "0.0.6", "engines": { "vscode": "^1.66.0" }, "homepage": "https://github.com/vlang/v-analyzer", "license": "MIT", "bugs": { "url": "https://github.com/vlang/v-analyzer/issues" }, "repository": { "type": "git", "url": "https://github.com/vlang/v-analyzer" }, "keywords": [ "V", "v", "v language", "vlang" ], "scripts": { "compile": "node ./scripts/build.js", "compile-dev": "node ./scripts/build.js --dev", "compile-watch": "node ./scripts/build.js --watch", "test": "npm run testgrammar", "lint": "eslint .", "lintmd": "markdownlint *.md -i CHANGELOG.md", "vscode:prepublish": "node ./scripts/minify_json.js && npm run compile", "watch": "tsc -watch -p ./", "package": "npx vsce package", "postpackage": "node ./scripts/minify_json.js --restore", "testgrammar": "vscode-tmgrammar-test -g syntaxes/v.tmLanguage.json \"syntaxes/tests/*.vv\"", "format": "prettier --write \"scripts/**/*.js\" \"src/**/*.ts\"" }, "main": "dist/extension.js", "dependencies": { "axios": "^1.6.8", "semver": "^7.6.0", "vscode-languageclient": "^9.0.1" }, "devDependencies": { "@types/node": "^20.11.28", "@types/semver": "^7.5.8", "@types/vscode": "~1.66.0", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vscode/vsce": "^2.24.0", "esbuild": "^0.20.2", "eslint": "^8.57.0", "markdownlint-cli": "^0.39.0", "prettier": "^3.2.5", "typescript": "^5.4.2", "vscode-tmgrammar-test": "^0.1.3" }, "activationEvents": [ "workspaceContains:**/*.v", "workspaceContains:**/*.vv", "workspaceContains:**/*.vsh", "workspaceContains:**/v.mod", "onCommand:v.runWorkspace", "onCommand:v.runFile", "onCommand:v.runTests", "onCommand:v.version", "onCommand:v.serverVersion" ], "categories": [ "Programming Languages" ], "extensionPack": [ "ms-vscode.cpptools" ], "contributes": { "languages": [ { "id": "v", "aliases": [ "V" ], "extensions": [ ".v", ".vsh", ".vv" ], "configuration": "languages/v-language-configuration.json" }, { "id": "v.mod", "language": "v.mod", "scopeName": "source.v.mod", "extensions": [ ".mod" ], "configuration": "languages/vmod-language-configuration.json" }, { "id": "stree", "scopeName": "source.stree", "extensions": [ ".stree" ] } ], "grammars": [ { "language": "v", "scopeName": "source.v", "path": "syntaxes/v.tmLanguage.json" }, { "language": "v.mod", "scopeName": "source.v.mod", "path": "syntaxes/v.mod.tmLanguage.json" }, { "language": "stree", "scopeName": "source.stree", "path": "syntaxes/stree.tmGrammar.json" } ], "configuration": { "title": "v-analyzer", "properties": { "v.executablePath": { "scope": "resource", "type": "string", "description": "Custom path to the V compiler executable (`v`).\nNOTE: Setting this won't change the VROOT path in v-analyzer." }, "v-analyzer.customVrootPath": { "scope": "resource", "type": "string", "default": "", "description": "Custom path to the V installation directory (VROOT).\nNOTE: Setting this won't change the V compiler executable to be used." }, "v-analyzer.serverPath": { "scope": "resource", "type": "string", "default": "", "description": "Custom path to the v-analyzer executable (if empty uses `v-analyzer` executable from PATH)." }, "v-analyzer.connectionMode": { "scope": "resource", "type": "string", "default": "stdio", "enum": [ "stdio", "tcp" ], "description": "Specify the mode to be used when connecting to v-analyzer.", "enumDescriptions": [ "Connects to the language server via standard input/output. (Default)", "Connects to the language server via TCP (you need to run analyzer manually with `--socket` flag, debug only!)." ] }, "v-analyzer.tcpMode.port": { "scope": "resource", "type": "number", "default": 5007, "description": "Port to be used when connecting to the language server. (Only in TCP mode)" }, "v-analyzer.customArgs": { "scope": "resource", "type": "string", "description": "Custom arguments to be passed to the v-analyzer executable." } } }, "configurationDefaults": { "[v]": { "editor.insertSpaces": false } }, "keybindings": [ { "command": "v.fmt", "key": "ctrl+i ctrl+i" } ], "commands": [ { "command": "v.run", "title": "Run current directory", "category": "V" }, { "command": "v.fmt", "title": "Format current file", "category": "V" }, { "command": "v.prod", "title": "Build an optimized executable from current file", "category": "V" }, { "command": "v-analyzer.version", "title": "Show language version", "category": "V" }, { "command": "v-analyzer.serverVersion", "title": "Show v-analyzer server version", "category": "v-analyzer" }, { "command": "v-analyzer.startServer", "title": "Start server", "category": "v-analyzer" }, { "command": "v-analyzer.stopServer", "title": "Stop server", "category": "v-analyzer" }, { "command": "v-analyzer.restartServer", "title": "Restart server", "category": "v-analyzer" }, { "command": "v-analyzer.showReferences", "title": "Show References", "category": "v-analyzer" }, { "command": "v-analyzer.viewStubTree", "title": "View Stub Tree", "category": "v-analyzer" }, { "command": "v-analyzer.uploadToPlayground", "title": "Upload to V Playground", "category": "v-analyzer" }, { "command": "v-analyzer.showWelcome", "title": "Show Welcome", "description": "Open the welcome page for the v-analyzer extension.", "category": "v-analyzer" }, { "command": "v-analyzer.openGlobalConfig", "title": "Open Global Config", "description": "Open the global config file for the v-analyzer server.", "category": "v-analyzer" } ], "menus": { "commandPalette": [ { "command": "v.run", "when": "inVlangProject" }, { "command": "v.fmt", "when": "inVlangProject" }, { "command": "v.prod", "when": "inVlangProject" }, { "command": "v-analyzer.serverVersion", "when": "inVlangProject" }, { "command": "v-analyzer.stopServer", "when": "inVlangProject" }, { "command": "v-analyzer.restartServer", "when": "inVlangProject" }, { "command": "v-analyzer.uploadToPlayground", "when": "inVlangProject" } ] }, "breakpoints": [ { "language": "v" } ], "semanticTokenModifiers": [ { "id": "mutable", "description": "Style for mutable variables/parameters/receivers" }, { "id": "global", "description": "Style for global variables" } ], "semanticTokenScopes": [ { "language": "v", "scopes": { "*.mutable": [ "markup.underline" ], "*.global": [ "markup.bold" ] } } ] } } ================================================ FILE: editors/code/scripts/build.js ================================================ #!/usr/bin/env node //@ts-check "use strict"; const esbuild = require("esbuild"); const isWatch = process.argv.includes("--watch"); const isDev = process.argv.includes("--dev"); esbuild .context({ platform: "node", entryPoints: ["./src/extension.ts"], outdir: "./dist", external: ["vscode"], format: "cjs", sourcemap: "external", bundle: true, minify: !isDev, }) .then((context) => { if (isWatch) { context.watch(); } else { context.rebuild().then(() => context.dispose()); } }) .catch(() => process.exit(1)); ================================================ FILE: editors/code/scripts/minify_json.js ================================================ #!/usr/bin/env node // @ts-check "use strict"; const { exec } = require("child_process"); const { writeFileSync, copyFileSync, renameSync, existsSync } = require("fs"); const { resolve } = require("path"); const jsonFiles = [ "../syntaxes/v.tmLanguage.json", "../syntaxes/v.mod.tmLanguage.json", "../languages/v-language-configuration.json", "../languages/vmod-language-configuration.json", ]; const shouldRestore = process.argv.includes("--restore"); jsonFiles.forEach((jsonFile) => { const absolutePath = resolve(__dirname, jsonFile); const tmpFile = resolve(__dirname, jsonFile.replace(".json", ".tmp.json")); if (shouldRestore) { renameSync(tmpFile, absolutePath); } else { if (!existsSync(tmpFile)) { copyFileSync(absolutePath, tmpFile); } exec(`npx json-minify ${absolutePath}`, (error, stdout) => { if (error) throw error; writeFileSync(absolutePath, stdout); }); } }); ================================================ FILE: editors/code/src/bootstrap.ts ================================================ import cp from "child_process"; import os from "os"; import fs from "fs"; import { log } from "./log"; import { getWorkspaceConfig } from "./utils"; import { AnalyzerNotInstalledError } from "./ctx"; /** * bootstrap returns the path to the v-analyzer binary. * It will throw an error if the binary is not available. * * @returns {Promise} The path to the v-analyzer binary. */ export async function bootstrap(): Promise { const path = getAnalyzerPath(); if (!isAnalyzerExecutableValid(path)) { const config = getWorkspaceConfig(); const explicitPath = config.get("serverPath"); if (explicitPath) { throw new Error(`Failed to execute ${path} -v. \`config.serverPath\`has been set explicitly.\ Consider removing this config or making a valid server binary available at that path.`); } throw new AnalyzerNotInstalledError( `Failed to execute ${path} -v, make sure the v-analyzer is installed and available in the PATH`, ); } log.info("Server binary path:", path); return path; } function getAnalyzerPath(): string { const config = getWorkspaceConfig(); const explicitPath = config.get("serverPath"); const path = explicitPath ? explicitPath : "v-analyzer"; if (path.startsWith("~/") || path.startsWith("~\\")) { return path.replace("~", os.homedir()); } return path; } function isAnalyzerExecutableValid(path: string): boolean { const location = path === "v-analyzer" ? "PATH" : path; log.debug("Checking availability of a binary at", location); const res = cp.spawnSync(`${path}`, ["-v"]); return res.status === 0; } ================================================ FILE: editors/code/src/client.ts ================================================ import * as lc from "vscode-languageclient/node"; import vscode, { window, workspace } from "vscode"; let crashCount = 0; export async function createClient( outputChannel: vscode.OutputChannel, serverOptions: lc.ServerOptions, ): Promise { const clientOptions: lc.LanguageClientOptions = { documentSelector: [{ scheme: "file", language: "v" }], synchronize: { fileEvents: workspace.createFileSystemWatcher("**/*.v"), }, outputChannel: outputChannel, errorHandler: { error: (error: Error, _: lc.Message, count: number) => { // taken from: https://github.com/golang/vscode-go/blob/HEAD/src/goLanguageServer.ts#L533-L539 if (count < 5) { return { message: "", // suppresses error popups action: lc.ErrorAction.Continue, }; } void window.showErrorMessage( `v-analyzer: Error communicating with the language server: ${error}: ${error}.`, ); return { action: lc.ErrorAction.Shutdown, }; }, closed: () => { crashCount++; if (crashCount < 5) { return { message: "", // suppresses error popups action: lc.CloseAction.Restart, }; } return { action: lc.CloseAction.DoNotRestart, }; }, }, markdown: { isTrusted: true, supportHtml: true, }, }; const client = new lc.LanguageClient( "v-analyzer", "V Language Server", serverOptions, clientOptions, true, ); client.registerFeature(new ExperimentalFeatures()); return client; } class ExperimentalFeatures implements lc.StaticFeature { fillInitializeParams?: (params: lc.InitializeParams) => void; preInitialize?: ( capabilities: lc.ServerCapabilities, documentSelector: lc.DocumentSelector, ) => void; clear(): void {} getState(): lc.FeatureState { return { kind: "static" }; } fillClientCapabilities(capabilities: lc.ClientCapabilities): void { capabilities.experimental = { serverStatusNotification: true, viewStubTree: true, ...capabilities.experimental, }; } initialize( _capabilities: lc.ServerCapabilities, _documentSelector: lc.DocumentSelector | undefined, ): void {} dispose(): void {} } ================================================ FILE: editors/code/src/commands.ts ================================================ import * as vscode from "vscode"; import * as path from "path"; import * as lc from "vscode-languageclient"; import * as ra from "./lsp_ext"; import * as os from "os"; import { runVCommand, runVCommandCallback } from "./exec"; import { Command, ContextInit } from "./ctx"; import { LanguageClient } from "vscode-languageclient/node"; import { spawnSync } from "child_process"; import { isVlangDocument, isVlangEditor, sleep } from "./utils"; import { log } from "./log"; import axios from "axios"; import FormData from "form-data"; /** * Run current directory. */ export function runWorkspace(_: ContextInit): Command { return async () => { const document = vscode.window.activeTextEditor.document; await document.save(); const dir = path.parse(document.fileName).dir; runVCommand(["run", dir]); }; } export function runFile(_: ContextInit): Command { return async () => { const document = vscode.window.activeTextEditor.document; await document.save(); const fileName = document.fileName; runVCommand(["run", fileName]); }; } export function runTests(_: ContextInit): Command { return async (uri: string, name?: string) => { const args = ["test", uri]; if (name) { args.push("-run-only", `"*.${name}"`); } runVCommand(args); }; } /** * Show version info. */ export function version(_: ContextInit): Command { return () => { runVCommandCallback(["-version"], (err, stdout) => { if (err) { void vscode.window.showErrorMessage( "Unable to get the version number. Is V installed correctly?", ); return; } void vscode.window.showInformationMessage(stdout); }); }; } export function serverVersion(ctx: ContextInit): Command { return async () => { if (!ctx.serverPath) { void vscode.window.showWarningMessage(`v-analyzer server is not running`); return; } const { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" }); const versionString = stdout.slice(`v-analyzer version`.length).trim(); void vscode.window.showInformationMessage(`v-analyzer version: ${versionString}`); }; } export function showReferences(ctx: ContextInit): Command { return async (uri: string, positionData: string, locationData: string) => { const locations = JSON.parse(locationData); const position = JSON.parse(positionData); await showReferencesImpl(ctx.client, uri, position, locations); }; } export async function showReferencesImpl( client: LanguageClient | undefined, uri: string, position: lc.Position, locations: lc.Location[], ) { if (!client) return; await vscode.commands.executeCommand( "editor.action.showReferences", vscode.Uri.parse(uri), client.protocol2CodeConverter.asPosition(position), locations.map(client.protocol2CodeConverter.asLocation), ); } export function viewStubTree(ctx: ContextInit): Command { const tdcp = new (class implements vscode.TextDocumentContentProvider { readonly uri = vscode.Uri.parse( "v-analyzer-file-stub-tree://viewStubTree/file.stree", ); readonly eventEmitter = new vscode.EventEmitter(); constructor() { vscode.workspace.onDidChangeTextDocument( this.onDidChangeTextDocument, this, ctx.subscriptions, ); vscode.window.onDidChangeActiveTextEditor( this.onDidChangeActiveTextEditor, this, ctx.subscriptions, ); } private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) { if (isVlangDocument(event.document)) { // We need to order this after language server updates, but there's no API for that. // Hence, good old sleep(). void sleep(10).then(() => this.eventEmitter.fire(this.uri)); } } private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) { if (editor && isVlangEditor(editor)) { this.eventEmitter.fire(this.uri); } } async provideTextDocumentContent( _uri: vscode.Uri, ct: vscode.CancellationToken, ): Promise { const rustEditor = ctx.activeVlangEditor; if (!rustEditor) return ""; const client = ctx.client; const params = client.code2ProtocolConverter.asTextDocumentIdentifier( rustEditor.document, ); return client.sendRequest(ra.viewStubTree, params, ct); } get onDidChange(): vscode.Event { return this.eventEmitter.event; } })(); ctx.pushExtCleanup( vscode.workspace.registerTextDocumentContentProvider( "v-analyzer-file-stub-tree", tdcp, ), ); return async () => { const document = await vscode.workspace.openTextDocument(tdcp.uri); tdcp.eventEmitter.fire(tdcp.uri); void (await vscode.window.showTextDocument(document, { viewColumn: vscode.ViewColumn.Two, preserveFocus: true, })); }; } export function uploadToPlayground(_: ContextInit): Command { return async () => { const editor = vscode.window.activeTextEditor; if (!editor) { vscode.window.showInformationMessage("No editor is active."); return; } log.info("Uploading to playground..."); const selection = editor.selection; const code = selection.isEmpty ? editor.document.getText() : editor.document.getText(selection); const form = new FormData(); form.append("code", code); const response = await axios.post("https://play.vlang.io/share", form); const json = await response.data; const hash = json["hash"]; const error = json["error"]; if (error) { vscode.window.showErrorMessage(`V Playground: ${error}`); return; } const url = `https://play.vlang.io/p/${hash}`; const open = await vscode.window.showInformationMessage( "Successfully uploaded to V playground. Open in browser?", "Open", "Copy URL", ); if (open === "Open") { vscode.env.openExternal(vscode.Uri.parse(url)); } else if (open === "Copy URL") { vscode.env.clipboard.writeText(url); } }; } export function openGlobalConfig(_: ContextInit): Command { return async () => { const configPath = "~/.config/v-analyzer/config.toml"; const home = os.homedir(); const configPathWithHome = configPath.replace("~", home); const uri = vscode.Uri.file(configPathWithHome); const doc = await vscode.workspace.openTextDocument(uri); await vscode.window.showTextDocument(doc); }; } ================================================ FILE: editors/code/src/ctx.ts ================================================ import * as lc from "vscode-languageclient/node"; import * as ra from "./lsp_ext"; import * as vscode from "vscode"; import * as fs from "fs"; import * as https from "https"; import * as path from "path"; import * as cp from "child_process"; import { getWorkspaceConfig, getWorkspaceFolder, isVlangDocument, isVlangEditor, VlangEditor, } from "./utils"; import { Progress } from "vscode"; import { createClient } from "./client"; import { bootstrap } from "./bootstrap"; import { connectAnalyzerViaTcp } from "./tcp"; import { log } from "./log"; import { runVCommandCallback } from "./exec"; // Most of the file taken from `rust-analyzer/editors/code/src/ctx.ts` <3 export type Workspace = | { kind: "Empty" } | { kind: "Workspace Folder" } | { kind: "Detached Files"; files: vscode.TextDocument[] }; export function fetchWorkspace(): Workspace { const folders = (vscode.workspace.workspaceFolders || []).filter( (folder) => folder.uri.scheme === "file", ); const vlangDocuments = vscode.workspace.textDocuments.filter((document) => isVlangDocument(document), ); if (folders.length !== 0) { return { kind: "Workspace Folder" }; } if (vlangDocuments.length === 0) { return { kind: "Empty" }; } return { kind: "Detached Files", files: vlangDocuments }; } export type Command = (...args: any[]) => unknown; export type CommandFactory = { enabled: (ctx: ContextInit) => Command; disabled?: (ctx: Context) => Command; }; export type ContextInit = Context & { readonly client: lc.LanguageClient; }; export class AnalyzerNotInstalledError implements Error { constructor(additionalMessage?: string) { this.message = "v-analyzer is not installed"; if (additionalMessage) { this.message += `: ${additionalMessage}`; } this.name = "AnalyzerNotInstalled"; } message: string; name: string; } export class Context { readonly statusBar: vscode.StatusBarItem; readonly langStatusBar: vscode.StatusBarItem; private _client: lc.LanguageClient | undefined; private _serverPath: string | undefined; private outputChannel: vscode.OutputChannel | undefined; private clientSubscriptions: Disposable[]; private commandDisposables: Disposable[]; get client() { return this._client; } get subscriptions(): Disposable[] { return this.extCtx.subscriptions; } get serverPath(): string | undefined { return this._serverPath; } get activeVlangEditor(): VlangEditor | undefined { const editor = vscode.window.activeTextEditor; return editor && isVlangEditor(editor) ? editor : undefined; } constructor( readonly extCtx: vscode.ExtensionContext, readonly commandFactories: Record, readonly workspace: Workspace, ) { extCtx.subscriptions.push(this); this.statusBar = vscode.window.createStatusBarItem( "v-analyzer-status", vscode.StatusBarAlignment.Left, 50, ); this.langStatusBar = vscode.window.createStatusBarItem( "v-version", vscode.StatusBarAlignment.Left, 60, ); this.clientSubscriptions = []; this.commandDisposables = []; this.showLanguageStatusBar(); this.updateCommands("disable"); this.setServerStatus({ health: "stopped", }); } dispose() { this.statusBar.dispose(); this.langStatusBar.dispose(); void this.disposeClient(); this.commandDisposables.forEach((disposable) => disposable.dispose()); } async start() { log.info("Starting language client"); const client = await this.getOrCreateClient(); if (!client) { return; } await client.start(); this.updateCommands(); } private async getOrCreateClient() { if (this.workspace.kind === "Empty") { return undefined; } if (!this.outputChannel) { this.outputChannel = vscode.window.createOutputChannel( "V Analyzer Language Server", ); this.pushExtCleanup(this.outputChannel); } if (!this._client) { this._serverPath = await bootstrap().catch((err) => { if (err instanceof AnalyzerNotInstalledError) { log.info("v-analyzer is not installed"); const msg = "v-analyzer is not installed. Do you want to install it?"; void vscode.window .showInformationMessage(msg, "Yes", "No") .then((selected) => { if (selected == "Yes") { this.installAnalyzerWithProgress(); } }); } throw new AnalyzerNotInstalledError(); }); const newEnv = Object.assign({}, process.env); const folder = getWorkspaceFolder(); log.debug("cwd: ", folder.uri.fsPath); const run: lc.Executable = { command: this._serverPath, options: { env: newEnv, cwd: folder.uri.fsPath }, }; const config = getWorkspaceConfig(); const connMode = config.get("connectionMode"); const tcpPort = config.get("tcpMode.port"); if (connMode === "tcp") { log.info(`Connecting to analyzer via TCP on port ${tcpPort}`); log.info("Make sure to start the analyzer with the --socket flag"); log.info("Use it only for debugging purposes!"); } const serverOptions = connMode === "tcp" ? () => connectAnalyzerViaTcp(tcpPort) : { run, debug: run, }; this._client = await createClient(this.outputChannel, serverOptions); this.pushClientCleanup( this._client.onNotification(ra.serverStatus, (params) => this.setServerStatus(params), ), ); } return this._client; } async restart() { await this.stopAndDispose(); await this.start(); } async stopAndDispose() { if (!this._client) { return; } log.info("Disposing language client"); this.updateCommands("disable"); await this.disposeClient(); } private async disposeClient() { this.clientSubscriptions?.forEach((disposable) => disposable.dispose()); this.clientSubscriptions = []; try { await this._client?.dispose(); } catch (e) { // for some reasons dispose() always throws an error // when restarting analyzer, ignore for now // log.error('client stop error', e) } this._serverPath = undefined; this._client = undefined; } private updateCommands(forceDisable?: "disable") { this.commandDisposables.forEach((disposable) => disposable.dispose()); this.commandDisposables = []; const clientRunning = (!forceDisable && this._client?.isRunning()) ?? false; const isClientRunning = (_ctx: Context): _ctx is ContextInit => { return clientRunning; }; for (const [name, factory] of Object.entries(this.commandFactories)) { const fullName = `v-analyzer.${name}`; let callback; if (isClientRunning(this)) { // we asserted that `client` is defined callback = factory.enabled(this); } else if (factory.disabled) { callback = factory.disabled(this); } else { callback = () => vscode.window.showErrorMessage( `command ${fullName} failed: v-analyzer server is not running`, ); } this.commandDisposables.push( vscode.commands.registerCommand(fullName, callback), ); } } showLanguageStatusBar() { const statusBar = this.langStatusBar; statusBar.text = "V"; statusBar.show(); runVCommandCallback(["-version"], (err, stdout) => { if (err) { return; } const version = stdout.trim().replace("V ", ""); statusBar.text = `V ${version}`; }); } setServerStatus(status: ra.ServerStatusParams | { health: "stopped" }) { if (status.health === "error" && status.message) { const msg = status.message ?? "v-analyzer server error"; const openConfig = "Open Config"; vscode.window.showErrorMessage(msg, openConfig).then((selected) => { if (selected === openConfig) { vscode.commands.executeCommand("v-analyzer.openGlobalConfig"); } }); } let icon = ""; const statusBar = this.statusBar; statusBar.show(); statusBar.tooltip = new vscode.MarkdownString("", true); statusBar.tooltip.isTrusted = true; switch (status.health) { case "ok": statusBar.tooltip.appendText(status.message ?? "Ready"); statusBar.color = undefined; statusBar.backgroundColor = undefined; statusBar.command = "v-analyzer.stopServer"; icon = "$(zap) "; break; case "warning": if (status.message) { statusBar.tooltip.appendText(status.message); } statusBar.color = new vscode.ThemeColor( "statusBarItem.warningForeground", ); statusBar.backgroundColor = new vscode.ThemeColor( "statusBarItem.warningBackground", ); statusBar.command = "v-analyzer.openLogs"; icon = "$(warning) "; break; case "error": if (status.message) { statusBar.tooltip.appendText(status.message); } statusBar.color = new vscode.ThemeColor("statusBarItem.errorForeground"); statusBar.backgroundColor = new vscode.ThemeColor( "statusBarItem.errorBackground", ); statusBar.command = "v-analyzer.openGlobalConfig"; icon = "$(error) "; break; case "stopped": statusBar.tooltip.appendText("Server is stopped"); statusBar.tooltip.appendMarkdown( "\n\n[Start server](command:v-analyzer.startServer)", ); statusBar.color = undefined; statusBar.backgroundColor = undefined; statusBar.command = "v-analyzer.startServer"; statusBar.text = `$(stop-circle) v-analyzer`; return; } if (statusBar.tooltip.value) { statusBar.tooltip.appendText("\n\n"); } statusBar.tooltip.appendMarkdown( "\n\n[Restart server](command:v-analyzer.restartServer)", ); statusBar.tooltip.appendMarkdown( "\n\n[Stop server](command:v-analyzer.stopServer)", ); if (!status.quiescent) icon = "$(sync~spin) "; statusBar.text = `${icon}v-analyzer`; } pushExtCleanup(d: Disposable) { this.extCtx.subscriptions.push(d); } private pushClientCleanup(d: Disposable) { this.clientSubscriptions.push(d); } private getScriptPath(): string { const globalFolder = this.extCtx.globalStorageUri.fsPath; if (!fs.existsSync(globalFolder)) { fs.mkdirSync(globalFolder); } return path.join(globalFolder, "install.vsh"); } private installAnalyzerWithProgress() { return vscode.window.withProgress( { title: "Installing v-analyzer...", location: vscode.ProgressLocation.Notification, }, async (progress) => { return this.installAnalyzer(progress); }, ); } private cleanOutput(value: string): string { if (!value) { return ""; } return value.replace(/\u001b\[[0-9;]*m/g, ""); } private async startInstallation( progress: Progress<{ message?: string; increment?: number; }>, fromSources: boolean, ) { const progressMessage = fromSources ? "Installing v-analyzer from sources..." : "Installing v-analyzer binary..."; progress.report({ message: progressMessage }); const scriptPath = this.getScriptPath(); const buf = cp.spawnSync(`v`, ["run", scriptPath, "--no-interaction"], { encoding: "utf-8", }); log.debug(this.cleanOutput(buf.stdout)); log.debug(this.cleanOutput(buf.stderr)); if (buf.error) { log.error(buf.error); return; } void vscode.window.showInformationMessage( "v-analyzer binary has been installed successfully", ); progress.report({ message: "v-analyzer binary has been installed successfully" }); const config = vscode.workspace.getConfiguration(); config .update( "v-analyzer.serverPath", "~/.config/v-analyzer/bin/v-analyzer", vscode.ConfigurationTarget.Global, ) .then(() => { log.info( "v-analyzer.serverPath has been updated to ~/.config/v-analyzer/bin/v-analyzer", ); }); } private async installAnalyzer( progress: Progress<{ message?: string; increment?: number; }>, ) { await this.downloadScriptIfNeeded(); progress.report({ message: "Check prebuilt binary availability..." }); const scriptPath = this.getScriptPath(); const result = cp.execSync(`v run '${scriptPath}' check-availability`, { encoding: "utf-8", }); if (result.trim().includes("v-analyzer binary is available for your platform")) { progress.report({ message: "Found prebuilt binary for your platform, starting downloading...", }); return this.startInstallation(progress, false); } return vscode.window .showInformationMessage( "v-analyzer binary is not available for your platform. Do you want to build it from source?", "Yes", "No", ) .then(async (selected) => { if (selected == "Yes") { await this.startInstallation(progress, true); } }); } private async downloadScriptIfNeeded() { return new Promise((resolve, reject) => { const destinationPath = this.getScriptPath(); if (fs.existsSync(destinationPath)) { // Do nothing if a script already exists resolve(); return; } log.info("Downloading install script..."); const file = fs.createWriteStream(destinationPath); https .get( "https://raw.githubusercontent.com/vlang/v-analyzer/main/install.vsh", (response) => { response.pipe(file); file.on("finish", () => { file.close(); log.info("Install script has been downloaded successfully"); resolve(); }); }, ) .on("error", (err) => { fs.unlink(destinationPath, () => { log.error(`Failed to download install script: ${err}`); reject(err); }); }); }); } } export interface Disposable { dispose(): void; } ================================================ FILE: editors/code/src/exec.ts ================================================ import { Terminal, window } from "vscode"; import { getVExecCommand } from "./utils"; import cp, { exec, ExecException } from "child_process"; type ExecCallback = (error: ExecException | null, stdout: string, stderr: string) => void; let runTerminal: Terminal = null!; function outputTerminal(): Terminal { if (!runTerminal) { runTerminal = window.createTerminal("V"); } return runTerminal; } function buildCommand(args: string[]): string { const vexe = getVExecCommand(); return `${vexe} ${args.join(" ")}`; } /** * Run V command in V terminal inside VS Code. */ export function runVCommand(args: string[]): void { const cmd = buildCommand(args); const term = outputTerminal(); term.show(); term.sendText(cmd); } /** * Run V command in background. */ export function runVCommandInBackground(args: string[]): void { const cmd = buildCommand(args); cp.exec(cmd); } /** * Run V command in background and call callback when done. */ export function runVCommandCallback(args: string[], callback: ExecCallback): void { const cmd = buildCommand(args); exec(cmd, callback); } ================================================ FILE: editors/code/src/extension.ts ================================================ import * as vscode from "vscode"; import * as commands from "./commands"; import { AnalyzerNotInstalledError, CommandFactory, Context, fetchWorkspace, } from "./ctx"; import { WelcomePanel } from "./welcome"; import { setContextValue } from "./utils"; import { setGlobalState } from "./stateUtils"; const V_PROJECT_CONTEXT_NAME = "inVlangProject"; /** * This method is called when the extension is activated. * @param context The extension context */ export async function activate(context: vscode.ExtensionContext): Promise { if (vscode.extensions.getExtension("vlanguage.vscode-vlang")) { vscode.window .showWarningMessage( "You have both the v-analyzer and V plugins enabled." + "These are known to conflict and cause various functions of " + "both plugins to not work correctly. " + "v-analyzer provides all the features of the V plugin and more. " + "Disable the V plugin to avoid conflicts.", "Got it", ) .then(() => {}, console.error); } const ctx = new Context(context, createCommands(), fetchWorkspace()); setGlobalState(context.globalState); WelcomePanel.activate(ctx); const api = await activateServer(ctx).catch((err) => { if (!(err instanceof AnalyzerNotInstalledError)) { void vscode.window.showErrorMessage( `Cannot activate v-analyzer extension: ${err.message}`, ); throw err; } // If v-analyzer is not installed, we still want to activate the extension. return ctx; }); // Set the context variable inVlangProject which can be referenced when configuring, // for example, shortcuts or other things in package.json. // See https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts setContextValue(V_PROJECT_CONTEXT_NAME, true); return api; } export function deactivate(): void { setContextValue(V_PROJECT_CONTEXT_NAME, undefined); } async function activateServer(ctx: Context): Promise { vscode.workspace.onDidChangeConfiguration( (e: vscode.ConfigurationChangeEvent) => { if (!e.affectsConfiguration("v-analyzer")) return; void vscode.window .showInformationMessage( "v-analyzer: Restart is required for changes to take effect. Would you like to proceed?", "Yes", "No", ) .then((selected) => { if (selected == "Yes") { void vscode.commands.executeCommand("v-analyzer.restartServer"); } }); }, null, ctx.subscriptions, ); await ctx.start(); return ctx; } function createCommands(): Record { return { restartServer: { enabled: (ctx) => async () => { await ctx.restart(); }, disabled: (ctx) => async () => { await ctx.start(); }, }, startServer: { enabled: (ctx) => async () => { await ctx.start(); }, disabled: (ctx) => async () => { await ctx.start(); }, }, stopServer: { enabled: (ctx) => async () => { await ctx.stopAndDispose(); ctx.setServerStatus({ health: "stopped", }); }, disabled: (_) => async () => {}, }, runWorkspace: { enabled: commands.runWorkspace }, runFile: { enabled: commands.runFile }, runTests: { enabled: commands.runTests }, version: { enabled: commands.version }, serverVersion: { enabled: commands.serverVersion }, showReferences: { enabled: commands.showReferences }, viewStubTree: { enabled: commands.viewStubTree }, uploadToPlayground: { enabled: commands.uploadToPlayground }, showWelcome: { enabled: WelcomePanel.showWelcome, disabled: WelcomePanel.showWelcome, }, openGlobalConfig: { enabled: commands.openGlobalConfig, disabled: commands.openGlobalConfig, }, }; } ================================================ FILE: editors/code/src/log.ts ================================================ import vscode from "vscode"; import { inspect } from "util"; export const log = new (class { private enabled = true; private readonly output = vscode.window.createOutputChannel("V Analyzer Client"); setEnabled(yes: boolean): void { log.enabled = yes; } // Hint: the type [T, ...T[]] means a non-empty array debug(...msg: [unknown, ...unknown[]]): void { if (!log.enabled) return; log.write("DEBUG", ...msg); } info(...msg: [unknown, ...unknown[]]): void { log.write("INFO", ...msg); } warn(...msg: [unknown, ...unknown[]]): void { debugger; log.write("WARN", ...msg); } error(...msg: [unknown, ...unknown[]]): void { debugger; log.write("ERROR", ...msg); log.output.show(true); } private write(label: string, ...messageParts: unknown[]): void { const message = messageParts.map(log.stringify).join(" "); const dateTime = new Date().toLocaleString(); log.output.appendLine(`${label} [${dateTime}]: ${message}`); } private stringify(val: unknown): string { if (typeof val === "string") return val; return inspect(val, { colors: false, depth: 6, // heuristic }); } })(); ================================================ FILE: editors/code/src/lsp_ext.ts ================================================ import * as lc from "vscode-languageclient"; export const serverStatus = new lc.NotificationType( "experimental/serverStatus", ); export type ServerStatusParams = { health: "ok" | "warning" | "error"; quiescent: boolean; message?: string; }; export const viewStubTree = new lc.RequestType( "v-analyzer/viewStubTree", ); ================================================ FILE: editors/code/src/stateUtils.ts ================================================ import vscode from "vscode"; let globalState: vscode.Memento; export function getFromGlobalState(key: string, defaultValue?: any): any { if (!globalState) { return defaultValue; } return globalState.get(key, defaultValue); } export function updateGlobalState(key: string, value: any) { if (!globalState) { return Promise.resolve(); } return globalState.update(key, value); } export function setGlobalState(state: vscode.Memento) { globalState = state; } ================================================ FILE: editors/code/src/tcp.ts ================================================ import { StreamInfo } from "vscode-languageclient/node"; import * as net from "net"; export function connectAnalyzerViaTcp(port: number): Promise { const socket = net.connect({ port }); const result: StreamInfo = { writer: socket, reader: socket, }; return Promise.resolve(result); } ================================================ FILE: editors/code/src/utils.ts ================================================ import * as vscode from "vscode"; /** * Get V executable command. * Will get from user setting configuration first. * If user don't specify it, then get default command */ export function getVExecCommand(): string { const config = getWorkspaceConfig(); return config.get("v.executablePath", "v"); } /** * Get v-analyzer configuration. */ export function getWorkspaceConfig(): vscode.WorkspaceConfiguration { const currentWorkspaceFolder = getWorkspaceFolder(); const uri = currentWorkspaceFolder ? currentWorkspaceFolder.uri : null; return vscode.workspace.getConfiguration("v-analyzer", uri); } /** * Get the workspace of a current document. * @param uri The URI of document */ export function getWorkspaceFolder(uri?: vscode.Uri): vscode.WorkspaceFolder { if (uri) { return vscode.workspace.getWorkspaceFolder(uri)!; } if ( vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0 ) { return vscode.workspace.workspaceFolders[0]; } if (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document) { return vscode.workspace.getWorkspaceFolder( vscode.window.activeTextEditor.document.uri, )!; } return null!; } /** * Sets ['when'](https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts) * clause contexts */ export function setContextValue(key: string, value: any): Thenable { return vscode.commands.executeCommand("setContext", key, value); } export type VlangDocument = vscode.TextDocument & { languageId: "v" }; export type VlangEditor = vscode.TextEditor & { document: VlangDocument }; export function isVlangDocument( document: vscode.TextDocument, ): document is VlangDocument { return document.languageId === "v" && document.uri.scheme === "file"; } export function isVlangEditor(editor: vscode.TextEditor): editor is VlangEditor { return isVlangDocument(editor.document); } export function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } ================================================ FILE: editors/code/src/welcome.ts ================================================ import vscode from "vscode"; import path from "path"; import semver from "semver"; import { Command, ContextInit } from "./ctx"; import { getFromGlobalState, updateGlobalState } from "./stateUtils"; // Most of this code is copied from the Go extension's welcome.ts file. <3 export class WelcomePanel { public static showWelcome(ctx: ContextInit): Command { return WelcomePanel.createOrShow(ctx); } public static activate(ctx: ContextInit) { if (vscode.window.registerWebviewPanelSerializer) { // Make sure we register a serializer in activation event vscode.window.registerWebviewPanelSerializer(WelcomePanel.viewType, { async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel) { WelcomePanel.revive(webviewPanel, ctx.extCtx.extensionUri); }, }); } showGoWelcomePage(); } public static currentPanel: WelcomePanel | undefined; public static readonly viewType = "welcomeV"; public static createOrShow(ctx: ContextInit) { return () => { const extensionUri = ctx.extCtx.extensionUri; const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; // If we already have a panel, show it. if (WelcomePanel.currentPanel) { WelcomePanel.currentPanel.panel.reveal(column); return; } // Otherwise, create a new panel. const panel = vscode.window.createWebviewPanel( WelcomePanel.viewType, "V for VS Code", column || vscode.ViewColumn.One, { // And restrict the webview to only loading content from our extension's directory. localResourceRoots: [joinPath(extensionUri)], }, ); panel.iconPath = joinPath(extensionUri, "media", "logo.png"); WelcomePanel.currentPanel = new WelcomePanel(panel, extensionUri); }; } public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { WelcomePanel.currentPanel = new WelcomePanel(panel, extensionUri); } public readonly dataroot: vscode.Uri; // exported for testing. private readonly panel: vscode.WebviewPanel; private readonly extensionUri: vscode.Uri; private disposables: vscode.Disposable[] = []; private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { this.panel = panel; this.extensionUri = extensionUri; this.dataroot = joinPath(this.extensionUri, "media"); // Set the webview's initial html content this.update(); // Listen for when the panel is disposed // This happens when the user closes the panel or when the panel is closed programmatically this.panel.onDidDispose(() => this.dispose(), null, this.disposables); } public dispose() { WelcomePanel.currentPanel = undefined; // Clean up our resources this.panel.dispose(); while (this.disposables.length) { const x = this.disposables.pop(); if (x) { x.dispose(); } } } private update() { const webview = this.panel.webview; this.panel.webview.html = this.getHtmlForWebview(webview); } private getHtmlForWebview(webview: vscode.Webview) { const vAnalyzerExtension = vscode.extensions.getExtension( "VOSCA.vscode-v-analyzer", )!; const vAnalyzerExtensionVersion = vAnalyzerExtension.packageJSON.version; const stylePath = joinPath(this.dataroot, "welcome.css"); const logoPath = joinPath(this.dataroot, "logo.png"); const stylesURI = webview.asWebviewUri(stylePath); const logoURI = webview.asWebviewUri(logoPath); return ` V for VS Code

V for VS Code v${vAnalyzerExtensionVersion}

The v-analyzer extension for Visual Studio Code, providing rich language support for V projects.

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: [] }