Skip to content

Commit

Permalink
Move the Location struct to a more common place (#1106)
Browse files Browse the repository at this point in the history
A `Location` refers to a span within a specific source file. It's
expressed as a source name and `Range`. See:
https://github.com/microsoft/vscode/blob/7923151da4b3567636c1346df9e8ba682744bbf3/src/vscode-dts/vscode.d.ts#L6581

It was defined in the language service protocol, but I'm moving it to a
more common location in `compiler/qsc` since I need the debugger to use
it in #1107.

To summarize this PR and #1038, here's how locations are represented in
the compiler vs. higher-level components that interface with VS Code
(debugger, language service):
 
| Compiler representation | Line/column representation |
|---|---|
| `u32` offset (into `SourceMap`) | `Position` (into specific source
file) |
| `Span` | `Range` |
| (`PackageId`, `Span`) | `Location` |
  • Loading branch information
minestarks authored Feb 7, 2024
1 parent 870d4af commit fa336ec
Show file tree
Hide file tree
Showing 15 changed files with 323 additions and 98 deletions.
1 change: 1 addition & 0 deletions compiler/qsc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod compile;
pub mod error;
pub mod incremental;
pub mod interpret;
pub mod location;
pub mod target;

pub use qsc_frontend::compile::{
Expand Down
256 changes: 256 additions & 0 deletions compiler/qsc/src/location.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use std::sync::Arc;

use qsc_data_structures::{
line_column::{Encoding, Range},
span::Span,
};
use qsc_frontend::compile::PackageStore;
use qsc_hir::hir::PackageId;

pub const QSHARP_LIBRARY_URI_SCHEME: &str = "qsharp-library-source";

/// Describes a location in source code in terms of a source name and [`Range`].
#[derive(Debug, PartialEq, Clone)]
pub struct Location {
pub source: Arc<str>,
pub range: Range,
}

impl Location {
/// Creates a [`Location`] from a package ID and a SourceMap-relative span.
///
/// To differentiate user sources from library sources, this function takes
/// a `user_package_id` parameter which denotes the user package.
/// All other packages in the package store are assumed to be library packages.
/// Source names from library packages are prepended with a unique URI scheme.
#[must_use]
pub fn from(
span: Span,
package_id: PackageId,
package_store: &PackageStore,
user_package_id: PackageId,
position_encoding: Encoding,
) -> Self {
let source = package_store
.get(package_id)
.expect("package id must exist in store")
.sources
.find_by_offset(span.lo)
.expect("source should exist for offset");

let source_name = if package_id == user_package_id {
source.name.clone()
} else {
// Currently the only supported external packages are our library packages,
// URI's to which need to include our custom library scheme.
format!("{}:{}", QSHARP_LIBRARY_URI_SCHEME, source.name).into()
};

Location {
source: source_name,
range: Range::from_span(position_encoding, &source.contents, &(span - source.offset)),
}
}
}

#[cfg(test)]
mod tests {
use crate::compile;
use expect_test::expect;
use qsc_data_structures::{line_column::Encoding, span::Span};
use qsc_frontend::compile::{PackageStore, RuntimeCapabilityFlags, SourceMap};
use qsc_hir::hir::PackageId;
use qsc_passes::PackageType;

use super::Location;

#[test]
fn from_std_span() {
let (store, std_package_id, user_package_id) = compile_package();

let location = Location::from(
Span { lo: 0, hi: 1 },
std_package_id,
&store,
user_package_id,
Encoding::Utf8,
);

expect![[r#"
Location {
source: "qsharp-library-source:arrays.qs",
range: Range {
start: Position {
line: 0,
column: 0,
},
end: Position {
line: 0,
column: 1,
},
},
}
"#]]
.assert_debug_eq(&location);
}

#[test]
fn from_core_span() {
let (store, _, user_package_id) = compile_package();

let location = Location::from(
Span { lo: 0, hi: 1 },
PackageId::CORE,
&store,
user_package_id,
Encoding::Utf8,
);

expect![[r#"
Location {
source: "qsharp-library-source:core/core.qs",
range: Range {
start: Position {
line: 0,
column: 0,
},
end: Position {
line: 0,
column: 1,
},
},
}
"#]]
.assert_debug_eq(&location);
}

#[test]
fn from_user_span() {
let (store, _, user_package_id) = compile_package();

let bar_start_offset = store
.get(user_package_id)
.expect("expected to find user package")
.sources
.find_by_name("bar.qs")
.expect("expected to find source")
.offset;

let location = Location::from(
Span {
lo: bar_start_offset,
hi: bar_start_offset + 1,
},
user_package_id,
&store,
user_package_id,
Encoding::Utf8,
);

expect![[r#"
Location {
source: "bar.qs",
range: Range {
start: Position {
line: 0,
column: 0,
},
end: Position {
line: 0,
column: 1,
},
},
}
"#]]
.assert_debug_eq(&location);
}

#[test]
fn from_out_of_bounds_lo() {
let (store, _, user_package_id) = compile_package();

let location = Location::from(
Span { lo: 1000, hi: 2000 },
user_package_id,
&store,
user_package_id,
Encoding::Utf8,
);

// Per [`Range`] spec, out of bounds positions map to EOF
expect![[r#"
Location {
source: "bar.qs",
range: Range {
start: Position {
line: 0,
column: 17,
},
end: Position {
line: 0,
column: 17,
},
},
}
"#]]
.assert_debug_eq(&location);
}

#[test]
fn from_out_of_bounds_hi() {
let (store, _, user_package_id) = compile_package();

let location = Location::from(
Span { lo: 0, hi: 2000 },
user_package_id,
&store,
user_package_id,
Encoding::Utf8,
);

// Per [`Range`] spec, out of bounds positions map to EOF
expect![[r#"
Location {
source: "foo.qs",
range: Range {
start: Position {
line: 0,
column: 0,
},
end: Position {
line: 0,
column: 17,
},
},
}
"#]]
.assert_debug_eq(&location);
}

fn compile_package() -> (PackageStore, PackageId, PackageId) {
let mut store = PackageStore::new(compile::core());
let mut dependencies = Vec::new();

let (package_type, capabilities) = (PackageType::Lib, RuntimeCapabilityFlags::all());

let std = compile::std(&store, capabilities);
let std_package_id = store.insert(std);

dependencies.push(std_package_id);
let sources = SourceMap::new(
[
("foo.qs".into(), "namespace Foo { }".into()),
("bar.qs".into(), "namespace Bar { }".into()),
],
None,
);
let (unit, _) =
compile::compile(&store, &dependencies, sources, package_type, capabilities);
let user_package_id = store.insert(unit);

(store, std_package_id, user_package_id)
}
}
2 changes: 1 addition & 1 deletion language_service/src/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ mod tests;

use crate::compilation::Compilation;
use crate::name_locator::{Handler, Locator, LocatorContext};
use crate::protocol::Location;
use crate::qsc_utils::into_location;
use qsc::ast::visit::Visitor;
use qsc::hir::PackageId;
use qsc::line_column::{Encoding, Position};
use qsc::location::Location;
use qsc::{ast, hir, Span};

pub(crate) fn get_definition(
Expand Down
17 changes: 7 additions & 10 deletions language_service/src/definition/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
#![allow(clippy::needless_raw_string_hashes)]

use expect_test::{expect, Expect};
use qsc::location::Location;

use super::get_definition;
use crate::{
protocol::Location,
test_utils::{
compile_notebook_with_fake_stdlib_and_markers, compile_with_fake_stdlib_and_markers,
},
Expand All @@ -26,8 +26,8 @@ fn assert_definition(source_with_markers: &str) {
None
} else {
Some(Location {
source: "<source>".to_string(),
span: target_spans[0],
source: "<source>".into(),
range: target_spans[0],
})
};
assert_eq!(&expected_definition, &actual_definition);
Expand All @@ -40,10 +40,7 @@ fn assert_definition_notebook(cells_with_markers: &[(&str, &str)]) {
let expected_definition = if target_spans.is_empty() {
None
} else {
Some(Location {
source: target_spans[0].0.clone(),
span: target_spans[0].1,
})
Some(target_spans[0].clone())
};
assert_eq!(&expected_definition, &actual_definition);
}
Expand Down Expand Up @@ -302,7 +299,7 @@ fn std_call() {
Some(
Location {
source: "qsharp-library-source:<std>",
span: Range {
range: Range {
start: Position {
line: 1,
column: 26,
Expand Down Expand Up @@ -410,7 +407,7 @@ fn std_udt() {
Some(
Location {
source: "qsharp-library-source:<std>",
span: Range {
range: Range {
start: Position {
line: 4,
column: 24,
Expand Down Expand Up @@ -442,7 +439,7 @@ fn std_udt_udt_field() {
Some(
Location {
source: "qsharp-library-source:<std>",
span: Range {
range: Range {
start: Position {
line: 4,
column: 31,
Expand Down
2 changes: 1 addition & 1 deletion language_service/src/hover/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn check_notebook(cells_with_markers: &[(&str, &str)], expect: &Expect) {

let actual =
get_hover(&compilation, &cell_uri, position, Encoding::Utf8).expect("Expected a hover.");
assert_eq!(&actual.span, &target_spans[0].1);
assert_eq!(&actual.span, &target_spans[0].range);
expect.assert_eq(&actual.contents);
}

Expand Down
7 changes: 5 additions & 2 deletions language_service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures_util::StreamExt;
use log::{trace, warn};
use protocol::{
CompletionList, DiagnosticUpdate, Hover, Location, NotebookMetadata, SignatureHelp,
CompletionList, DiagnosticUpdate, Hover, NotebookMetadata, SignatureHelp,
WorkspaceConfigurationUpdate,
};
use qsc::line_column::{Encoding, Position, Range};
use qsc::{
line_column::{Encoding, Position, Range},
location::Location,
};
use qsc_project::JSFileEntry;
use state::{CompilationState, CompilationStateUpdater};
use std::{cell::RefCell, fmt::Debug, future::Future, pin::Pin, rc::Rc, sync::Arc};
Expand Down
6 changes: 0 additions & 6 deletions language_service/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,6 @@ impl Hash for CompletionItem {
}
}

#[derive(Debug, PartialEq)]
pub struct Location {
pub source: String,
pub span: Range,
}

#[derive(Debug, PartialEq)]
pub struct Hover {
pub contents: String,
Expand Down
Loading

0 comments on commit fa336ec

Please sign in to comment.