Skip to content

Commit

Permalink
feat: LSP fields, functions and methods completion after "." and "::" (
Browse files Browse the repository at this point in the history
…#5714)

# Description

## Problem

Part of #1577

## Summary

Suggest struct fields and methods after you type "." or "::". 


![lsp-methods](https://github.com/user-attachments/assets/ff242c4f-6a90-4bc1-bc90-9343141f169d)

Here it's working in a project inside Aztec-Packages:


![lsp-methods-2](https://github.com/user-attachments/assets/0b3c9496-b2ea-4d6c-9e50-2fc4295420e1)


## Additional Context

Some things don't work well yet. For example if you chain some calls,
like `[1, 2, 3].as_slice().` nothing is offered there. It has to do with
how things are stored in `id_to_location`. Or if you type `if
something.` then nothing gets offered because that doesn't parse right.

We could improve those things on later PRs, though, but at least
autocompletion now is much better than before.

## Documentation

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
asterite authored Aug 13, 2024
1 parent a87d926 commit 13c1fe6
Show file tree
Hide file tree
Showing 8 changed files with 900 additions and 160 deletions.
176 changes: 90 additions & 86 deletions aztec_macros/src/transforms/contract_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,101 +324,105 @@ pub fn update_fn_signatures_in_contract_interface(
});

if let Some(interface_struct) = maybe_interface_struct {
let methods = context.def_interner.get_struct_methods(interface_struct.borrow().id);

for func_id in methods.iter().flat_map(|methods| methods.direct.iter()) {
let name = context.def_interner.function_name(func_id);
let fn_parameters = &context.def_interner.function_meta(func_id).parameters.clone();

if name == "at" || name == "interface" || name == "storage" {
continue;
}

let fn_signature_hash = compute_fn_signature_hash(
name,
&fn_parameters
.iter()
.skip(1)
.map(|(_, typ, _)| typ.clone())
.collect::<Vec<Type>>(),
);
let hir_func = context.def_interner.function(func_id).block(&context.def_interner);

let function_selector_statement = context.def_interner.statement(
hir_func.statements().get(hir_func.statements().len() - 2).ok_or((
AztecMacroError::CouldNotGenerateContractInterface {
secondary_message: Some(
"Function signature statement not found, invalid body length"
.to_string(),
),
},
file_id,
))?,
);
let function_selector_expression_id = match function_selector_statement {
HirStatement::Let(let_statement) => Ok(let_statement.expression),
_ => Err((
AztecMacroError::CouldNotGenerateContractInterface {
secondary_message: Some(
"Function selector statement must be an expression".to_string(),
),
},
file_id,
)),
}?;
let function_selector_expression =
context.def_interner.expression(&function_selector_expression_id);

let current_fn_signature_expression_id = match function_selector_expression {
HirExpression::Call(call_expr) => Ok(call_expr.arguments[0]),
_ => Err((
AztecMacroError::CouldNotGenerateContractInterface {
secondary_message: Some(
"Function selector argument expression must be call expression"
.to_string(),
),
},
file_id,
)),
}?;

let current_fn_signature_expression =
context.def_interner.expression(&current_fn_signature_expression_id);
if let Some(methods) =
context.def_interner.get_struct_methods(interface_struct.borrow().id).cloned()
{
for func_id in methods.iter().flat_map(|(_name, methods)| methods.direct.iter()) {
let name = context.def_interner.function_name(func_id);
let fn_parameters =
&context.def_interner.function_meta(func_id).parameters.clone();

if name == "at" || name == "interface" || name == "storage" {
continue;
}

match current_fn_signature_expression {
HirExpression::Literal(HirLiteral::Integer(value, _)) => {
if !value.is_zero() {
Err((
let fn_signature_hash = compute_fn_signature_hash(
name,
&fn_parameters
.iter()
.skip(1)
.map(|(_, typ, _)| typ.clone())
.collect::<Vec<Type>>(),
);
let hir_func =
context.def_interner.function(func_id).block(&context.def_interner);

let function_selector_statement = context.def_interner.statement(
hir_func.statements().get(hir_func.statements().len() - 2).ok_or((
AztecMacroError::CouldNotGenerateContractInterface {
secondary_message: Some(
"Function signature statement not found, invalid body length"
.to_string(),
),
},
file_id,
))?,
);
let function_selector_expression_id = match function_selector_statement {
HirStatement::Let(let_statement) => Ok(let_statement.expression),
_ => Err((
AztecMacroError::CouldNotGenerateContractInterface {
secondary_message: Some(
"Function selector statement must be an expression".to_string(),
),
},
file_id,
)),
}?;
let function_selector_expression =
context.def_interner.expression(&function_selector_expression_id);

let current_fn_signature_expression_id = match function_selector_expression {
HirExpression::Call(call_expr) => Ok(call_expr.arguments[0]),
_ => Err((
AztecMacroError::CouldNotGenerateContractInterface {
secondary_message: Some(
"Function selector argument expression must be call expression"
.to_string(),
),
},
file_id,
)),
}?;

let current_fn_signature_expression =
context.def_interner.expression(&current_fn_signature_expression_id);

match current_fn_signature_expression {
HirExpression::Literal(HirLiteral::Integer(value, _)) => {
if !value.is_zero() {
Err((
AztecMacroError::CouldNotGenerateContractInterface {
secondary_message: Some(
"Function signature argument must be a placeholder with value 0".to_string()),
},
file_id,
))
} else {
Ok(())
} else {
Ok(())
}
}
}
_ => Err((
AztecMacroError::CouldNotGenerateContractInterface {
secondary_message: Some(
"Function signature argument must be a literal field element"
.to_string(),
),
_ => Err((
AztecMacroError::CouldNotGenerateContractInterface {
secondary_message: Some(
"Function signature argument must be a literal field element"
.to_string(),
),
},
file_id,
)),
}?;

context.def_interner.update_expression(
current_fn_signature_expression_id,
|expr| {
*expr = HirExpression::Literal(HirLiteral::Integer(
FieldElement::from(fn_signature_hash as u128),
false,
))
},
file_id,
)),
}?;

context.def_interner.update_expression(
current_fn_signature_expression_id,
|expr| {
*expr = HirExpression::Literal(HirLiteral::Integer(
FieldElement::from(fn_signature_hash as u128),
false,
))
},
);
);
}
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_frontend/src/hir_def/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,12 +666,12 @@ impl std::fmt::Display for Type {
Type::Function(args, ret, env) => {
let closure_env_text = match **env {
Type::Unit => "".to_string(),
_ => format!(" with env {env}"),
_ => format!("[{env}]"),
};

let args = vecmap(args.iter(), ToString::to_string);

write!(f, "fn({}) -> {ret}{closure_env_text}", args.join(", "))
write!(f, "fn{closure_env_text}({}) -> {ret}", args.join(", "))
}
Type::MutableReference(element) => {
write!(f, "&mut {element}")
Expand Down
68 changes: 42 additions & 26 deletions compiler/noirc_frontend/src/node_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,10 @@ pub struct NodeInterner {
/// may have both `impl Struct<u32> { fn foo(){} }` and `impl Struct<u8> { fn foo(){} }`.
/// If this happens, the returned Vec will have 2 entries and we'll need to further
/// disambiguate them by checking the type of each function.
struct_methods: HashMap<(StructId, String), Methods>,
struct_methods: HashMap<StructId, HashMap<String, Methods>>,

/// Methods on primitive types defined in the stdlib.
primitive_methods: HashMap<(TypeMethodKey, String), Methods>,
primitive_methods: HashMap<TypeMethodKey, HashMap<String, Methods>>,

// For trait implementation functions, this is their self type and trait they belong to
func_id_to_trait: HashMap<FuncId, (Type, TraitId)>,
Expand Down Expand Up @@ -1131,22 +1131,27 @@ impl NodeInterner {
self.structs[&id].clone()
}

pub fn get_struct_methods(&self, id: StructId) -> Vec<Methods> {
self.struct_methods
.keys()
.filter_map(|(key_id, name)| {
if key_id == &id {
Some(
self.struct_methods
.get(&(*key_id, name.clone()))
.expect("get_struct_methods given invalid StructId")
.clone(),
)
} else {
None
}
})
.collect()
pub fn get_struct_methods(&self, id: StructId) -> Option<&HashMap<String, Methods>> {
self.struct_methods.get(&id)
}

fn get_primitive_methods(&self, key: TypeMethodKey) -> Option<&HashMap<String, Methods>> {
self.primitive_methods.get(&key)
}

pub fn get_type_methods(&self, typ: &Type) -> Option<&HashMap<String, Methods>> {
match typ {
Type::Struct(struct_type, _) => {
let struct_type = struct_type.borrow();
self.get_struct_methods(struct_type.id)
}
Type::Alias(type_alias, generics) => {
let type_alias = type_alias.borrow();
let typ = type_alias.get_type(generics);
self.get_type_methods(&typ)
}
_ => get_type_method_key(typ).and_then(|key| self.get_primitive_methods(key)),
}
}

pub fn get_trait(&self, id: TraitId) -> &Trait {
Expand Down Expand Up @@ -1300,8 +1305,12 @@ impl NodeInterner {
return Some(existing);
}

let key = (id, method_name);
self.struct_methods.entry(key).or_default().add_method(method_id, is_trait_method);
self.struct_methods
.entry(id)
.or_default()
.entry(method_name)
.or_default()
.add_method(method_id, is_trait_method);
None
}
Type::Error => None,
Expand All @@ -1314,7 +1323,9 @@ impl NodeInterner {
unreachable!("Cannot add a method to the unsupported type '{}'", other)
});
self.primitive_methods
.entry((key, method_name))
.entry(key)
.or_default()
.entry(method_name)
.or_default()
.add_method(method_id, is_trait_method);
None
Expand Down Expand Up @@ -1648,7 +1659,7 @@ impl NodeInterner {
method_name: &str,
force_type_check: bool,
) -> Option<FuncId> {
let methods = self.struct_methods.get(&(id, method_name.to_owned()));
let methods = self.struct_methods.get(&id).and_then(|h| h.get(method_name));

// If there is only one method, just return it immediately.
// It will still be typechecked later.
Expand All @@ -1673,16 +1684,16 @@ impl NodeInterner {
} else {
// Failed to find a match for the type in question, switch to looking at impls
// for all types `T`, e.g. `impl<T> Foo for T`
let key = &(TypeMethodKey::Generic, method_name.to_owned());
let global_methods = self.primitive_methods.get(key)?;
let global_methods =
self.primitive_methods.get(&TypeMethodKey::Generic)?.get(method_name)?;
global_methods.find_matching_method(typ, self)
}
}

/// Looks up a given method name on the given primitive type.
pub fn lookup_primitive_method(&self, typ: &Type, method_name: &str) -> Option<FuncId> {
let key = get_type_method_key(typ)?;
let methods = self.primitive_methods.get(&(key, method_name.to_owned()))?;
let methods = self.primitive_methods.get(&key)?.get(method_name)?;
self.find_matching_method(typ, Some(methods), method_name)
}

Expand Down Expand Up @@ -1803,6 +1814,11 @@ impl NodeInterner {
self.prefix_operator_traits.insert(operator, trait_id);
}

pub fn is_operator_trait(&self, trait_id: TraitId) -> bool {
self.infix_operator_traits.values().any(|id| *id == trait_id)
|| self.prefix_operator_traits.values().any(|id| *id == trait_id)
}

/// This function is needed when creating a NodeInterner for testing so that calls
/// to `get_operator_trait` do not panic when the stdlib isn't present.
#[cfg(test)]
Expand Down Expand Up @@ -2017,7 +2033,7 @@ impl Methods {
}

/// Iterate through each method, starting with the direct methods
fn iter(&self) -> impl Iterator<Item = FuncId> + '_ {
pub fn iter(&self) -> impl Iterator<Item = FuncId> + '_ {
self.direct.iter().copied().chain(self.trait_impl_methods.iter().copied())
}

Expand Down
2 changes: 2 additions & 0 deletions compiler/noirc_frontend/src/parser/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub enum ParserErrorReason {
ExpectedFieldName(Token),
#[error("expected a pattern but found a type - {0}")]
ExpectedPatternButFoundType(Token),
#[error("expected an identifier after .")]
ExpectedIdentifierAfterDot,
#[error("expected an identifier after ::")]
ExpectedIdentifierAfterColons,
#[error("Expected a ; separating these two statements")]
Expand Down
Loading

0 comments on commit 13c1fe6

Please sign in to comment.