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

feat: (LSP) show global value on hover #6097

Merged
merged 5 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 83 additions & 83 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@ impl<'context> Elaborator<'context> {

let length = stmt.expression;
let span = self.interner.expr_span(&length);
let result = self.try_eval_array_length_id(length, span);
let result = try_eval_array_length_id(self.interner, length, span);

match result.map(|length| length.try_into()) {
Ok(Ok(length_value)) => return length_value,
Expand All @@ -633,88 +633,6 @@ impl<'context> Elaborator<'context> {
0
}

fn try_eval_array_length_id(
&self,
rhs: ExprId,
span: Span,
) -> Result<u128, Option<ResolverError>> {
// Arbitrary amount of recursive calls to try before giving up
let fuel = 100;
self.try_eval_array_length_id_with_fuel(rhs, span, fuel)
}

fn try_eval_array_length_id_with_fuel(
&self,
rhs: ExprId,
span: Span,
fuel: u32,
) -> Result<u128, Option<ResolverError>> {
if fuel == 0 {
// If we reach here, it is likely from evaluating cyclic globals. We expect an error to
// be issued for them after name resolution so issue no error now.
return Err(None);
}

match self.interner.expression(&rhs) {
HirExpression::Literal(HirLiteral::Integer(int, false)) => {
int.try_into_u128().ok_or(Some(ResolverError::IntegerTooLarge { span }))
}
HirExpression::Ident(ident, _) => {
if let Some(definition) = self.interner.try_definition(ident.id) {
match definition.kind {
DefinitionKind::Global(global_id) => {
let let_statement = self.interner.get_global_let_statement(global_id);
if let Some(let_statement) = let_statement {
let expression = let_statement.expression;
self.try_eval_array_length_id_with_fuel(expression, span, fuel - 1)
} else {
Err(Some(ResolverError::InvalidArrayLengthExpr { span }))
}
}
_ => Err(Some(ResolverError::InvalidArrayLengthExpr { span })),
}
} else {
Err(Some(ResolverError::InvalidArrayLengthExpr { span }))
}
}
HirExpression::Infix(infix) => {
let lhs = self.try_eval_array_length_id_with_fuel(infix.lhs, span, fuel - 1)?;
let rhs = self.try_eval_array_length_id_with_fuel(infix.rhs, span, fuel - 1)?;

match infix.operator.kind {
BinaryOpKind::Add => Ok(lhs + rhs),
BinaryOpKind::Subtract => Ok(lhs - rhs),
BinaryOpKind::Multiply => Ok(lhs * rhs),
BinaryOpKind::Divide => Ok(lhs / rhs),
BinaryOpKind::Equal => Ok((lhs == rhs) as u128),
BinaryOpKind::NotEqual => Ok((lhs != rhs) as u128),
BinaryOpKind::Less => Ok((lhs < rhs) as u128),
BinaryOpKind::LessEqual => Ok((lhs <= rhs) as u128),
BinaryOpKind::Greater => Ok((lhs > rhs) as u128),
BinaryOpKind::GreaterEqual => Ok((lhs >= rhs) as u128),
BinaryOpKind::And => Ok(lhs & rhs),
BinaryOpKind::Or => Ok(lhs | rhs),
BinaryOpKind::Xor => Ok(lhs ^ rhs),
BinaryOpKind::ShiftRight => Ok(lhs >> rhs),
BinaryOpKind::ShiftLeft => Ok(lhs << rhs),
BinaryOpKind::Modulo => Ok(lhs % rhs),
}
}
HirExpression::Cast(cast) => {
let lhs = self.try_eval_array_length_id_with_fuel(cast.lhs, span, fuel - 1)?;
let lhs_value = Value::Field(lhs.into());
let evaluated_value =
Interpreter::evaluate_cast_one_step(&cast, rhs, lhs_value, self.interner)
.map_err(|error| Some(ResolverError::ArrayLengthInterpreter { error }))?;

evaluated_value
.to_u128()
.ok_or_else(|| Some(ResolverError::InvalidArrayLengthExpr { span }))
}
_other => Err(Some(ResolverError::InvalidArrayLengthExpr { span })),
}
}

pub fn unify(
&mut self,
actual: &Type,
Expand Down Expand Up @@ -1848,6 +1766,88 @@ impl<'context> Elaborator<'context> {
}
}

pub fn try_eval_array_length_id(
interner: &NodeInterner,
rhs: ExprId,
span: Span,
) -> Result<u128, Option<ResolverError>> {
// Arbitrary amount of recursive calls to try before giving up
let fuel = 100;
try_eval_array_length_id_with_fuel(interner, rhs, span, fuel)
}

fn try_eval_array_length_id_with_fuel(
interner: &NodeInterner,
rhs: ExprId,
span: Span,
fuel: u32,
) -> Result<u128, Option<ResolverError>> {
if fuel == 0 {
// If we reach here, it is likely from evaluating cyclic globals. We expect an error to
// be issued for them after name resolution so issue no error now.
return Err(None);
}

match interner.expression(&rhs) {
HirExpression::Literal(HirLiteral::Integer(int, false)) => {
int.try_into_u128().ok_or(Some(ResolverError::IntegerTooLarge { span }))
}
HirExpression::Ident(ident, _) => {
if let Some(definition) = interner.try_definition(ident.id) {
match definition.kind {
DefinitionKind::Global(global_id) => {
let let_statement = interner.get_global_let_statement(global_id);
if let Some(let_statement) = let_statement {
let expression = let_statement.expression;
try_eval_array_length_id_with_fuel(interner, expression, span, fuel - 1)
} else {
Err(Some(ResolverError::InvalidArrayLengthExpr { span }))
}
}
_ => Err(Some(ResolverError::InvalidArrayLengthExpr { span })),
}
} else {
Err(Some(ResolverError::InvalidArrayLengthExpr { span }))
}
}
HirExpression::Infix(infix) => {
let lhs = try_eval_array_length_id_with_fuel(interner, infix.lhs, span, fuel - 1)?;
let rhs = try_eval_array_length_id_with_fuel(interner, infix.rhs, span, fuel - 1)?;

match infix.operator.kind {
BinaryOpKind::Add => Ok(lhs + rhs),
BinaryOpKind::Subtract => Ok(lhs - rhs),
BinaryOpKind::Multiply => Ok(lhs * rhs),
BinaryOpKind::Divide => Ok(lhs / rhs),
BinaryOpKind::Equal => Ok((lhs == rhs) as u128),
BinaryOpKind::NotEqual => Ok((lhs != rhs) as u128),
BinaryOpKind::Less => Ok((lhs < rhs) as u128),
BinaryOpKind::LessEqual => Ok((lhs <= rhs) as u128),
BinaryOpKind::Greater => Ok((lhs > rhs) as u128),
BinaryOpKind::GreaterEqual => Ok((lhs >= rhs) as u128),
BinaryOpKind::And => Ok(lhs & rhs),
BinaryOpKind::Or => Ok(lhs | rhs),
BinaryOpKind::Xor => Ok(lhs ^ rhs),
BinaryOpKind::ShiftRight => Ok(lhs >> rhs),
BinaryOpKind::ShiftLeft => Ok(lhs << rhs),
BinaryOpKind::Modulo => Ok(lhs % rhs),
}
}
HirExpression::Cast(cast) => {
let lhs = try_eval_array_length_id_with_fuel(interner, cast.lhs, span, fuel - 1)?;
let lhs_value = Value::Field(lhs.into());
let evaluated_value =
Interpreter::evaluate_cast_one_step(&cast, rhs, lhs_value, interner)
.map_err(|error| Some(ResolverError::ArrayLengthInterpreter { error }))?;

evaluated_value
.to_u128()
.ok_or_else(|| Some(ResolverError::InvalidArrayLengthExpr { span }))
}
_other => Err(Some(ResolverError::InvalidArrayLengthExpr { span })),
}
}

/// Gives an error if a user tries to create a mutable reference
/// to an immutable variable.
fn verify_mutable_reference(interner: &NodeInterner, rhs: ExprId) -> Result<(), ResolverError> {
Expand Down
96 changes: 92 additions & 4 deletions tooling/lsp/src/requests/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
use lsp_types::{Hover, HoverContents, HoverParams, MarkupContent, MarkupKind};
use noirc_frontend::{
ast::Visibility,
elaborator::types::try_eval_array_length_id,
hir::def_map::ModuleId,
hir_def::{stmt::HirPattern, traits::Trait},
macros_api::{NodeInterner, StructId},
hir_def::{expr::HirArrayLiteral, stmt::HirPattern, traits::Trait},
macros_api::{HirExpression, HirLiteral, NodeInterner, StructId},
node_interner::{
DefinitionId, DefinitionKind, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId,
DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId,
},
Generics, Shared, StructType, Type, TypeAlias, TypeBinding, TypeVariable,
};
Expand Down Expand Up @@ -166,24 +167,107 @@
fn format_global(id: GlobalId, args: &ProcessRequestCallbackArgs) -> String {
let global_info = args.interner.get_global(id);
let definition_id = global_info.definition_id;
let definition = args.interner.definition(definition_id);
let typ = args.interner.definition_type(definition_id);

let mut string = String::new();
if format_parent_module(ReferenceId::Global(id), args, &mut string) {
string.push('\n');
}

string.push_str(" ");
if definition.comptime {
string.push_str("comptime ");
}
if definition.mutable {
string.push_str("mut ");
}
string.push_str("global ");
string.push_str(&global_info.ident.0.contents);
string.push_str(": ");
string.push_str(&format!("{}", typ));

// See if we can figure out what's the global's value
if let Some(stmt) = args.interner.get_global_let_statement(id) {
if let Some(value) = get_global_value(args.interner, stmt.expression) {
string.push_str(" = ");
string.push_str(&value);
}
}

string.push_str(&go_to_type_links(&typ, args.interner, args.files));

append_doc_comments(args.interner, ReferenceId::Global(id), &mut string);

string
}

fn get_global_value(interner: &NodeInterner, expr: ExprId) -> Option<String> {
let span = interner.expr_span(&expr);

// Globals as array lengths are extremely common, so we try that first.
if let Ok(result) = try_eval_array_length_id(interner, expr, span) {
return Some(result.to_string());
}

match interner.expression(&expr) {
HirExpression::Literal(literal) => match literal {
HirLiteral::Array(hir_array_literal) => {
get_global_array_value(interner, hir_array_literal, false)
}
HirLiteral::Slice(hir_array_literal) => {
get_global_array_value(interner, hir_array_literal, true)
}
HirLiteral::Bool(value) => Some(value.to_string()),
HirLiteral::Integer(field_element, _) => Some(field_element.to_string()),
HirLiteral::Str(string) => Some(format!("{:?}", string)),
HirLiteral::FmtStr(..) => None,
HirLiteral::Unit => Some("()".to_string()),
},
HirExpression::Tuple(values) => {
get_exprs_global_value(interner, &values).map(|value| format!("({})", value))
}
_ => None,
}
}

fn get_global_array_value(
interner: &NodeInterner,
literal: HirArrayLiteral,
is_slice: bool,
) -> Option<String> {
match literal {
HirArrayLiteral::Standard(values) => {
get_exprs_global_value(interner, &values).map(|value| {
if is_slice {
format!("&[{}]", value)
} else {
format!("[{}]", value)
}
})
}
HirArrayLiteral::Repeated { repeated_element, length } => {
get_global_value(interner, repeated_element).map(|value| {
if is_slice {
format!("&[{}; {}]", value, length)
} else {
format!("[{}; {}]", value, length)
}
})
}
}
}

fn get_exprs_global_value(interner: &NodeInterner, exprs: &[ExprId]) -> Option<String> {
let strings: Vec<String> =
exprs.iter().filter_map(|value| get_global_value(interner, *value)).collect();
if strings.len() == exprs.len() {
Some(strings.join(", "))
} else {
None
}
}

fn format_function(id: FuncId, args: &ProcessRequestCallbackArgs) -> String {
let func_meta = args.interner.function_meta(&id);
let func_name_definition_id = args.interner.definition(func_meta.name.id);
Expand Down Expand Up @@ -263,6 +347,10 @@

fn format_local(id: DefinitionId, args: &ProcessRequestCallbackArgs) -> String {
let definition_info = args.interner.definition(id);
if let DefinitionKind::Global(global_id) = &definition_info.kind {
return format_global(*global_id, args);
}

let DefinitionKind::Local(expr_id) = definition_info.kind else {
panic!("Expected a local reference to reference a local definition")
};
Expand Down Expand Up @@ -565,7 +653,7 @@
"two/src/lib.nr",
Position { line: 6, character: 9 },
r#" one
mod subone"#,

Check warning on line 656 in tooling/lsp/src/requests/hover.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (subone)
)
.await;
}
Expand All @@ -576,7 +664,7 @@
"workspace",
"two/src/lib.nr",
Position { line: 9, character: 20 },
r#" one::subone

Check warning on line 667 in tooling/lsp/src/requests/hover.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (subone)
struct SubOneStruct {
some_field: i32,
some_other_field: Field,
Expand All @@ -591,7 +679,7 @@
"workspace",
"two/src/lib.nr",
Position { line: 46, character: 17 },
r#" one::subone

Check warning on line 682 in tooling/lsp/src/requests/hover.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (subone)
struct GenericStruct<A, B> {
}"#,
)
Expand All @@ -604,7 +692,7 @@
"workspace",
"two/src/lib.nr",
Position { line: 9, character: 35 },
r#" one::subone::SubOneStruct

Check warning on line 695 in tooling/lsp/src/requests/hover.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (subone)
some_field: i32"#,
)
.await;
Expand All @@ -616,7 +704,7 @@
"workspace",
"two/src/lib.nr",
Position { line: 12, character: 17 },
r#" one::subone

Check warning on line 707 in tooling/lsp/src/requests/hover.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (subone)
trait SomeTrait"#,
)
.await;
Expand All @@ -629,7 +717,7 @@
"two/src/lib.nr",
Position { line: 15, character: 25 },
r#" one::subone
global some_global: Field"#,
global some_global: Field = 2"#,
)
.await;
}
Expand Down
Loading