Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add integration tests for VS Code debugging, eliminate FileAccessor #1107

Merged
merged 12 commits into from
Mar 12, 2024
27 changes: 8 additions & 19 deletions compiler/qsc/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub use qsc_eval::{
use crate::{
error::{self, WithStack},
incremental::Compiler,
location::Location,
};
use debug::format_call_stack;
use miette::Diagnostic;
Expand Down Expand Up @@ -431,25 +432,15 @@ impl Debugger {
Global::Udt => "udt".into(),
};

let hir_package = self
.interpreter
.compiler
.package_store()
.get(map_fir_package_to_hir(frame.id.package))
.expect("package should exist");
let source = hir_package
.sources
.find_by_offset(frame.span.lo)
.expect("frame should have a source");
let path = source.name.to_string();
StackFrame {
name,
functor,
path,
range: Range::from_span(
location: Location::from(
frame.span,
map_fir_package_to_hir(frame.id.package),
self.interpreter.compiler.package_store(),
map_fir_package_to_hir(self.interpreter.source_package),
self.position_encoding,
&source.contents,
&(frame.span - source.offset),
),
}
})
Expand Down Expand Up @@ -536,10 +527,8 @@ pub struct StackFrame {
pub name: String,
/// The functor of the callable.
pub functor: String,
/// The path of the source file.
pub path: String,
/// The source range of the call site.
pub range: Range,
/// The source location of the call site.
pub location: Location,
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
Expand Down
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
Loading
Loading