From 99016c427380f459c8c0098a075a91c33cd9b8dd Mon Sep 17 00:00:00 2001 From: Chris Rybicki Date: Mon, 1 Apr 2024 13:52:36 -0400 Subject: [PATCH] chore(compiler): store byte offsets in wing spans (#6094) This is a minor improvement that prevents the need to calculate line/column numbers when we're emitting errors for CLI error messages. ## Checklist - [ ] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [ ] Description explains motivation and solution - [ ] Tests added (always) - [ ] Docs updated (only required for features) - [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing *By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*. --- .../server/src/utils/format-wing-error.ts | 34 ++------------- apps/wing/src/commands/compile.ts | 29 ++----------- libs/wingc/src/diagnostic.rs | 33 +++++++++++++++ libs/wingc/src/jsify/codemaker.rs | 4 ++ libs/wingc/src/lsp/completions.rs | 4 ++ libs/wingc/src/parser.rs | 2 + libs/wingc/src/type_check.rs | 10 ++++- libs/wingcompiler/src/wingc.ts | 42 ++++++++----------- 8 files changed, 78 insertions(+), 80 deletions(-) diff --git a/apps/wing-console/console/server/src/utils/format-wing-error.ts b/apps/wing-console/console/server/src/utils/format-wing-error.ts index 09615d30e71..17d6a976b99 100644 --- a/apps/wing-console/console/server/src/utils/format-wing-error.ts +++ b/apps/wing-console/console/server/src/utils/format-wing-error.ts @@ -6,16 +6,6 @@ import { prettyPrintError } from "@winglang/sdk/lib/util/enhanced-error"; import type { File, Label } from "codespan-wasm"; import { CHARS_ASCII, emitDiagnostic } from "codespan-wasm"; -function offsetFromLineAndColumn(source: string, line: number, column: number) { - const lines = source.split("\n"); - let offset = 0; - for (let index = 0; index < line; index++) { - offset += lines[index]!.length + 1; - } - offset += column; - return offset; -} - export const formatWingError = async (error: unknown, entryPoint?: string) => { try { if (error instanceof CompileError) { @@ -33,16 +23,8 @@ export const formatWingError = async (error: unknown, entryPoint?: string) => { if (span !== null && span !== undefined && span.file_id) { // `span` should only be null if source file couldn't be read etc. const source = await readFile(span.file_id, "utf8"); - const start = offsetFromLineAndColumn( - source, - span.start.line, - span.start.col, - ); - const end = offsetFromLineAndColumn( - source, - span.end.line, - span.end.col, - ); + const start = span.start_offset; + const end = span.end_offset; const filePath = relative(cwd, span.file_id); files.push({ name: filePath, source }); labels.push({ @@ -60,16 +42,8 @@ export const formatWingError = async (error: unknown, entryPoint?: string) => { continue; } const source = await readFile(annotation.span.file_id, "utf8"); - const start = offsetFromLineAndColumn( - source, - annotation.span.start.line, - annotation.span.start.col, - ); - const end = offsetFromLineAndColumn( - source, - annotation.span.end.line, - annotation.span.end.col, - ); + const start = annotation.span.start_offset; + const end = annotation.span.end_offset; const filePath = relative(cwd, annotation.span.file_id); files.push({ name: filePath, source }); labels.push({ diff --git a/apps/wing/src/commands/compile.ts b/apps/wing/src/commands/compile.ts index 0e675c9e2f4..2697a0d0ad8 100644 --- a/apps/wing/src/commands/compile.ts +++ b/apps/wing/src/commands/compile.ts @@ -103,8 +103,8 @@ export async function compile(entrypoint?: string, options?: CompileOptions): Pr if (span?.file_id) { // `span` should only be null if source file couldn't be read etc. const source = await fsPromise.readFile(span.file_id, "utf8"); - const start = byteOffsetFromLineAndColumn(source, span.start.line, span.start.col); - const end = byteOffsetFromLineAndColumn(source, span.end.line, span.end.col); + const start = span.start_offset; + const end = span.end_offset; const filePath = relative(cwd, span.file_id); files.push({ name: filePath, source }); labels.push({ @@ -122,16 +122,8 @@ export async function compile(entrypoint?: string, options?: CompileOptions): Pr continue; } const source = await fsPromise.readFile(annotation.span.file_id, "utf8"); - const start = byteOffsetFromLineAndColumn( - source, - annotation.span.start.line, - annotation.span.start.col - ); - const end = byteOffsetFromLineAndColumn( - source, - annotation.span.end.line, - annotation.span.end.col - ); + const start = annotation.span.start_offset; + const end = annotation.span.end_offset; const filePath = relative(cwd, annotation.span.file_id); files.push({ name: filePath, source }); labels.push({ @@ -179,16 +171,3 @@ export async function compile(entrypoint?: string, options?: CompileOptions): Pr } } } - -function byteOffsetFromLineAndColumn(source: string, line: number, column: number) { - const lines = source.split("\n"); - let offset = 0; - for (let i = 0; i < line; i++) { - offset += lines[i].length + 1; - } - - // Convert char offset to byte offset - const encoder = new TextEncoder(); - const srouce_bytes = encoder.encode(source.substring(0, offset)); - return srouce_bytes.length + column; -} diff --git a/libs/wingc/src/diagnostic.rs b/libs/wingc/src/diagnostic.rs index 07c23c7bbf4..040b4fcbc49 100644 --- a/libs/wingc/src/diagnostic.rs +++ b/libs/wingc/src/diagnostic.rs @@ -92,6 +92,10 @@ pub struct WingSpan { pub end: WingLocation, /// Relative path to the file based on the entrypoint file pub file_id: String, + /// Byte-level offsets into the file. + /// Not used for comparisons (start/end are used instead) + pub start_offset: usize, + pub end_offset: usize, } impl WingSpan { @@ -213,10 +217,23 @@ impl WingSpan { other.start }; let end = if self.end > other.end { self.end } else { other.end }; + let start_offset = if self.start_offset < other.start_offset { + self.start_offset + } else { + other.start_offset + }; + let end_offset = if self.end_offset > other.end_offset { + self.end_offset + } else { + other.end_offset + }; + Self { start, end, file_id: self.file_id.clone(), + start_offset, + end_offset, } } @@ -433,6 +450,8 @@ mod tests { start: WingLocation { line: 0, col: 0 }, end: WingLocation { line: 1, col: 10 }, file_id: "test".to_string(), + start_offset: 0, + end_offset: 10, }; let in_position = Position { line: 0, character: 5 }; @@ -450,17 +469,23 @@ mod tests { start: WingLocation { line: 0, col: 0 }, end: WingLocation { line: 1, col: 10 }, file_id: file_id.to_string(), + start_offset: 0, + end_offset: 10, }; let in_span = WingSpan { start: WingLocation { line: 0, col: 5 }, end: WingLocation { line: 1, col: 5 }, file_id: file_id.to_string(), + start_offset: 0, + end_offset: 10, }; let out_span = WingSpan { start: WingLocation { line: 2, col: 0 }, end: WingLocation { line: 2, col: 5 }, file_id: file_id.to_string(), + start_offset: 0, + end_offset: 10, }; assert!(span.contains_span(&in_span)); @@ -473,6 +498,8 @@ mod tests { start: WingLocation { line: 0, col: 0 }, end: WingLocation { line: 1, col: 10 }, file_id: "test".to_string(), + start_offset: 0, + end_offset: 10, }; let in_location = WingLocation { line: 0, col: 5 }; @@ -488,6 +515,8 @@ mod tests { start: WingLocation { line: 1, col: 5 }, end: WingLocation { line: 1, col: 10 }, file_id: "test".to_string(), + start_offset: 0, + end_offset: 10, }; let like_span1 = span1.clone(); @@ -496,11 +525,15 @@ mod tests { start: WingLocation { line: 0, col: 0 }, end: WingLocation { line: 1, col: 1 }, file_id: "test".to_string(), + start_offset: 0, + end_offset: 10, }; let later = WingSpan { start: WingLocation { line: 2, col: 0 }, end: WingLocation { line: 2, col: 5 }, file_id: "test".to_string(), + start_offset: 0, + end_offset: 10, }; assert!(span1 == like_span1); diff --git a/libs/wingc/src/jsify/codemaker.rs b/libs/wingc/src/jsify/codemaker.rs index 7aa603ed52f..cdcae5916b0 100644 --- a/libs/wingc/src/jsify/codemaker.rs +++ b/libs/wingc/src/jsify/codemaker.rs @@ -380,6 +380,8 @@ mod tests { file_id: "test.w".to_string(), start: WingLocation { line: 2, col: 4 }, end: WingLocation { line: 2, col: 8 }, + start_offset: 0, + end_offset: 0, }; let mut code = CodeMaker::with_source(&base_span); code.line("1"); @@ -415,6 +417,8 @@ mod tests { file_id: "test.w".to_string(), start: WingLocation { line: 2, col: 4 }, end: WingLocation { line: 2, col: 8 }, + start_offset: 0, + end_offset: 0, }; let mut code = CodeMaker::with_source(&base_span); code.line("1"); diff --git a/libs/wingc/src/lsp/completions.rs b/libs/wingc/src/lsp/completions.rs index ec3dbf31e61..c1d4c1541a0 100644 --- a/libs/wingc/src/lsp/completions.rs +++ b/libs/wingc/src/lsp/completions.rs @@ -179,11 +179,15 @@ pub fn on_completion(params: lsp_types::CompletionParams) -> CompletionResponse start: parent.start_position().into(), end: parent.end_position().into(), file_id: file.to_string(), + start_offset: parent.start_byte(), + end_offset: parent.end_byte(), }), WingSpan { start: node_to_complete.start_position().into(), end: node_to_complete.end_position().into(), file_id: file.to_string(), + start_offset: node_to_complete.start_byte(), + end_offset: node_to_complete.end_byte(), }, params.text_document_position.position.into(), &root_scope, diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index e78dc59dd07..ecafcbf3c32 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -565,6 +565,8 @@ impl<'s> Parser<'s> { start: node_range.start_point.into(), end: node_range.end_point.into(), file_id: self.source_name.to_string(), + start_offset: node_range.start_byte, + end_offset: node_range.end_byte, } } diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 936b7112bf8..387307f78c3 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -5473,11 +5473,19 @@ impl<'a> TypeChecker<'a> { let start = root.span.start; let end = path.last().map(|s| s.span.end).unwrap_or(root.span.end); let file_id = root.span.file_id.clone(); + let start_offset = root.span.start_offset; + let end_offset = path.last().map(|s| s.span.end_offset).unwrap_or(root.span.end_offset); Some(UserDefinedType { root, fields: path, - span: WingSpan { start, end, file_id }, + span: WingSpan { + start, + end, + file_id, + start_offset, + end_offset, + }, }) } diff --git a/libs/wingcompiler/src/wingc.ts b/libs/wingcompiler/src/wingc.ts index 9f3cceecf94..749b621bb90 100644 --- a/libs/wingcompiler/src/wingc.ts +++ b/libs/wingcompiler/src/wingc.ts @@ -188,7 +188,7 @@ export async function load(options: WingCompilerLoadOptions) { wasi_snapshot_preview1: wasi.wasiImport, env: { // This function is used only by the lsp - send_diagnostic: () => { }, + send_diagnostic: () => {}, }, ...(options.imports ?? {}), } as any; @@ -222,34 +222,28 @@ const HIGH_MASK = BigInt(32); // From diagnostic.rs export interface WingDiagnostic { message: string; - span?: { - start: { - line: number; - col: number; - }; - end: { - line: number; - col: number; - }; - file_id: string; - }; + span?: WingSpan; annotations: { message: string; - span: { - start: { - line: number; - col: number; - }; - end: { - line: number; - col: number; - }; - file_id: string; - }; + span: WingSpan; }[]; hints: string[]; } +export interface WingSpan { + start: { + line: number; + col: number; + }; + end: { + line: number; + col: number; + }; + file_id: string; + start_offset: number; + end_offset: number; +} + /** * Runs the given WASM function in the Wing Compiler WASM instance. * @@ -282,7 +276,7 @@ export function invoke( argMemoryBuffer.set(bytes); const result = exports[func](argPointer, bytes.byteLength); - + if (result === 0 || result === undefined || result === 0n) { return 0; } else {