diff --git a/aztec_macros/src/utils/hir_utils.rs b/aztec_macros/src/utils/hir_utils.rs index 7198ed5bd3d..784fb9b5617 100644 --- a/aztec_macros/src/utils/hir_utils.rs +++ b/aztec_macros/src/utils/hir_utils.rs @@ -2,15 +2,15 @@ use acvm::acir::AcirField; use iter_extended::vecmap; use noirc_errors::Location; use noirc_frontend::ast; +use noirc_frontend::elaborator::Elaborator; +use noirc_frontend::hir::def_collector::dc_crate::{ + CollectedItems, UnresolvedFunctions, UnresolvedGlobal, +}; use noirc_frontend::macros_api::{HirExpression, HirLiteral}; use noirc_frontend::node_interner::{NodeInterner, TraitImplKind}; use noirc_frontend::{ graph::CrateId, - hir::{ - def_map::{LocalModuleId, ModuleId}, - resolution::{path_resolver::StandardPathResolver, resolver::Resolver}, - type_check::type_check_func, - }, + hir::def_map::{LocalModuleId, ModuleId}, macros_api::{FileId, HirContext, MacroError, ModuleDefId, StructId}, node_interner::{FuncId, TraitId}, Shared, StructType, Type, @@ -190,24 +190,17 @@ pub fn inject_fn( span: None, })?; - let def_maps = &mut context.def_maps; - - let path_resolver = - StandardPathResolver::new(ModuleId { local_id: module_id, krate: *crate_id }); - - let resolver = Resolver::new(&mut context.def_interner, &path_resolver, def_maps, file_id); - - let (hir_func, meta, _) = resolver.resolve_function(func, func_id); + let mut items = CollectedItems::default(); + let functions = vec![(module_id, func_id, func)]; + let trait_id = None; + items.functions.push(UnresolvedFunctions { file_id, functions, trait_id, self_type: None }); - context.def_interner.push_fn_meta(meta, func_id); - context.def_interner.update_fn(func_id, hir_func); - - let errors = type_check_func(&mut context.def_interner, func_id); + let errors = Elaborator::elaborate(context, *crate_id, items, None); if !errors.is_empty() { return Err(MacroError { primary_message: "Failed to type check autogenerated function".to_owned(), - secondary_message: Some(errors.iter().map(|err| err.to_string()).collect::()), + secondary_message: Some(errors.iter().map(|err| err.0.to_string()).collect::()), span: None, }); } @@ -243,17 +236,10 @@ pub fn inject_global( ) }); - let def_maps = &mut context.def_maps; - - let path_resolver = - StandardPathResolver::new(ModuleId { local_id: module_id, krate: *crate_id }); - - let mut resolver = Resolver::new(&mut context.def_interner, &path_resolver, def_maps, file_id); - - let hir_stmt = resolver.resolve_global_let(global, global_id); + let mut items = CollectedItems::default(); + items.globals.push(UnresolvedGlobal { file_id, module_id, global_id, stmt_def: global }); - let statement_id = context.def_interner.get_global(global_id).let_statement; - context.def_interner.replace_statement(statement_id, hir_stmt); + let _errors = Elaborator::elaborate(context, *crate_id, items, None); } pub fn fully_qualified_note_path(context: &HirContext, note_id: StructId) -> Option { diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 6b148cd5428..b41efebc905 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -10,7 +10,7 @@ use super::{ BlockExpression, Expression, ExpressionKind, IndexExpression, MemberAccessExpression, MethodCallExpression, UnresolvedType, }; -use crate::hir::resolution::resolver::SELF_TYPE_NAME; +use crate::elaborator::types::SELF_TYPE_NAME; use crate::lexer::token::SpannedToken; use crate::macros_api::SecondaryAttribute; use crate::parser::{ParserError, ParserErrorReason}; diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 72e2beea570..6eed2c16e6b 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -10,7 +10,7 @@ use crate::{ }, hir::{ comptime::{self, InterpreterError}, - resolution::{errors::ResolverError, resolver::LambdaContext}, + resolution::errors::ResolverError, type_check::TypeCheckError, }, hir_def::{ @@ -32,7 +32,7 @@ use crate::{ QuotedType, Shared, StructType, Type, }; -use super::Elaborator; +use super::{Elaborator, LambdaContext}; impl<'context> Elaborator<'context> { pub(super) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index b0fd8f79557..431a0b9a2da 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -16,12 +16,12 @@ use crate::{ dc_mod, errors::DuplicateType, }, - resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext}, + resolution::{errors::ResolverError, path_resolver::PathResolver}, scope::ScopeForest as GenericScopeForest, - type_check::{check_trait_impl_method_matches_declaration, TypeCheckError}, + type_check::TypeCheckError, }, hir_def::{ - expr::HirIdent, + expr::{HirCapturedVar, HirIdent}, function::{FunctionBody, Parameters}, traits::TraitConstraint, types::{Generics, Kind, ResolvedGeneric}, @@ -67,7 +67,7 @@ mod patterns; mod scope; mod statements; mod traits; -mod types; +pub mod types; mod unquote; use fm::FileId; @@ -75,6 +75,8 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; +use self::traits::check_trait_impl_method_matches_declaration; + /// ResolverMetas are tagged onto each definition to track how many times they are used #[derive(Debug, PartialEq, Eq)] pub struct ResolverMeta { @@ -85,6 +87,13 @@ pub struct ResolverMeta { type ScopeForest = GenericScopeForest; +pub struct LambdaContext { + pub captures: Vec, + /// the index in the scope tree + /// (sometimes being filled by ScopeTree's find method) + pub scope_index: usize, +} + pub struct Elaborator<'context> { scopes: ScopeForest, diff --git a/compiler/noirc_frontend/src/elaborator/scope.rs b/compiler/noirc_frontend/src/elaborator/scope.rs index e01c7e997d4..23638b03cf5 100644 --- a/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/compiler/noirc_frontend/src/elaborator/scope.rs @@ -3,7 +3,6 @@ use noirc_errors::{Location, Spanned}; use crate::ast::ERROR_IDENT; use crate::hir::def_map::{LocalModuleId, ModuleId}; use crate::hir::resolution::path_resolver::{PathResolver, StandardPathResolver}; -use crate::hir::resolution::resolver::SELF_TYPE_NAME; use crate::hir::scope::{Scope as GenericScope, ScopeTree as GenericScopeTree}; use crate::macros_api::Ident; use crate::{ @@ -21,6 +20,7 @@ use crate::{ }; use crate::{Type, TypeAlias}; +use super::types::SELF_TYPE_NAME; use super::{Elaborator, ResolverMeta}; type Scope = GenericScope; diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index 0627121f836..9443791700f 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -1,21 +1,27 @@ use std::{collections::BTreeMap, rc::Rc}; use iter_extended::vecmap; -use noirc_errors::Location; +use noirc_errors::{Location, Span}; use crate::{ ast::{ FunctionKind, TraitItem, UnresolvedGeneric, UnresolvedGenerics, UnresolvedTraitConstraint, }, - hir::def_collector::dc_crate::{CollectedItems, UnresolvedTrait}, - hir_def::traits::{TraitConstant, TraitFunction, TraitType}, + hir::{ + def_collector::dc_crate::{CollectedItems, UnresolvedTrait}, + type_check::TypeCheckError, + }, + hir_def::{ + function::Parameters, + traits::{TraitConstant, TraitFunction, TraitType}, + }, macros_api::{ BlockExpression, FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, - NoirFunction, Param, Pattern, UnresolvedType, Visibility, + NodeInterner, NoirFunction, Param, Pattern, UnresolvedType, Visibility, }, node_interner::{FuncId, TraitId}, token::Attributes, - Kind, ResolvedGeneric, Type, TypeVariableKind, + Kind, ResolvedGeneric, Type, TypeBindings, TypeVariableKind, }; use super::Elaborator; @@ -204,3 +210,159 @@ impl<'context> Elaborator<'context> { self.generics.truncate(old_generic_count); } } + +/// Checks that the type of a function in a trait impl matches the type +/// of the corresponding function declaration in the trait itself. +/// +/// To do this, given a trait such as: +/// `trait Foo { fn foo(...); }` +/// +/// And an impl such as: +/// `impl Foo for Bar { fn foo(...); } ` +/// +/// We have to substitute: +/// - Self for Bar +/// - A for D +/// - B for F +/// +/// Before we can type check. Finally, we must also check that the unification +/// result does not introduce any new bindings. This can happen if the impl +/// function's type is more general than that of the trait function. E.g. +/// `fn baz(a: A, b: B)` when the impl required `fn baz(a: A, b: A)`. +/// +/// This does not type check the body of the impl function. +pub(crate) fn check_trait_impl_method_matches_declaration( + interner: &mut NodeInterner, + function: FuncId, +) -> Vec { + let meta = interner.function_meta(&function); + let method_name = interner.function_name(&function); + let mut errors = Vec::new(); + + let definition_type = meta.typ.as_monotype(); + + let impl_ = + meta.trait_impl.expect("Trait impl function should have a corresponding trait impl"); + + // If the trait implementation is not defined in the interner then there was a previous + // error in resolving the trait path and there is likely no trait for this impl. + let Some(impl_) = interner.try_get_trait_implementation(impl_) else { + return errors; + }; + + let impl_ = impl_.borrow(); + let trait_info = interner.get_trait(impl_.trait_id); + + let mut bindings = TypeBindings::new(); + bindings.insert( + trait_info.self_type_typevar_id, + (trait_info.self_type_typevar.clone(), impl_.typ.clone()), + ); + + if trait_info.generics.len() != impl_.trait_generics.len() { + let expected = trait_info.generics.len(); + let found = impl_.trait_generics.len(); + let span = impl_.ident.span(); + let item = trait_info.name.to_string(); + errors.push(TypeCheckError::GenericCountMismatch { item, expected, found, span }); + } + + // Substitute each generic on the trait with the corresponding generic on the impl + for (generic, arg) in trait_info.generics.iter().zip(&impl_.trait_generics) { + bindings.insert(generic.type_var.id(), (generic.type_var.clone(), arg.clone())); + } + + // If this is None, the trait does not have the corresponding function. + // This error should have been caught in name resolution already so we don't + // issue an error for it here. + if let Some(trait_fn_id) = trait_info.method_ids.get(method_name) { + let trait_fn_meta = interner.function_meta(trait_fn_id); + + if trait_fn_meta.direct_generics.len() != meta.direct_generics.len() { + let expected = trait_fn_meta.direct_generics.len(); + let found = meta.direct_generics.len(); + let span = meta.name.location.span; + let item = method_name.to_string(); + errors.push(TypeCheckError::GenericCountMismatch { item, expected, found, span }); + } + + // Substitute each generic on the trait function with the corresponding generic on the impl function + for ( + ResolvedGeneric { type_var: trait_fn_generic, .. }, + ResolvedGeneric { name, type_var: impl_fn_generic, .. }, + ) in trait_fn_meta.direct_generics.iter().zip(&meta.direct_generics) + { + let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone(), Kind::Normal); + bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), arg)); + } + + let (declaration_type, _) = trait_fn_meta.typ.instantiate_with_bindings(bindings, interner); + + check_function_type_matches_expected_type( + &declaration_type, + definition_type, + method_name, + &meta.parameters, + meta.name.location.span, + &trait_info.name.0.contents, + &mut errors, + ); + } + + errors +} + +fn check_function_type_matches_expected_type( + expected: &Type, + actual: &Type, + method_name: &str, + actual_parameters: &Parameters, + span: Span, + trait_name: &str, + errors: &mut Vec, +) { + let mut bindings = TypeBindings::new(); + // Shouldn't need to unify envs, they should always be equal since they're both free functions + if let (Type::Function(params_a, ret_a, _env_a), Type::Function(params_b, ret_b, _env_b)) = + (expected, actual) + { + if params_a.len() == params_b.len() { + for (i, (a, b)) in params_a.iter().zip(params_b.iter()).enumerate() { + if a.try_unify(b, &mut bindings).is_err() { + errors.push(TypeCheckError::TraitMethodParameterTypeMismatch { + method_name: method_name.to_string(), + expected_typ: a.to_string(), + actual_typ: b.to_string(), + parameter_span: actual_parameters.0[i].0.span(), + parameter_index: i + 1, + }); + } + } + + if ret_b.try_unify(ret_a, &mut bindings).is_err() { + errors.push(TypeCheckError::TypeMismatch { + expected_typ: ret_a.to_string(), + expr_typ: ret_b.to_string(), + expr_span: span, + }); + } + } else { + errors.push(TypeCheckError::MismatchTraitImplNumParameters { + actual_num_parameters: params_b.len(), + expected_num_parameters: params_a.len(), + trait_name: trait_name.to_string(), + method_name: method_name.to_string(), + span, + }); + } + } + + // If result bindings is not empty, a type variable was bound which means the two + // signatures were not a perfect match. Note that this relies on us already binding + // all the expected generics to each other prior to this check. + if !bindings.is_empty() { + let expected_typ = expected.to_string(); + let expr_typ = actual.to_string(); + errors.push(TypeCheckError::TypeMismatch { expected_typ, expr_typ, expr_span: span }); + } +} diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 35ff421ed32..a50b8949971 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -12,10 +12,7 @@ use crate::{ hir::{ comptime::{Interpreter, Value}, def_map::ModuleDefId, - resolution::{ - errors::ResolverError, - resolver::{verify_mutable_reference, SELF_TYPE_NAME, WILDCARD_TYPE}, - }, + resolution::errors::ResolverError, type_check::{NoMatchingImplFoundError, Source, TypeCheckError}, }, hir_def::{ @@ -27,8 +24,8 @@ use crate::{ traits::TraitConstraint, }, macros_api::{ - HirExpression, HirLiteral, HirStatement, Path, PathKind, SecondaryAttribute, Signedness, - UnaryOp, UnresolvedType, UnresolvedTypeData, + HirExpression, HirLiteral, HirStatement, NodeInterner, Path, PathKind, SecondaryAttribute, + Signedness, UnaryOp, UnresolvedType, UnresolvedTypeData, }, node_interner::{ DefinitionKind, DependencyId, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId, @@ -38,6 +35,9 @@ use crate::{ use super::{lints, Elaborator}; +pub const SELF_TYPE_NAME: &str = "Self"; +pub const WILDCARD_TYPE: &str = "_"; + impl<'context> Elaborator<'context> { /// Translates an UnresolvedType to a Type with a `TypeKind::Normal` pub(super) fn resolve_type(&mut self, typ: UnresolvedType) -> Type { @@ -1617,3 +1617,29 @@ impl<'context> Elaborator<'context> { context.trait_constraints.push((constraint, expr_id)); } } + +/// 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> { + match interner.expression(&rhs) { + HirExpression::MemberAccess(member_access) => { + verify_mutable_reference(interner, member_access.lhs) + } + HirExpression::Index(_) => { + let span = interner.expr_span(&rhs); + Err(ResolverError::MutableReferenceToArrayElement { span }) + } + HirExpression::Ident(ident, _) => { + if let Some(definition) = interner.try_definition(ident.id) { + if !definition.mutable { + return Err(ResolverError::MutableReferenceToImmutableVariable { + span: interner.expr_span(&rhs), + variable: definition.name.clone(), + }); + } + } + Ok(()) + } + _ => Ok(()), + } +} diff --git a/compiler/noirc_frontend/src/hir/comptime/tests.rs b/compiler/noirc_frontend/src/hir/comptime/tests.rs index 6fdd956caf6..84430bdfa30 100644 --- a/compiler/noirc_frontend/src/hir/comptime/tests.rs +++ b/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -1,17 +1,62 @@ #![cfg(test)] -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; +use std::path::PathBuf; +use fm::{FileId, FileManager}; +use noirc_arena::Index; use noirc_errors::Location; use super::errors::InterpreterError; use super::interpreter::Interpreter; use super::value::Value; +use crate::elaborator::Elaborator; use crate::graph::CrateId; -use crate::hir::type_check::test::type_check_src_code; +use crate::hir::def_collector::dc_crate::DefCollector; +use crate::hir::def_collector::dc_mod::collect_defs; +use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleData}; +use crate::hir::{Context, ParsedFiles}; +use crate::macros_api::NodeInterner; +use crate::node_interner::FuncId; +use crate::parser::parse_program; -fn interpret_helper(src: &str, func_namespace: Vec) -> Result { - let (mut interner, main_id) = type_check_src_code(src, func_namespace); +fn elaborate_src_code(src: &str) -> (NodeInterner, FuncId) { + let file = FileId::default(); + + // Can't use Index::test_new here for some reason, even with #[cfg(test)]. + let module_id = LocalModuleId(Index::unsafe_zeroed()); + let mut modules = noirc_arena::Arena::default(); + let location = Location::new(Default::default(), file); + let root = LocalModuleId(modules.insert(ModuleData::new(None, location, false))); + assert_eq!(root, module_id); + + let file_manager = FileManager::new(&PathBuf::new()); + let parsed_files = ParsedFiles::new(); + let mut context = Context::new(file_manager, parsed_files); + context.def_interner.populate_dummy_operator_traits(); + + let krate = context.crate_graph.add_crate_root(FileId::dummy()); + + let (module, errors) = parse_program(src); + assert_eq!(errors.len(), 0); + let ast = module.into_sorted(); + + let def_map = CrateDefMap { root: module_id, modules, krate, extern_prelude: BTreeMap::new() }; + let mut collector = DefCollector::new(def_map); + + collect_defs(&mut collector, ast, FileId::dummy(), module_id, krate, &mut context, &[]); + context.def_maps.insert(krate, collector.def_map); + + let errors = Elaborator::elaborate(&mut context, krate, collector.items, None); + assert_eq!(errors.len(), 0); + + let main = context.get_main_function(&krate).expect("Expected 'main' function"); + + (context.def_interner, main) +} + +fn interpret_helper(src: &str) -> Result { + let (mut interner, main_id) = elaborate_src_code(src); let mut scopes = vec![HashMap::default()]; let no_debug_evaluate_comptime = None; let mut interpreter_errors = vec![]; @@ -27,20 +72,20 @@ fn interpret_helper(src: &str, func_namespace: Vec) -> Result) -> Value { - interpret_helper(src, func_namespace).unwrap_or_else(|error| { +fn interpret(src: &str) -> Value { + interpret_helper(src).unwrap_or_else(|error| { panic!("Expected interpreter to exit successfully, but found {error:?}") }) } -fn interpret_expect_error(src: &str, func_namespace: Vec) -> InterpreterError { - interpret_helper(src, func_namespace).expect_err("Expected interpreter to error") +fn interpret_expect_error(src: &str) -> InterpreterError { + interpret_helper(src).expect_err("Expected interpreter to error") } #[test] fn interpreter_works() { let program = "comptime fn main() -> pub Field { 3 }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::Field(3u128.into())); } @@ -51,7 +96,7 @@ fn mutation_works() { x = 4; x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::I8(4)); } @@ -62,7 +107,7 @@ fn mutating_references() { *x = 4; *x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::I32(4)); } @@ -73,7 +118,7 @@ fn mutating_mutable_references() { *x = 4; *x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::I64(4)); } @@ -85,7 +130,7 @@ fn mutation_leaks() { *y = 5; x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::I8(5)); } @@ -96,7 +141,7 @@ fn mutating_arrays() { a1[1] = 22; a1[1] }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::U8(22)); } @@ -110,7 +155,7 @@ fn mutate_in_new_scope() { } x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::U8(2)); } @@ -123,7 +168,7 @@ fn for_loop() { } x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::U8(15)); } @@ -136,7 +181,7 @@ fn for_loop_u16() { } x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::U16(15)); } @@ -152,7 +197,7 @@ fn for_loop_with_break() { } x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::U32(6)); } @@ -168,7 +213,7 @@ fn for_loop_with_continue() { } x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::U64(11)); } @@ -177,7 +222,7 @@ fn assert() { let program = "comptime fn main() { assert(1 == 1); }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::Unit); } @@ -186,7 +231,7 @@ fn assert_fail() { let program = "comptime fn main() { assert(1 == 2); }"; - let result = interpret_expect_error(program, vec!["main".into()]); + let result = interpret_expect_error(program); assert!(matches!(result, InterpreterError::FailingConstraint { .. })); } @@ -196,7 +241,7 @@ fn lambda() { let f = |x: u8| x + 1; f(1) }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert!(matches!(result, Value::U8(2))); } @@ -214,21 +259,21 @@ fn non_deterministic_recursion() { fib(x - 1) + fib(x - 2) } }"; - let result = interpret(program, vec!["main".into(), "fib".into()]); + let result = interpret(program); assert_eq!(result, Value::U64(55)); } #[test] fn generic_functions() { let program = " - fn main() -> pub u8 { + comptime fn main() -> pub u8 { apply(1, |x| x + 1) } - fn apply(x: T, f: fn[Env](T) -> U) -> U { + comptime fn apply(x: T, f: fn[Env](T) -> U) -> U { f(x) } "; - let result = interpret(program, vec!["main".into(), "apply".into()]); + let result = interpret(program); assert!(matches!(result, Value::U8(2))); } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 199c5626546..200e8cbb22f 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -115,11 +115,11 @@ pub struct DefCollector { #[derive(Default)] pub struct CollectedItems { - pub(crate) functions: Vec, + pub functions: Vec, pub(crate) types: BTreeMap, pub(crate) type_aliases: BTreeMap, pub(crate) traits: BTreeMap, - pub(crate) globals: Vec, + pub globals: Vec, pub(crate) impls: ImplMap, pub(crate) trait_impls: Vec, } @@ -156,6 +156,19 @@ pub enum CompilationError { DebugComptimeScopeNotFound(Vec), } +impl std::fmt::Display for CompilationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CompilationError::ParseError(error) => write!(f, "{}", error), + CompilationError::DefinitionError(error) => write!(f, "{}", error), + CompilationError::ResolverError(error) => write!(f, "{}", error), + CompilationError::TypeError(error) => write!(f, "{}", error), + CompilationError::InterpreterError(error) => write!(f, "{:?}", error), + CompilationError::DebugComptimeScopeNotFound(error) => write!(f, "{:?}", error), + } + } +} + impl<'a> From<&'a CompilationError> for CustomDiagnostic { fn from(value: &'a CompilationError) -> Self { match value { @@ -208,7 +221,7 @@ impl From for CompilationError { } impl DefCollector { - fn new(def_map: CrateDefMap) -> DefCollector { + pub fn new(def_map: CrateDefMap) -> DefCollector { DefCollector { def_map, imports: vec![], diff --git a/compiler/noirc_frontend/src/hir/def_map/module_data.rs b/compiler/noirc_frontend/src/hir/def_map/module_data.rs index 488ccc476d7..62ece65c6de 100644 --- a/compiler/noirc_frontend/src/hir/def_map/module_data.rs +++ b/compiler/noirc_frontend/src/hir/def_map/module_data.rs @@ -108,7 +108,7 @@ impl ModuleData { } pub fn find_func_with_name(&self, name: &Ident) -> Option { - self.scope.find_func_with_name(name) + dbg!(&self.scope).find_func_with_name(name) } pub fn import( diff --git a/compiler/noirc_frontend/src/hir/resolution/mod.rs b/compiler/noirc_frontend/src/hir/resolution/mod.rs index 601e78015ca..01a3fe856e5 100644 --- a/compiler/noirc_frontend/src/hir/resolution/mod.rs +++ b/compiler/noirc_frontend/src/hir/resolution/mod.rs @@ -8,4 +8,3 @@ pub mod errors; pub mod import; pub mod path_resolver; -pub mod resolver; diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs deleted file mode 100644 index de953946004..00000000000 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ /dev/null @@ -1,2198 +0,0 @@ -// Fix usage of intern and resolve -// In some places, we do intern, however in others we are resolving and interning -// Ideally, I want to separate the interning and resolving abstractly -// so separate functions, but combine them naturally -// This could be possible, if lowering, is given a mutable map/scope as a parameter. -// So that it can match Idents to Ids. This is close to what the Scope map looks like -// Except for the num_times_used parameter. -// We can instead have a map from Ident to Into and implement that trait on ResolverMeta -// -// -// XXX: Change mentions of intern to resolve. In regards to the above comment -// -// XXX: Resolver does not check for unused functions -use acvm::acir::AcirField; - -use crate::hir_def::expr::{ - HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCapturedVar, - HirCastExpression, HirConstructorExpression, HirExpression, HirIdent, HirIfExpression, - HirIndexExpression, HirInfixExpression, HirLambda, HirLiteral, HirMemberAccess, - HirMethodCallExpression, HirPrefixExpression, ImplKind, -}; - -use crate::hir_def::function::FunctionBody; -use crate::hir_def::traits::{Trait, TraitConstraint}; -use crate::macros_api::SecondaryAttribute; -use crate::token::Attributes; -use regex::Regex; -use std::collections::{BTreeMap, BTreeSet, HashSet}; -use std::rc::Rc; - -use crate::ast::{ - ArrayLiteral, BinaryOpKind, BlockExpression, Expression, ExpressionKind, ForRange, - FunctionDefinition, FunctionKind, FunctionReturnType, Ident, ItemVisibility, LValue, - LetStatement, Literal, NoirFunction, NoirStruct, NoirTypeAlias, Param, Path, PathKind, Pattern, - Statement, StatementKind, TraitBound, UnaryOp, UnresolvedGeneric, UnresolvedGenerics, - UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, - Visibility, ERROR_IDENT, -}; -use crate::graph::CrateId; -use crate::hir::def_map::{ModuleDefId, TryFromModuleDefId, MAIN_FUNCTION}; -use crate::hir::{ - comptime::{Interpreter, Value}, - def_map::CrateDefMap, - resolution::path_resolver::PathResolver, -}; -use crate::hir_def::stmt::{HirAssignStatement, HirForStatement, HirLValue, HirPattern}; -use crate::node_interner::{ - DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, NodeInterner, StmtId, - StructId, TraitId, TraitImplId, TraitMethodId, TypeAliasId, -}; -use crate::{ - GenericTypeVars, Generics, Kind, ResolvedGeneric, Shared, StructType, Type, TypeAlias, - TypeVariable, TypeVariableKind, -}; -use fm::FileId; -use iter_extended::vecmap; -use noirc_errors::{Location, Span, Spanned}; - -use crate::hir::scope::{ - Scope as GenericScope, ScopeForest as GenericScopeForest, ScopeTree as GenericScopeTree, -}; -use crate::hir_def::{ - function::{FuncMeta, HirFunction}, - stmt::{HirConstrainStatement, HirLetStatement, HirStatement}, -}; - -use super::errors::{PubPosition, ResolverError}; -use super::import::PathResolution; - -pub const SELF_TYPE_NAME: &str = "Self"; -pub const WILDCARD_TYPE: &str = "_"; - -type Scope = GenericScope; -type ScopeTree = GenericScopeTree; -type ScopeForest = GenericScopeForest; - -pub struct LambdaContext { - pub captures: Vec, - /// the index in the scope tree - /// (sometimes being filled by ScopeTree's find method) - pub scope_index: usize, -} - -/// The primary jobs of the Resolver are to validate that every variable found refers to exactly 1 -/// definition in scope, and to convert the AST into the HIR. -/// -/// A Resolver is a short-lived struct created to resolve a top-level definition. -/// One of these is created for each function definition and struct definition. -/// This isn't strictly necessary to its function, it could be refactored out in the future. -pub struct Resolver<'a> { - scopes: ScopeForest, - path_resolver: &'a dyn PathResolver, - def_maps: &'a BTreeMap, - trait_id: Option, - trait_bounds: Vec, - pub interner: &'a mut NodeInterner, - errors: Vec, - file: FileId, - - /// Set to the current type if we're resolving an impl - self_type: Option, - - /// If we're currently resolving methods within a trait impl, this will be set - /// to the corresponding trait impl ID. - current_trait_impl: Option, - - /// The current dependency item we're resolving. - /// Used to link items to their dependencies in the dependency graph - current_item: Option, - - /// In-resolution names - /// - /// This needs to be a set because we can have multiple in-resolution - /// names when resolving structs that are declared in reverse order of their - /// dependencies, such as in the following case: - /// - /// ``` - /// struct Wrapper { - /// value: Wrapped - /// } - /// struct Wrapped { - /// } - /// ``` - resolving_ids: BTreeSet, - - /// True if the current module is a contract. - /// This is usually determined by self.path_resolver.module_id(), but it can - /// be overridden for impls. Impls are an odd case since the methods within resolve - /// as if they're in the parent module, but should be placed in a child module. - /// Since they should be within a child module, in_contract is manually set to false - /// for these so we can still resolve them in the parent module without them being in a contract. - in_contract: bool, - - /// Contains a mapping of the current struct or functions's generics to - /// unique type variables if we're resolving a struct. Empty otherwise. - /// This is a Vec rather than a map to preserve the order a functions generics - /// were declared in. - generics: Vec, - - /// When resolving lambda expressions, we need to keep track of the variables - /// that are captured. We do this in order to create the hidden environment - /// parameter for the lambda function. - lambda_stack: Vec, - - /// True if we're currently resolving an unconstrained function - in_unconstrained_fn: bool, - - /// How many loops we're currently within. - /// This increases by 1 at the start of a loop, and decreases by 1 when it ends. - nested_loops: u32, -} - -/// ResolverMetas are tagged onto each definition to track how many times they are used -#[derive(Debug, PartialEq, Eq)] -struct ResolverMeta { - num_times_used: usize, - ident: HirIdent, - warn_if_unused: bool, -} - -pub enum ResolvePathError { - WrongKind, - NotFound, -} - -impl<'a> Resolver<'a> { - pub fn new( - interner: &'a mut NodeInterner, - path_resolver: &'a dyn PathResolver, - def_maps: &'a BTreeMap, - file: FileId, - ) -> Resolver<'a> { - let module_id = path_resolver.module_id(); - let in_contract = module_id.module(def_maps).is_contract; - - Self { - path_resolver, - def_maps, - trait_id: None, - trait_bounds: Vec::new(), - scopes: ScopeForest::default(), - interner, - self_type: None, - generics: Vec::new(), - errors: Vec::new(), - lambda_stack: Vec::new(), - current_trait_impl: None, - current_item: None, - resolving_ids: BTreeSet::new(), - file, - in_contract, - in_unconstrained_fn: false, - nested_loops: 0, - } - } - - pub fn set_self_type(&mut self, self_type: Option) { - self.self_type = self_type; - } - - pub fn set_trait_id(&mut self, trait_id: Option) { - self.trait_id = trait_id; - } - - pub fn set_trait_impl_id(&mut self, impl_id: Option) { - self.current_trait_impl = impl_id; - } - - pub fn get_self_type(&mut self) -> Option<&Type> { - self.self_type.as_ref() - } - - fn push_err(&mut self, err: ResolverError) { - self.errors.push(err); - } - - /// This turns function parameters of the form: - /// fn foo(x: impl Bar) - /// - /// into - /// fn foo(x: T0_impl_Bar) where T0_impl_Bar: Bar - fn desugar_impl_trait_args(&mut self, func: &mut NoirFunction, func_id: FuncId) { - let mut impl_trait_generics = HashSet::new(); - let mut counter: usize = 0; - for parameter in func.def.parameters.iter_mut() { - if let UnresolvedTypeData::TraitAsType(path, args) = ¶meter.typ.typ { - let mut new_generic_ident: Ident = - format!("T{}_impl_{}", func_id, path.as_string()).into(); - let mut new_generic_path = Path::from_ident(new_generic_ident.clone()); - let new_generic = UnresolvedGeneric::from(new_generic_ident.clone()); - while impl_trait_generics.contains(&new_generic) - || self.lookup_generic_or_global_type(&new_generic_path).is_some() - { - new_generic_ident = - format!("T{}_impl_{}_{}", func_id, path.as_string(), counter).into(); - new_generic_path = Path::from_ident(new_generic_ident.clone()); - counter += 1; - } - impl_trait_generics.insert(UnresolvedGeneric::from(new_generic_ident.clone())); - - let is_synthesized = true; - let new_generic_type_data = - UnresolvedTypeData::Named(new_generic_path, vec![], is_synthesized); - let new_generic_type = - UnresolvedType { typ: new_generic_type_data.clone(), span: None }; - let new_trait_bound = TraitBound { - trait_path: path.clone(), - trait_id: None, - trait_generics: args.to_vec(), - }; - let new_trait_constraint = UnresolvedTraitConstraint { - typ: new_generic_type, - trait_bound: new_trait_bound, - }; - - parameter.typ.typ = new_generic_type_data; - func.def.generics.push(new_generic_ident.into()); - func.def.where_clause.push(new_trait_constraint); - } - } - self.add_generics(&impl_trait_generics.into_iter().collect()); - } - - /// Resolving a function involves interning the metadata - /// interning any statements inside of the function - /// and interning the function itself - /// We resolve and lower the function at the same time - /// Since lowering would require scope data, unless we add an extra resolution field to the AST - pub fn resolve_function( - mut self, - mut func: NoirFunction, - func_id: FuncId, - ) -> (HirFunction, FuncMeta, Vec) { - self.scopes.start_function(); - self.current_item = Some(DependencyId::Function(func_id)); - - // Check whether the function has globals in the local module and add them to the scope - self.resolve_local_globals(); - self.add_generics(&func.def.generics); - - self.desugar_impl_trait_args(&mut func, func_id); - self.trait_bounds = func.def.where_clause.clone(); - - let is_low_level_or_oracle = func - .attributes() - .function - .as_ref() - .map_or(false, |func| func.is_low_level() || func.is_oracle()); - let (hir_func, func_meta) = self.intern_function(func, func_id); - let func_scope_tree = self.scopes.end_function(); - - // The arguments to low-level and oracle functions are always unused so we do not produce warnings for them. - if !is_low_level_or_oracle { - self.check_for_unused_variables_in_scope_tree(func_scope_tree); - } - - self.trait_bounds.clear(); - (hir_func, func_meta, self.errors) - } - - pub fn resolve_trait_function( - &mut self, - name: &Ident, - generics: &UnresolvedGenerics, - parameters: &[(Ident, UnresolvedType)], - return_type: &FunctionReturnType, - where_clause: &[UnresolvedTraitConstraint], - func_id: FuncId, - ) -> (HirFunction, FuncMeta) { - self.scopes.start_function(); - - // Check whether the function has globals in the local module and add them to the scope - self.resolve_local_globals(); - - self.trait_bounds = where_clause.to_vec(); - - let kind = FunctionKind::Normal; - let def = FunctionDefinition { - name: name.clone(), - attributes: Attributes::empty(), - is_unconstrained: false, - is_comptime: false, - visibility: ItemVisibility::Public, // Trait functions are always public - generics: generics.clone(), - parameters: vecmap(parameters, |(name, typ)| Param { - visibility: Visibility::Private, - pattern: Pattern::Identifier(name.clone()), - typ: typ.clone(), - span: name.span(), - }), - body: BlockExpression { statements: Vec::new() }, - span: name.span(), - where_clause: where_clause.to_vec(), - return_type: return_type.clone(), - return_visibility: Visibility::Private, - }; - - let (hir_func, func_meta) = self.intern_function(NoirFunction { kind, def }, func_id); - let _ = self.scopes.end_function(); - // Don't check the scope tree for unused variables, they can't be used in a declaration anyway. - self.trait_bounds.clear(); - (hir_func, func_meta) - } - - fn check_for_unused_variables_in_scope_tree(&mut self, scope_decls: ScopeTree) { - let mut unused_vars = Vec::new(); - for scope in scope_decls.0.into_iter() { - Resolver::check_for_unused_variables_in_local_scope(scope, &mut unused_vars); - } - - for unused_var in unused_vars.iter() { - if let Some(definition_info) = self.interner.try_definition(unused_var.id) { - let name = &definition_info.name; - if name != ERROR_IDENT && !definition_info.is_global() { - let ident = Ident(Spanned::from(unused_var.location.span, name.to_owned())); - self.push_err(ResolverError::UnusedVariable { ident }); - } - } - } - } - - fn check_for_unused_variables_in_local_scope(decl_map: Scope, unused_vars: &mut Vec) { - let unused_variables = decl_map.filter(|(variable_name, metadata)| { - let has_underscore_prefix = variable_name.starts_with('_'); // XXX: This is used for development mode, and will be removed - metadata.warn_if_unused && metadata.num_times_used == 0 && !has_underscore_prefix - }); - unused_vars.extend(unused_variables.map(|(_, meta)| meta.ident.clone())); - } - - /// Run the given function in a new scope. - fn in_new_scope T>(&mut self, f: F) -> T { - self.scopes.start_scope(); - let ret = f(self); - let scope = self.scopes.end_scope(); - self.check_for_unused_variables_in_scope_tree(scope.into()); - ret - } - - fn add_variable_decl( - &mut self, - name: Ident, - mutable: bool, - allow_shadowing: bool, - definition: DefinitionKind, - ) -> HirIdent { - self.add_variable_decl_inner(name, mutable, allow_shadowing, true, definition) - } - - fn add_variable_decl_inner( - &mut self, - name: Ident, - mutable: bool, - allow_shadowing: bool, - warn_if_unused: bool, - definition: DefinitionKind, - ) -> HirIdent { - if definition.is_global() { - return self.add_global_variable_decl(name, definition); - } - - let location = Location::new(name.span(), self.file); - let var_name = name.0.contents.clone(); - let id = self.interner.push_definition(var_name, mutable, false, definition, location); - let ident = HirIdent::non_trait_method(id, location); - let resolver_meta = - ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused }; - - let scope = self.scopes.get_mut_scope(); - let old_value = scope.add_key_value(name.0.contents.clone(), resolver_meta); - - if !allow_shadowing { - if let Some(old_value) = old_value { - self.push_err(ResolverError::DuplicateDefinition { - name: name.0.contents, - first_span: old_value.ident.location.span, - second_span: location.span, - }); - } - } - - ident - } - - fn add_global_variable_decl(&mut self, name: Ident, definition: DefinitionKind) -> HirIdent { - let scope = self.scopes.get_mut_scope(); - - // This check is necessary to maintain the same definition ids in the interner. Currently, each function uses a new resolver that has its own ScopeForest and thus global scope. - // We must first check whether an existing definition ID has been inserted as otherwise there will be multiple definitions for the same global statement. - // This leads to an error in evaluation where the wrong definition ID is selected when evaluating a statement using the global. The check below prevents this error. - let mut global_id = None; - let global = self.interner.get_all_globals(); - for global_info in global { - if global_info.ident == name - && global_info.local_id == self.path_resolver.local_module_id() - { - global_id = Some(global_info.id); - } - } - - let (ident, resolver_meta) = if let Some(id) = global_id { - let global = self.interner.get_global(id); - let hir_ident = HirIdent::non_trait_method(global.definition_id, global.location); - let ident = hir_ident.clone(); - let resolver_meta = ResolverMeta { num_times_used: 0, ident, warn_if_unused: true }; - (hir_ident, resolver_meta) - } else { - let location = Location::new(name.span(), self.file); - let var_name = name.0.contents.clone(); - let id = self.interner.push_definition(var_name, false, false, definition, location); - let ident = HirIdent::non_trait_method(id, location); - let resolver_meta = - ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused: true }; - (ident, resolver_meta) - }; - - let old_global_value = scope.add_key_value(name.0.contents.clone(), resolver_meta); - if let Some(old_global_value) = old_global_value { - self.push_err(ResolverError::DuplicateDefinition { - name: name.0.contents.clone(), - first_span: old_global_value.ident.location.span, - second_span: name.span(), - }); - } - ident - } - - // Checks for a variable having been declared before - // variable declaration and definition cannot be separate in Noir - // Once the variable has been found, intern and link `name` to this definition - // return the IdentId of `name` - // - // If a variable is not found, then an error is logged and a dummy id - // is returned, for better error reporting UX - fn find_variable_or_default(&mut self, name: &Ident) -> (HirIdent, usize) { - self.find_variable(name).unwrap_or_else(|error| { - self.push_err(error); - let id = DefinitionId::dummy_id(); - let location = Location::new(name.span(), self.file); - (HirIdent::non_trait_method(id, location), 0) - }) - } - - fn find_variable(&mut self, name: &Ident) -> Result<(HirIdent, usize), ResolverError> { - // Find the definition for this Ident - let scope_tree = self.scopes.current_scope_tree(); - let variable = scope_tree.find(&name.0.contents); - - let location = Location::new(name.span(), self.file); - if let Some((variable_found, scope)) = variable { - variable_found.num_times_used += 1; - let id = variable_found.ident.id; - Ok((HirIdent::non_trait_method(id, location), scope)) - } else { - Err(ResolverError::VariableNotDeclared { - name: name.0.contents.clone(), - span: name.0.span(), - }) - } - } - - fn intern_function(&mut self, func: NoirFunction, id: FuncId) -> (HirFunction, FuncMeta) { - let func_meta = self.extract_meta(&func, id); - - if func.def.is_unconstrained { - self.in_unconstrained_fn = true; - } - - let hir_func = match func.kind { - FunctionKind::Builtin | FunctionKind::LowLevel | FunctionKind::Oracle => { - HirFunction::empty() - } - FunctionKind::Normal | FunctionKind::Recursive => { - let expr_id = self.intern_block(func.def.body); - self.interner.push_expr_location(expr_id, func.def.span, self.file); - HirFunction::unchecked_from_expr(expr_id) - } - }; - - (hir_func, func_meta) - } - - pub fn resolve_trait_constraint( - &mut self, - constraint: UnresolvedTraitConstraint, - ) -> Option { - let typ = self.resolve_type(constraint.typ); - let trait_generics = - vecmap(constraint.trait_bound.trait_generics, |typ| self.resolve_type(typ)); - - let span = constraint.trait_bound.trait_path.span(); - let the_trait = self.lookup_trait_or_error(constraint.trait_bound.trait_path)?; - let trait_id = the_trait.id; - - let expected_generics = the_trait.generics.len(); - let actual_generics = trait_generics.len(); - - if actual_generics != expected_generics { - let item_name = the_trait.name.to_string(); - self.push_err(ResolverError::IncorrectGenericCount { - span, - item_name, - actual: actual_generics, - expected: expected_generics, - }); - } - - Some(TraitConstraint { typ, trait_id, trait_generics }) - } - - /// Translates an UnresolvedType into a Type and appends any - /// freshly created TypeVariables created to new_variables. - fn resolve_type_inner(&mut self, typ: UnresolvedType) -> Type { - use crate::ast::UnresolvedTypeData::*; - - let resolved_type = match typ.typ { - FieldElement => Type::FieldElement, - Array(size, elem) => { - let elem = Box::new(self.resolve_type_inner(*elem)); - let size = self.convert_expression_type(size); - Type::Array(Box::new(size), elem) - } - Slice(elem) => { - let elem = Box::new(self.resolve_type_inner(*elem)); - Type::Slice(elem) - } - Expression(expr) => self.convert_expression_type(expr), - Integer(sign, bits) => Type::Integer(sign, bits), - Bool => Type::Bool, - String(size) => { - let resolved_size = self.convert_expression_type(size); - Type::String(Box::new(resolved_size)) - } - FormatString(size, fields) => { - let resolved_size = self.convert_expression_type(size); - let fields = self.resolve_type_inner(*fields); - Type::FmtString(Box::new(resolved_size), Box::new(fields)) - } - Quoted(quoted) => Type::Quoted(quoted), - Unit => Type::Unit, - Unspecified => Type::Error, - Error => Type::Error, - Named(path, args, _) => self.resolve_named_type(path, args), - TraitAsType(path, args) => self.resolve_trait_as_type(path, args), - - Tuple(fields) => Type::Tuple(vecmap(fields, |field| self.resolve_type_inner(field))), - Function(args, ret, env) => { - let args = vecmap(args, |arg| self.resolve_type_inner(arg)); - let ret = Box::new(self.resolve_type_inner(*ret)); - - // expect() here is valid, because the only places we don't have a span are omitted types - // e.g. a function without return type implicitly has a spanless UnresolvedType::Unit return type - // To get an invalid env type, the user must explicitly specify the type, which will have a span - let env_span = - env.span.expect("Unexpected missing span for closure environment type"); - - let env = Box::new(self.resolve_type_inner(*env)); - - match *env { - Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _, _) => { - Type::Function(args, ret, env) - } - _ => { - self.push_err(ResolverError::InvalidClosureEnvironment { - typ: *env, - span: env_span, - }); - Type::Error - } - } - } - MutableReference(element) => { - Type::MutableReference(Box::new(self.resolve_type_inner(*element))) - } - Parenthesized(typ) => self.resolve_type_inner(*typ), - Resolved(id) => self.interner.get_quoted_type(id).clone(), - }; - - if let Type::Struct(_, _) = resolved_type { - if let Some(unresolved_span) = typ.span { - // Record the location of the type reference - self.interner.push_type_ref_location( - resolved_type.clone(), - Location::new(unresolved_span, self.file), - ); - } - } - resolved_type - } - - fn find_generic(&self, target_name: &str) -> Option<&ResolvedGeneric> { - self.generics.iter().find(|generic| generic.name.as_ref() == target_name) - } - - fn resolve_named_type(&mut self, path: Path, args: Vec) -> Type { - if args.is_empty() { - if let Some(typ) = self.lookup_generic_or_global_type(&path) { - return typ; - } - } - - // Check if the path is a type variable first. We currently disallow generics on type - // variables since we do not support higher-kinded types. - if path.segments.len() == 1 { - let name = &path.last_segment().0.contents; - - if name == SELF_TYPE_NAME { - if let Some(self_type) = self.self_type.clone() { - if !args.is_empty() { - self.push_err(ResolverError::GenericsOnSelfType { span: path.span() }); - } - return self_type; - } - } - } - - let span = path.span(); - let mut args = vecmap(args, |arg| self.resolve_type_inner(arg)); - - if let Some(type_alias) = self.lookup_type_alias(path.clone()) { - let type_alias = type_alias.borrow(); - let expected_generic_count = type_alias.generics.len(); - let type_alias_string = type_alias.to_string(); - let id = type_alias.id; - - self.verify_generics_count(expected_generic_count, &mut args, span, || { - type_alias_string - }); - - if let Some(item) = self.current_item { - self.interner.add_type_alias_dependency(item, id); - } - - // Collecting Type Alias references [Location]s to be used by LSP in order - // to resolve the definition of the type alias - self.interner.add_type_alias_ref(id, Location::new(span, self.file)); - - // Because there is no ordering to when type aliases (and other globals) are resolved, - // it is possible for one to refer to an Error type and issue no error if it is set - // equal to another type alias. Fixing this fully requires an analysis to create a DFG - // of definition ordering, but for now we have an explicit check here so that we at - // least issue an error that the type was not found instead of silently passing. - let alias = self.interner.get_type_alias(id); - return Type::Alias(alias, args); - } - - match self.lookup_struct_or_error(path) { - Some(struct_type) => { - if self.resolving_ids.contains(&struct_type.borrow().id) { - self.push_err(ResolverError::SelfReferentialStruct { - span: struct_type.borrow().name.span(), - }); - - return Type::Error; - } - - let expected_generic_count = struct_type.borrow().generics.len(); - if !self.in_contract - && self - .interner - .struct_attributes(&struct_type.borrow().id) - .iter() - .any(|attr| matches!(attr, SecondaryAttribute::Abi(_))) - { - self.push_err(ResolverError::AbiAttributeOutsideContract { - span: struct_type.borrow().name.span(), - }); - } - self.verify_generics_count(expected_generic_count, &mut args, span, || { - struct_type.borrow().to_string() - }); - - if let Some(current_item) = self.current_item { - let dependency_id = struct_type.borrow().id; - self.interner.add_type_dependency(current_item, dependency_id); - } - - Type::Struct(struct_type, args) - } - None => Type::Error, - } - } - - fn resolve_trait_as_type(&mut self, path: Path, args: Vec) -> Type { - let args = vecmap(args, |arg| self.resolve_type_inner(arg)); - - if let Some(t) = self.lookup_trait_or_error(path) { - Type::TraitAsType(t.id, Rc::new(t.name.to_string()), args) - } else { - Type::Error - } - } - - fn verify_generics_count( - &mut self, - expected_count: usize, - args: &mut Vec, - span: Span, - type_name: impl FnOnce() -> String, - ) { - if args.len() != expected_count { - self.errors.push(ResolverError::IncorrectGenericCount { - span, - item_name: type_name(), - actual: args.len(), - expected: expected_count, - }); - - // Fix the generic count so we can continue typechecking - args.resize_with(expected_count, || Type::Error); - } - } - - fn lookup_generic_or_global_type(&mut self, path: &Path) -> Option { - if path.segments.len() == 1 { - let name = &path.last_segment().0.contents; - if let Some(generic) = self.find_generic(name) { - // We always insert a `TypeKind::Normal` as we do not support explicit numeric generics - // in the resolver - return Some(Type::NamedGeneric( - generic.type_var.clone(), - generic.name.clone(), - Kind::Normal, - )); - }; - } - - // If we cannot find a local generic of the same name, try to look up a global - match self.path_resolver.resolve(self.def_maps, path.clone(), &mut None) { - Ok(PathResolution { module_def_id: ModuleDefId::GlobalId(id), error }) => { - if let Some(current_item) = self.current_item { - self.interner.add_global_dependency(current_item, id); - } - - if let Some(error) = error { - self.push_err(error.into()); - } - Some(Type::Constant(self.eval_global_as_array_length(id, path))) - } - _ => None, - } - } - - fn convert_expression_type(&mut self, length: UnresolvedTypeExpression) -> Type { - match length { - UnresolvedTypeExpression::Variable(path) => { - self.lookup_generic_or_global_type(&path).unwrap_or_else(|| { - self.push_err(ResolverError::NoSuchNumericTypeVariable { path }); - Type::Constant(0) - }) - } - UnresolvedTypeExpression::Constant(int, _) => Type::Constant(int), - UnresolvedTypeExpression::BinaryOperation(lhs, op, rhs, _) => { - let (lhs_span, rhs_span) = (lhs.span(), rhs.span()); - let lhs = self.convert_expression_type(*lhs); - let rhs = self.convert_expression_type(*rhs); - - match (lhs, rhs) { - (Type::Constant(lhs), Type::Constant(rhs)) => { - Type::Constant(op.function()(lhs, rhs)) - } - (lhs, _) => { - let span = - if !matches!(lhs, Type::Constant(_)) { lhs_span } else { rhs_span }; - self.push_err(ResolverError::InvalidArrayLengthExpr { span }); - Type::Constant(0) - } - } - } - } - } - - fn get_ident_from_path(&mut self, path: Path) -> (HirIdent, usize) { - let location = Location::new(path.span(), self.file); - - let error = match path.as_ident().map(|ident| self.find_variable(ident)) { - Some(Ok(found)) => return found, - // Try to look it up as a global, but still issue the first error if we fail - Some(Err(error)) => match self.lookup_global(path) { - Ok(id) => return (HirIdent::non_trait_method(id, location), 0), - Err(_) => error, - }, - None => match self.lookup_global(path) { - Ok(id) => return (HirIdent::non_trait_method(id, location), 0), - Err(error) => error, - }, - }; - self.push_err(error); - let id = DefinitionId::dummy_id(); - (HirIdent::non_trait_method(id, location), 0) - } - - /// Translates an UnresolvedType to a Type - pub fn resolve_type(&mut self, typ: UnresolvedType) -> Type { - let span = typ.span; - let resolved_type = self.resolve_type_inner(typ); - if resolved_type.is_nested_slice() { - self.errors.push(ResolverError::NestedSlices { span: span.unwrap() }); - } - resolved_type - } - - pub fn resolve_type_alias( - mut self, - unresolved: NoirTypeAlias, - alias_id: TypeAliasId, - ) -> (Type, Generics, Vec) { - let generics = self.add_generics(&unresolved.generics); - self.resolve_local_globals(); - - self.current_item = Some(DependencyId::Alias(alias_id)); - let typ = self.resolve_type(unresolved.typ); - - (typ, generics, self.errors) - } - - pub fn take_errors(self) -> Vec { - self.errors - } - - /// Return the current generics. - /// Needed to keep referring to the same type variables across many - /// methods in a single impl. - pub fn get_generics(&self) -> &[ResolvedGeneric] { - &self.generics - } - - /// Set the current generics that are in scope. - /// Unlike add_generics, this function will not create any new type variables, - /// opting to reuse the existing ones it is directly given. - pub fn set_generics(&mut self, generics: Vec) { - self.generics = generics; - } - - /// Translates a (possibly Unspecified) UnresolvedType to a Type. - /// Any UnresolvedType::Unspecified encountered are replaced with fresh type variables. - fn resolve_inferred_type(&mut self, typ: UnresolvedType) -> Type { - match &typ.typ { - UnresolvedTypeData::Unspecified => self.interner.next_type_variable(), - _ => self.resolve_type(typ), - } - } - - /// Add the given generics to scope. - /// Each generic will have a fresh Shared associated with it. - pub fn add_generics(&mut self, generics: &UnresolvedGenerics) -> Generics { - vecmap(generics, |generic| { - // Map the generic to a fresh type variable - let id = self.interner.next_type_variable_id(); - let typevar = TypeVariable::unbound(id); - let ident = generic.ident(); - let span = ident.0.span(); - - // Check for name collisions of this generic - let name = Rc::new(ident.0.contents.clone()); - - let resolved_generic = ResolvedGeneric { - name: name.clone(), - type_var: typevar, - // We only support numeric generics in the elaborator - kind: Kind::Normal, - span, - }; - if let Some(generic) = self.find_generic(&name) { - self.errors.push(ResolverError::DuplicateDefinition { - name: ident.0.contents.clone(), - first_span: generic.span, - second_span: span, - }); - } else { - self.generics.push(resolved_generic.clone()); - } - - resolved_generic - }) - } - - /// Add the given existing generics to scope. - /// This is useful for adding the same generics to many items. E.g. apply impl generics - /// to each function in the impl or trait generics to each item in the trait. - pub fn add_existing_generics( - &mut self, - unresolved_generics: &UnresolvedGenerics, - generics: &GenericTypeVars, - ) { - assert_eq!(unresolved_generics.len(), generics.len()); - - for (unresolved_generic, typevar) in unresolved_generics.iter().zip(generics) { - self.add_existing_generic( - unresolved_generic, - unresolved_generic.span(), - typevar.clone(), - ); - } - } - - pub fn add_existing_generic( - &mut self, - unresolved_generic: &UnresolvedGeneric, - span: Span, - typevar: TypeVariable, - ) { - let name = &unresolved_generic.ident().0.contents; - - // Check for name collisions of this generic - let rc_name = Rc::new(name.clone()); - - if let Some(generic) = self.find_generic(&rc_name) { - self.errors.push(ResolverError::DuplicateDefinition { - name: name.clone(), - first_span: generic.span, - second_span: span, - }); - } else { - let resolved_generic = ResolvedGeneric { - name: rc_name, - type_var: typevar.clone(), - kind: unresolved_generic - .kind() - .expect("ICE: Deprecated code should only support normal kinds"), - span, - }; - self.generics.push(resolved_generic); - } - } - - pub fn resolve_struct_fields( - mut self, - unresolved: NoirStruct, - struct_id: StructId, - ) -> (Generics, Vec<(Ident, Type)>, Vec) { - let generics = self.add_generics(&unresolved.generics); - - // Check whether the struct definition has globals in the local module and add them to the scope - self.resolve_local_globals(); - - self.current_item = Some(DependencyId::Struct(struct_id)); - - self.resolving_ids.insert(struct_id); - let fields = vecmap(unresolved.fields, |(ident, typ)| (ident, self.resolve_type(typ))); - self.resolving_ids.remove(&struct_id); - - (generics, fields, self.errors) - } - - fn resolve_local_globals(&mut self) { - let globals = vecmap(self.interner.get_all_globals(), |global| { - (global.id, global.local_id, global.ident.clone()) - }); - for (id, local_module_id, name) in globals { - if local_module_id == self.path_resolver.local_module_id() { - let definition = DefinitionKind::Global(id); - self.add_global_variable_decl(name, definition); - } - } - } - - /// TODO: This is currently only respected for generic free functions - /// there's a bunch of other places where trait constraints can pop up - fn resolve_trait_constraints( - &mut self, - where_clause: &[UnresolvedTraitConstraint], - ) -> Vec { - where_clause - .iter() - .cloned() - .filter_map(|constraint| self.resolve_trait_constraint(constraint)) - .collect() - } - - /// Extract metadata from a NoirFunction - /// to be used in analysis and intern the function parameters - /// Prerequisite: self.add_generics() has already been called with the given - /// function's generics, including any generics from the impl, if any. - fn extract_meta(&mut self, func: &NoirFunction, func_id: FuncId) -> FuncMeta { - let location = Location::new(func.name_ident().span(), self.file); - let id = self.interner.function_definition_id(func_id); - let name_ident = HirIdent::non_trait_method(id, location); - - let attributes = func.attributes().clone(); - let has_no_predicates_attribute = attributes.is_no_predicates(); - let should_fold = attributes.is_foldable(); - if !self.inline_attribute_allowed(func) { - if has_no_predicates_attribute { - self.push_err(ResolverError::NoPredicatesAttributeOnUnconstrained { - ident: func.name_ident().clone(), - }); - } else if should_fold { - self.push_err(ResolverError::FoldAttributeOnUnconstrained { - ident: func.name_ident().clone(), - }); - } - } - // Both the #[fold] and #[no_predicates] alter a function's inline type and code generation in similar ways. - // In certain cases such as type checking (for which the following flag will be used) both attributes - // indicate we should code generate in the same way. Thus, we unify the attributes into one flag here. - let has_inline_attribute = has_no_predicates_attribute || should_fold; - - let generics = vecmap(&self.generics, |generic| generic.type_var.clone()); - let mut parameters = vec![]; - let mut parameter_types = vec![]; - - for Param { visibility, pattern, typ, span: _ } in func.parameters().iter().cloned() { - if visibility == Visibility::Public && !self.pub_allowed(func) { - self.push_err(ResolverError::UnnecessaryPub { - ident: func.name_ident().clone(), - position: PubPosition::Parameter, - }); - } - - let pattern = self.resolve_pattern(pattern, DefinitionKind::Local(None)); - let typ = self.resolve_type_inner(typ); - - parameters.push((pattern, typ.clone(), visibility)); - parameter_types.push(typ); - } - - let return_type = Box::new(self.resolve_type(func.return_type())); - - self.declare_numeric_generics(¶meter_types, &return_type); - - if !self.pub_allowed(func) && func.def.return_visibility == Visibility::Public { - self.push_err(ResolverError::UnnecessaryPub { - ident: func.name_ident().clone(), - position: PubPosition::ReturnType, - }); - } - let is_low_level_function = - attributes.function.as_ref().map_or(false, |func| func.is_low_level()); - if !self.path_resolver.module_id().krate.is_stdlib() && is_low_level_function { - let error = - ResolverError::LowLevelFunctionOutsideOfStdlib { ident: func.name_ident().clone() }; - self.push_err(error); - } - - // 'pub' is required on return types for entry point functions - if self.is_entry_point_function(func) - && return_type.as_ref() != &Type::Unit - && func.def.return_visibility == Visibility::Private - { - self.push_err(ResolverError::NecessaryPub { ident: func.name_ident().clone() }); - } - // '#[recursive]' attribute is only allowed for entry point functions - if !self.is_entry_point_function(func) && func.kind == FunctionKind::Recursive { - self.push_err(ResolverError::MisplacedRecursiveAttribute { - ident: func.name_ident().clone(), - }); - } - - let mut typ = Type::Function(parameter_types, return_type, Box::new(Type::Unit)); - - if !generics.is_empty() { - typ = Type::Forall(generics, Box::new(typ)); - } - - self.interner.push_definition_type(name_ident.id, typ.clone()); - - let direct_generics = func.def.generics.iter(); - let direct_generics = direct_generics - .filter_map(|generic| self.find_generic(&generic.ident().0.contents).cloned()) - .collect(); - - FuncMeta { - name: name_ident, - kind: func.kind, - location, - typ, - direct_generics, - struct_id: None, - trait_impl: self.current_trait_impl, - parameters: parameters.into(), - return_type: func.def.return_type.clone(), - return_visibility: func.def.return_visibility, - has_body: !func.def.body.is_empty(), - trait_constraints: self.resolve_trait_constraints(&func.def.where_clause), - is_entry_point: self.is_entry_point_function(func), - has_inline_attribute, - source_crate: self.path_resolver.module_id().krate, - - // These fields are only used by the elaborator - all_generics: Vec::new(), - is_trait_function: false, - parameter_idents: Vec::new(), - function_body: FunctionBody::Resolved, - } - } - - /// True if the 'pub' keyword is allowed on parameters in this function - /// 'pub' on function parameters is only allowed for entry point functions - fn pub_allowed(&self, func: &NoirFunction) -> bool { - self.is_entry_point_function(func) || func.attributes().is_foldable() - } - - fn is_entry_point_function(&self, func: &NoirFunction) -> bool { - if self.in_contract { - func.attributes().is_contract_entry_point() - } else { - func.name() == MAIN_FUNCTION - } - } - - fn inline_attribute_allowed(&self, func: &NoirFunction) -> bool { - // Inline attributes are only relevant for constrained functions - // as all unconstrained functions are not inlined - !func.def.is_unconstrained - } - - // TODO(https://github.com/noir-lang/noir/issues/5156): Remove this method in favor of explicit numeric generics - fn declare_numeric_generics(&mut self, params: &[Type], return_type: &Type) { - if self.generics.is_empty() { - return; - } - - for (name_to_find, type_variable) in Self::find_numeric_generics(params, return_type) { - // Declare any generics to let users use numeric generics in scope. - // Don't issue a warning if these are unused - // - // We can fail to find the generic in self.generics if it is an implicit one created - // by the compiler. This can happen when, e.g. eliding array lengths using the slice - // syntax [T]. - if let Some(ResolvedGeneric { name, span, .. }) = - self.generics.iter().find(|generic| generic.name.as_ref() == &name_to_find) - { - let ident = Ident::new(name.to_string(), *span); - let definition = DefinitionKind::GenericType(type_variable); - self.add_variable_decl_inner(ident.clone(), false, false, false, definition); - } - } - } - - fn find_numeric_generics( - parameters: &[Type], - return_type: &Type, - ) -> Vec<(String, TypeVariable)> { - let mut found = BTreeMap::new(); - for parameter in parameters { - Self::find_numeric_generics_in_type(parameter, &mut found); - } - Self::find_numeric_generics_in_type(return_type, &mut found); - found.into_iter().collect() - } - - fn find_numeric_generics_in_type(typ: &Type, found: &mut BTreeMap) { - match typ { - Type::FieldElement - | Type::Integer(_, _) - | Type::Bool - | Type::Unit - | Type::Error - | Type::TypeVariable(_, _) - | Type::Constant(_) - | Type::NamedGeneric(_, _, _) - | Type::Quoted(_) - | Type::Forall(_, _) => (), - - Type::TraitAsType(_, _, args) => { - for arg in args { - Self::find_numeric_generics_in_type(arg, found); - } - } - - Type::Array(length, element_type) => { - if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { - found.insert(name.to_string(), type_variable.clone()); - } - Self::find_numeric_generics_in_type(element_type, found); - } - - Type::Slice(element_type) => { - Self::find_numeric_generics_in_type(element_type, found); - } - - Type::Tuple(fields) => { - for field in fields { - Self::find_numeric_generics_in_type(field, found); - } - } - - Type::Function(parameters, return_type, _env) => { - for parameter in parameters { - Self::find_numeric_generics_in_type(parameter, found); - } - Self::find_numeric_generics_in_type(return_type, found); - } - - Type::Struct(struct_type, generics) => { - for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name, _) = generic { - if struct_type.borrow().generic_is_numeric(i) { - found.insert(name.to_string(), type_variable.clone()); - } - } else { - Self::find_numeric_generics_in_type(generic, found); - } - } - } - Type::Alias(alias, generics) => { - for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name, _) = generic { - if alias.borrow().generic_is_numeric(i) { - found.insert(name.to_string(), type_variable.clone()); - } - } else { - Self::find_numeric_generics_in_type(generic, found); - } - } - } - Type::MutableReference(element) => Self::find_numeric_generics_in_type(element, found), - Type::String(length) => { - if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { - found.insert(name.to_string(), type_variable.clone()); - } - } - Type::FmtString(length, fields) => { - if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { - found.insert(name.to_string(), type_variable.clone()); - } - Self::find_numeric_generics_in_type(fields, found); - } - } - } - - pub fn resolve_global_let( - &mut self, - let_stmt: LetStatement, - global_id: GlobalId, - ) -> HirStatement { - self.current_item = Some(DependencyId::Global(global_id)); - let expression = self.resolve_expression(let_stmt.expression); - let definition = DefinitionKind::Global(global_id); - - if !self.in_contract - && let_stmt.attributes.iter().any(|attr| matches!(attr, SecondaryAttribute::Abi(_))) - { - let span = let_stmt.pattern.span(); - self.push_err(ResolverError::AbiAttributeOutsideContract { span }); - } - - if !let_stmt.comptime && matches!(let_stmt.pattern, Pattern::Mutable(..)) { - let span = let_stmt.pattern.span(); - self.push_err(ResolverError::MutableGlobal { span }); - } - - HirStatement::Let(HirLetStatement { - pattern: self.resolve_pattern(let_stmt.pattern, definition), - r#type: self.resolve_type(let_stmt.r#type), - expression, - attributes: let_stmt.attributes, - comptime: let_stmt.comptime, - }) - } - - pub fn resolve_stmt(&mut self, stmt: StatementKind, span: Span) -> HirStatement { - match stmt { - StatementKind::Let(let_stmt) => { - let expression = self.resolve_expression(let_stmt.expression); - let definition = DefinitionKind::Local(Some(expression)); - HirStatement::Let(HirLetStatement { - pattern: self.resolve_pattern(let_stmt.pattern, definition), - r#type: self.resolve_type(let_stmt.r#type), - expression, - attributes: let_stmt.attributes, - comptime: let_stmt.comptime, - }) - } - StatementKind::Constrain(constrain_stmt) => { - let expr_id = self.resolve_expression(constrain_stmt.0); - let assert_message_expr_id = - constrain_stmt.1.map(|assert_expr_id| self.resolve_expression(assert_expr_id)); - - HirStatement::Constrain(HirConstrainStatement( - expr_id, - self.file, - assert_message_expr_id, - )) - } - StatementKind::Expression(expr) => { - HirStatement::Expression(self.resolve_expression(expr)) - } - StatementKind::Semi(expr) => HirStatement::Semi(self.resolve_expression(expr)), - StatementKind::Assign(assign_stmt) => { - let identifier = self.resolve_lvalue(assign_stmt.lvalue); - let expression = self.resolve_expression(assign_stmt.expression); - let stmt = HirAssignStatement { lvalue: identifier, expression }; - HirStatement::Assign(stmt) - } - StatementKind::For(for_loop) => { - match for_loop.range { - ForRange::Range(start_range, end_range) => { - let start_range = self.resolve_expression(start_range); - let end_range = self.resolve_expression(end_range); - let (identifier, block) = (for_loop.identifier, for_loop.block); - - self.nested_loops += 1; - - // TODO: For loop variables are currently mutable by default since we haven't - // yet implemented syntax for them to be optionally mutable. - let (identifier, block) = self.in_new_scope(|this| { - let decl = this.add_variable_decl( - identifier, - false, - true, - DefinitionKind::Local(None), - ); - (decl, this.resolve_expression(block)) - }); - - self.nested_loops -= 1; - - HirStatement::For(HirForStatement { - start_range, - end_range, - block, - identifier, - }) - } - range @ ForRange::Array(_) => { - let for_stmt = - range.into_for(for_loop.identifier, for_loop.block, for_loop.span); - self.resolve_stmt(for_stmt.kind, for_loop.span) - } - } - } - StatementKind::Break => { - self.check_break_continue(true, span); - HirStatement::Break - } - StatementKind::Continue => { - self.check_break_continue(false, span); - HirStatement::Continue - } - StatementKind::Error => HirStatement::Error, - StatementKind::Comptime(statement) => { - let hir_statement = self.resolve_stmt(statement.kind, statement.span); - let statement_id = self.interner.push_stmt(hir_statement); - self.interner.push_stmt_location(statement_id, statement.span, self.file); - HirStatement::Comptime(statement_id) - } - } - } - - pub fn intern_stmt(&mut self, stmt: Statement) -> StmtId { - let hir_stmt = self.resolve_stmt(stmt.kind, stmt.span); - let id = self.interner.push_stmt(hir_stmt); - self.interner.push_stmt_location(id, stmt.span, self.file); - id - } - - fn resolve_lvalue(&mut self, lvalue: LValue) -> HirLValue { - match lvalue { - LValue::Ident(ident) => { - let ident = self.find_variable_or_default(&ident); - self.resolve_local_variable(ident.0.clone(), ident.1); - - HirLValue::Ident(ident.0, Type::Error) - } - LValue::MemberAccess { object, field_name, span } => HirLValue::MemberAccess { - object: Box::new(self.resolve_lvalue(*object)), - field_name, - location: Location::new(span, self.file), - field_index: None, - typ: Type::Error, - }, - LValue::Index { array, index, span } => { - let array = Box::new(self.resolve_lvalue(*array)); - let index = self.resolve_expression(index); - let location = Location::new(span, self.file); - HirLValue::Index { array, index, location, typ: Type::Error } - } - LValue::Dereference(lvalue, span) => { - let lvalue = Box::new(self.resolve_lvalue(*lvalue)); - let location = Location::new(span, self.file); - HirLValue::Dereference { lvalue, location, element_type: Type::Error } - } - } - } - - fn resolve_local_variable(&mut self, hir_ident: HirIdent, var_scope_index: usize) { - let mut transitive_capture_index: Option = None; - - for lambda_index in 0..self.lambda_stack.len() { - if self.lambda_stack[lambda_index].scope_index > var_scope_index { - // Beware: the same variable may be captured multiple times, so we check - // for its presence before adding the capture below. - let pos = self.lambda_stack[lambda_index] - .captures - .iter() - .position(|capture| capture.ident.id == hir_ident.id); - - if pos.is_none() { - self.lambda_stack[lambda_index].captures.push(HirCapturedVar { - ident: hir_ident.clone(), - transitive_capture_index, - }); - } - - if lambda_index + 1 < self.lambda_stack.len() { - // There is more than one closure between the current scope and - // the scope of the variable, so this is a propagated capture. - // We need to track the transitive capture index as we go up in - // the closure stack. - transitive_capture_index = Some(pos.unwrap_or( - // If this was a fresh capture, we added it to the end of - // the captures vector: - self.lambda_stack[lambda_index].captures.len() - 1, - )); - } - } - } - } - - fn resolve_array_literal(&mut self, array_literal: ArrayLiteral) -> HirArrayLiteral { - match array_literal { - ArrayLiteral::Standard(elements) => { - let elements = vecmap(elements, |elem| self.resolve_expression(elem)); - HirArrayLiteral::Standard(elements) - } - ArrayLiteral::Repeated { repeated_element, length } => { - let span = length.span; - let length = - UnresolvedTypeExpression::from_expr(*length, span).unwrap_or_else(|error| { - self.errors.push(ResolverError::ParserError(Box::new(error))); - UnresolvedTypeExpression::Constant(0, span) - }); - - let length = self.convert_expression_type(length); - let repeated_element = self.resolve_expression(*repeated_element); - - HirArrayLiteral::Repeated { repeated_element, length } - } - } - } - - pub fn resolve_expression(&mut self, expr: Expression) -> ExprId { - let hir_expr = match expr.kind { - ExpressionKind::Literal(literal) => HirExpression::Literal(match literal { - Literal::Bool(b) => HirLiteral::Bool(b), - Literal::Array(array_literal) => { - HirLiteral::Array(self.resolve_array_literal(array_literal)) - } - Literal::Slice(array_literal) => { - HirLiteral::Slice(self.resolve_array_literal(array_literal)) - } - Literal::Integer(integer, sign) => HirLiteral::Integer(integer, sign), - Literal::Str(str) => HirLiteral::Str(str), - Literal::RawStr(str, _) => HirLiteral::Str(str), - Literal::FmtStr(str) => self.resolve_fmt_str_literal(str, expr.span), - Literal::Unit => HirLiteral::Unit, - }), - ExpressionKind::Variable(path, generics) => { - let generics = - generics.map(|generics| vecmap(generics, |typ| self.resolve_type(typ))); - - if let Some((method, constraint, assumed)) = self.resolve_trait_generic_path(&path) - { - HirExpression::Ident( - HirIdent { - location: Location::new(expr.span, self.file), - id: self.interner.trait_method_id(method), - impl_kind: ImplKind::TraitMethod(method, constraint, assumed), - }, - generics, - ) - } else { - // If the Path is being used as an Expression, then it is referring to a global from a separate module - // Otherwise, then it is referring to an Identifier - // This lookup allows support of such statements: let x = foo::bar::SOME_GLOBAL + 10; - // If the expression is a singular indent, we search the resolver's current scope as normal. - let (hir_ident, var_scope_index) = self.get_ident_from_path(path.clone()); - - if hir_ident.id != DefinitionId::dummy_id() { - match self.interner.definition(hir_ident.id).kind { - DefinitionKind::Function(id) => { - if let Some(current_item) = self.current_item { - self.interner.add_function_dependency(current_item, id); - } - } - DefinitionKind::Global(global_id) => { - if let Some(current_item) = self.current_item { - self.interner.add_global_dependency(current_item, global_id); - } - } - DefinitionKind::GenericType(_) => { - // Initialize numeric generics to a polymorphic integer type in case - // they're used in expressions. We must do this here since the type - // checker does not check definition kinds and otherwise expects - // parameters to already be typed. - if self.interner.definition_type(hir_ident.id) == Type::Error { - let typ = Type::polymorphic_integer_or_field(self.interner); - self.interner.push_definition_type(hir_ident.id, typ); - } - } - DefinitionKind::Local(_) => { - // only local variables can be captured by closures. - self.resolve_local_variable(hir_ident.clone(), var_scope_index); - } - } - } - - HirExpression::Ident(hir_ident, generics) - } - } - ExpressionKind::Prefix(prefix) => { - let operator = prefix.operator; - let rhs = self.resolve_expression(prefix.rhs); - let trait_method_id = self.interner.get_prefix_operator_trait_method(&operator); - - if operator == UnaryOp::MutableReference { - if let Err(error) = verify_mutable_reference(self.interner, rhs) { - self.errors.push(error); - } - } - - HirExpression::Prefix(HirPrefixExpression { operator, rhs, trait_method_id }) - } - ExpressionKind::Infix(infix) => { - let lhs = self.resolve_expression(infix.lhs); - let rhs = self.resolve_expression(infix.rhs); - let trait_id = self.interner.get_operator_trait_method(infix.operator.contents); - - HirExpression::Infix(HirInfixExpression { - lhs, - operator: HirBinaryOp::new(infix.operator, self.file), - trait_method_id: trait_id, - rhs, - }) - } - ExpressionKind::Call(call_expr) => { - // Get the span and name of path for error reporting - let func = self.resolve_expression(*call_expr.func); - - let arguments = vecmap(call_expr.arguments, |arg| self.resolve_expression(arg)); - let location = Location::new(expr.span, self.file); - HirExpression::Call(HirCallExpression { func, arguments, location }) - } - ExpressionKind::MethodCall(call_expr) => { - let method = call_expr.method_name; - let object = self.resolve_expression(call_expr.object); - - // Cannot verify the generic count here equals the expected count since we don't - // know which definition `method` refers to until it is resolved during type checking. - let generics = call_expr - .generics - .map(|generics| vecmap(generics, |typ| self.resolve_type(typ))); - - let arguments = vecmap(call_expr.arguments, |arg| self.resolve_expression(arg)); - let location = Location::new(expr.span, self.file); - HirExpression::MethodCall(HirMethodCallExpression { - method, - object, - generics, - arguments, - location, - }) - } - ExpressionKind::Cast(cast_expr) => HirExpression::Cast(HirCastExpression { - lhs: self.resolve_expression(cast_expr.lhs), - r#type: self.resolve_type(cast_expr.r#type), - }), - ExpressionKind::If(if_expr) => HirExpression::If(HirIfExpression { - condition: self.resolve_expression(if_expr.condition), - consequence: self.resolve_expression(if_expr.consequence), - alternative: if_expr.alternative.map(|e| self.resolve_expression(e)), - }), - ExpressionKind::Index(indexed_expr) => HirExpression::Index(HirIndexExpression { - collection: self.resolve_expression(indexed_expr.collection), - index: self.resolve_expression(indexed_expr.index), - }), - ExpressionKind::Block(block_expr) => { - HirExpression::Block(self.resolve_block(block_expr)) - } - ExpressionKind::Constructor(constructor) => { - let span = constructor.type_name.span(); - - match self.lookup_type_or_error(constructor.type_name) { - Some(Type::Struct(r#type, struct_generics)) => { - let typ = r#type.clone(); - let fields = constructor.fields; - let resolve_expr = Resolver::resolve_expression; - let fields = - self.resolve_constructor_fields(typ, fields, span, resolve_expr); - HirExpression::Constructor(HirConstructorExpression { - fields, - r#type, - struct_generics, - }) - } - Some(typ) => { - self.push_err(ResolverError::NonStructUsedInConstructor { typ, span }); - HirExpression::Error - } - None => HirExpression::Error, - } - } - ExpressionKind::MemberAccess(access) => { - // Validating whether the lhs actually has the rhs as a field - // needs to wait until type checking when we know the type of the lhs - HirExpression::MemberAccess(HirMemberAccess { - lhs: self.resolve_expression(access.lhs), - rhs: access.rhs, - // This is only used when lhs is a reference and we want to return a reference to rhs - is_offset: false, - }) - } - ExpressionKind::Error => HirExpression::Error, - ExpressionKind::Tuple(elements) => { - let elements = vecmap(elements, |elem| self.resolve_expression(elem)); - HirExpression::Tuple(elements) - } - // We must stay in the same function scope as the parent function to allow for closures - // to capture variables. This is currently limited to immutable variables. - ExpressionKind::Lambda(lambda) => self.in_new_scope(|this| { - let scope_index = this.scopes.current_scope_index(); - - this.lambda_stack.push(LambdaContext { captures: Vec::new(), scope_index }); - - let parameters = vecmap(lambda.parameters, |(pattern, typ)| { - let parameter = DefinitionKind::Local(None); - (this.resolve_pattern(pattern, parameter), this.resolve_inferred_type(typ)) - }); - - let return_type = this.resolve_inferred_type(lambda.return_type); - let body = this.resolve_expression(lambda.body); - - let lambda_context = this.lambda_stack.pop().unwrap(); - - HirExpression::Lambda(HirLambda { - parameters, - return_type, - body, - captures: lambda_context.captures, - }) - }), - ExpressionKind::Parenthesized(sub_expr) => return self.resolve_expression(*sub_expr), - - // The quoted expression isn't resolved since we don't want errors if variables aren't defined - ExpressionKind::Quote(block) => HirExpression::Quote(block), - ExpressionKind::Comptime(block, _) => { - HirExpression::Comptime(self.resolve_block(block)) - } - ExpressionKind::Resolved(_) => unreachable!( - "ExpressionKind::Resolved should only be emitted by the comptime interpreter" - ), - ExpressionKind::Unquote(_) => { - self.push_err(ResolverError::UnquoteUsedOutsideQuote { span: expr.span }); - HirExpression::Literal(HirLiteral::Unit) - } - }; - - // If these lines are ever changed, make sure to change the early return - // in the ExpressionKind::Variable case as well - let expr_id = self.interner.push_expr(hir_expr); - self.interner.push_expr_location(expr_id, expr.span, self.file); - expr_id - } - - fn resolve_pattern(&mut self, pattern: Pattern, definition: DefinitionKind) -> HirPattern { - self.resolve_pattern_mutable(pattern, None, definition) - } - - fn resolve_pattern_mutable( - &mut self, - pattern: Pattern, - mutable: Option, - definition: DefinitionKind, - ) -> HirPattern { - match pattern { - Pattern::Identifier(name) => { - // If this definition is mutable, do not store the rhs because it will - // not always refer to the correct value of the variable - let definition = match (mutable, definition) { - (Some(_), DefinitionKind::Local(_)) => DefinitionKind::Local(None), - (_, other) => other, - }; - let id = self.add_variable_decl(name, mutable.is_some(), true, definition); - HirPattern::Identifier(id) - } - Pattern::Mutable(pattern, span, _) => { - if let Some(first_mut) = mutable { - self.push_err(ResolverError::UnnecessaryMut { first_mut, second_mut: span }); - } - - let pattern = self.resolve_pattern_mutable(*pattern, Some(span), definition); - let location = Location::new(span, self.file); - HirPattern::Mutable(Box::new(pattern), location) - } - Pattern::Tuple(fields, span) => { - let fields = vecmap(fields, |field| { - self.resolve_pattern_mutable(field, mutable, definition.clone()) - }); - let location = Location::new(span, self.file); - HirPattern::Tuple(fields, location) - } - Pattern::Struct(name, fields, span) => { - let error_identifier = |this: &mut Self| { - // Must create a name here to return a HirPattern::Identifier. Allowing - // shadowing here lets us avoid further errors if we define ERROR_IDENT - // multiple times. - let name = ERROR_IDENT.into(); - let identifier = this.add_variable_decl(name, false, true, definition.clone()); - HirPattern::Identifier(identifier) - }; - - let (struct_type, generics) = match self.lookup_type_or_error(name) { - Some(Type::Struct(struct_type, generics)) => (struct_type, generics), - None => return error_identifier(self), - Some(typ) => { - self.push_err(ResolverError::NonStructUsedInConstructor { typ, span }); - return error_identifier(self); - } - }; - - let resolve_field = |this: &mut Self, pattern| { - this.resolve_pattern_mutable(pattern, mutable, definition.clone()) - }; - - let typ = struct_type.clone(); - let fields = self.resolve_constructor_fields(typ, fields, span, resolve_field); - - let typ = Type::Struct(struct_type, generics); - let location = Location::new(span, self.file); - HirPattern::Struct(typ, fields, location) - } - } - } - - /// Resolve all the fields of a struct constructor expression. - /// Ensures all fields are present, none are repeated, and all - /// are part of the struct. - /// - /// This is generic to allow it to work for constructor expressions - /// and constructor patterns. - fn resolve_constructor_fields( - &mut self, - struct_type: Shared, - fields: Vec<(Ident, T)>, - span: Span, - mut resolve_function: impl FnMut(&mut Self, T) -> U, - ) -> Vec<(Ident, U)> { - let mut ret = Vec::with_capacity(fields.len()); - let mut seen_fields = HashSet::new(); - let mut unseen_fields = struct_type.borrow().field_names(); - - for (field, expr) in fields { - let resolved = resolve_function(self, expr); - - if unseen_fields.contains(&field) { - unseen_fields.remove(&field); - seen_fields.insert(field.clone()); - } else if seen_fields.contains(&field) { - // duplicate field - self.push_err(ResolverError::DuplicateField { field: field.clone() }); - } else { - // field not required by struct - self.push_err(ResolverError::NoSuchField { - field: field.clone(), - struct_definition: struct_type.borrow().name.clone(), - }); - } - - ret.push((field, resolved)); - } - - if !unseen_fields.is_empty() { - self.push_err(ResolverError::MissingFields { - span, - missing_fields: unseen_fields.into_iter().map(|field| field.to_string()).collect(), - struct_definition: struct_type.borrow().name.clone(), - }); - } - - ret - } - - pub fn get_struct(&self, type_id: StructId) -> Shared { - self.interner.get_struct(type_id) - } - - pub fn get_trait_mut(&mut self, trait_id: TraitId) -> &mut Trait { - self.interner.get_trait_mut(trait_id) - } - - fn lookup(&mut self, path: Path) -> Result { - let span = path.span(); - let id = self.resolve_path(path)?; - T::try_from(id).ok_or_else(|| ResolverError::Expected { - expected: T::description(), - got: id.as_str().to_owned(), - span, - }) - } - - fn lookup_global(&mut self, path: Path) -> Result { - let span = path.span(); - let id = self.resolve_path(path)?; - - if let Some(function) = TryFromModuleDefId::try_from(id) { - return Ok(self.interner.function_definition_id(function)); - } - - if let Some(global) = TryFromModuleDefId::try_from(id) { - let global = self.interner.get_global(global); - return Ok(global.definition_id); - } - - let expected = "global variable".into(); - let got = "local variable".into(); - Err(ResolverError::Expected { span, expected, got }) - } - - /// Lookup a given struct type by name. - fn lookup_struct_or_error(&mut self, path: Path) -> Option> { - match self.lookup(path) { - Ok(struct_id) => Some(self.get_struct(struct_id)), - Err(error) => { - self.push_err(error); - None - } - } - } - - /// Lookup a given trait by name/path. - fn lookup_trait_or_error(&mut self, path: Path) -> Option<&mut Trait> { - match self.lookup(path) { - Ok(trait_id) => Some(self.get_trait_mut(trait_id)), - Err(error) => { - self.push_err(error); - None - } - } - } - - /// Looks up a given type by name. - /// This will also instantiate any struct types found. - fn lookup_type_or_error(&mut self, path: Path) -> Option { - let ident = path.as_ident(); - if ident.map_or(false, |i| i == SELF_TYPE_NAME) { - if let Some(typ) = &self.self_type { - return Some(typ.clone()); - } - } - - match self.lookup(path) { - Ok(struct_id) => { - let struct_type = self.get_struct(struct_id); - let generics = struct_type.borrow().instantiate(self.interner); - Some(Type::Struct(struct_type, generics)) - } - Err(error) => { - self.push_err(error); - None - } - } - } - - fn lookup_type_alias(&mut self, path: Path) -> Option> { - self.lookup(path).ok().map(|id| self.interner.get_type_alias(id)) - } - - // this resolves Self::some_static_method, inside an impl block (where we don't have a concrete self_type) - fn resolve_trait_static_method_by_self( - &mut self, - path: &Path, - ) -> Option<(TraitMethodId, TraitConstraint, bool)> { - let trait_id = self.trait_id?; - - if path.kind == PathKind::Plain && path.segments.len() == 2 { - let name = &path.segments[0].0.contents; - let method = &path.segments[1]; - - if name == SELF_TYPE_NAME { - let the_trait = self.interner.get_trait(trait_id); - let method = the_trait.find_method(method.0.contents.as_str())?; - - let constraint = TraitConstraint { - typ: self.self_type.clone()?, - trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { - generic.type_var.clone() - })), - trait_id, - }; - return Some((method, constraint, false)); - } - } - None - } - - // this resolves TraitName::some_static_method - fn resolve_trait_static_method( - &mut self, - path: &Path, - ) -> Option<(TraitMethodId, TraitConstraint, bool)> { - if path.kind == PathKind::Plain && path.segments.len() == 2 { - let method = &path.segments[1]; - - let mut trait_path = path.clone(); - trait_path.pop(); - let trait_id = self.lookup(trait_path).ok()?; - let the_trait = self.interner.get_trait(trait_id); - - let method = the_trait.find_method(method.0.contents.as_str())?; - let constraint = TraitConstraint { - typ: Type::TypeVariable( - the_trait.self_type_typevar.clone(), - TypeVariableKind::Normal, - ), - trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { - generic.type_var.clone() - })), - trait_id, - }; - return Some((method, constraint, false)); - } - None - } - - // This resolves a static trait method T::trait_method by iterating over the where clause - // - // Returns the trait method, trait constraint, and whether the impl is assumed from a where - // clause. This is always true since this helper searches where clauses for a generic constraint. - // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` - fn resolve_trait_method_by_named_generic( - &mut self, - path: &Path, - ) -> Option<(TraitMethodId, TraitConstraint, bool)> { - if path.segments.len() != 2 { - return None; - } - - for UnresolvedTraitConstraint { typ, trait_bound } in self.trait_bounds.clone() { - if let UnresolvedTypeData::Named(constraint_path, _, _) = &typ.typ { - // if `path` is `T::method_name`, we're looking for constraint of the form `T: SomeTrait` - if constraint_path.segments.len() == 1 - && path.segments[0] != constraint_path.last_segment() - { - continue; - } - - if let Ok(ModuleDefId::TraitId(trait_id)) = - self.resolve_path(trait_bound.trait_path.clone()) - { - let the_trait = self.interner.get_trait(trait_id); - if let Some(method) = - the_trait.find_method(path.segments.last().unwrap().0.contents.as_str()) - { - let constraint = TraitConstraint { - trait_id, - typ: self.resolve_type(typ.clone()), - trait_generics: vecmap(trait_bound.trait_generics, |typ| { - self.resolve_type(typ) - }), - }; - return Some((method, constraint, true)); - } - } - } - } - None - } - - // Try to resolve the given trait method path. - // - // Returns the trait method, trait constraint, and whether the impl is assumed to exist by a where clause or not - // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` - fn resolve_trait_generic_path( - &mut self, - path: &Path, - ) -> Option<(TraitMethodId, TraitConstraint, bool)> { - self.resolve_trait_static_method_by_self(path) - .or_else(|| self.resolve_trait_static_method(path)) - .or_else(|| self.resolve_trait_method_by_named_generic(path)) - } - - fn resolve_path(&mut self, path: Path) -> Result { - let path_resolution = self.path_resolver.resolve(self.def_maps, path, &mut None)?; - - if let Some(error) = path_resolution.error { - self.push_err(error.into()); - } - - Ok(path_resolution.module_def_id) - } - - fn resolve_block(&mut self, block_expr: BlockExpression) -> HirBlockExpression { - let statements = - self.in_new_scope(|this| vecmap(block_expr.statements, |stmt| this.intern_stmt(stmt))); - HirBlockExpression { statements } - } - - pub fn intern_block(&mut self, block: BlockExpression) -> ExprId { - let hir_block = HirExpression::Block(self.resolve_block(block)); - self.interner.push_expr(hir_block) - } - - fn eval_global_as_array_length(&mut self, global: GlobalId, path: &Path) -> u32 { - let Some(stmt) = self.interner.get_global_let_statement(global) else { - let path = path.clone(); - self.push_err(ResolverError::NoSuchNumericTypeVariable { path }); - return 0; - }; - - let length = stmt.expression; - let span = self.interner.expr_span(&length); - let result = self.try_eval_array_length_id(length, span); - - match result.map(|length| length.try_into()) { - Ok(Ok(length_value)) => return length_value, - Ok(Err(_cast_err)) => self.push_err(ResolverError::IntegerTooLarge { span }), - Err(Some(error)) => self.push_err(error), - Err(None) => (), - } - 0 - } - - fn try_eval_array_length_id( - &self, - rhs: ExprId, - span: Span, - ) -> Result> { - // 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> { - 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, _) => { - let definition = self.interner.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 })), - } - } - 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 })), - } - } - - fn resolve_fmt_str_literal(&mut self, str: String, call_expr_span: Span) -> HirLiteral { - let re = Regex::new(r"\{([a-zA-Z0-9_]+)\}") - .expect("ICE: an invalid regex pattern was used for checking format strings"); - let mut fmt_str_idents = Vec::new(); - for field in re.find_iter(&str) { - let matched_str = field.as_str(); - let ident_name = &matched_str[1..(matched_str.len() - 1)]; - - let scope_tree = self.scopes.current_scope_tree(); - let variable = scope_tree.find(ident_name); - if let Some((old_value, _)) = variable { - old_value.num_times_used += 1; - let ident = HirExpression::Ident(old_value.ident.clone(), None); - let expr_id = self.interner.push_expr(ident); - self.interner.push_expr_location(expr_id, call_expr_span, self.file); - fmt_str_idents.push(expr_id); - } else if ident_name.parse::().is_ok() { - self.errors.push(ResolverError::NumericConstantInFormatString { - name: ident_name.to_owned(), - span: call_expr_span, - }); - } else { - self.errors.push(ResolverError::VariableNotDeclared { - name: ident_name.to_owned(), - span: call_expr_span, - }); - } - } - HirLiteral::FmtStr(str, fmt_str_idents) - } - - fn check_break_continue(&mut self, is_break: bool, span: Span) { - if !self.in_unconstrained_fn { - self.push_err(ResolverError::JumpInConstrainedFn { is_break, span }); - } - if self.nested_loops == 0 { - self.push_err(ResolverError::JumpOutsideLoop { is_break, span }); - } - } -} - -/// Gives an error if a user tries to create a mutable reference -/// to an immutable variable. -pub fn verify_mutable_reference(interner: &NodeInterner, rhs: ExprId) -> Result<(), ResolverError> { - match interner.expression(&rhs) { - HirExpression::MemberAccess(member_access) => { - verify_mutable_reference(interner, member_access.lhs) - } - HirExpression::Index(_) => { - let span = interner.expr_span(&rhs); - Err(ResolverError::MutableReferenceToArrayElement { span }) - } - HirExpression::Ident(ident, _) => { - if let Some(definition) = interner.try_definition(ident.id) { - if !definition.mutable { - return Err(ResolverError::MutableReferenceToImmutableVariable { - span: interner.expr_span(&rhs), - variable: definition.name.clone(), - }); - } - } - Ok(()) - } - _ => Ok(()), - } -} diff --git a/compiler/noirc_frontend/src/hir/type_check/expr.rs b/compiler/noirc_frontend/src/hir/type_check/expr.rs deleted file mode 100644 index 9dfe0901016..00000000000 --- a/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ /dev/null @@ -1,1395 +0,0 @@ -use iter_extended::vecmap; -use noirc_errors::Span; - -use crate::ast::{BinaryOpKind, IntegerBitSize, UnaryOp}; -use crate::hir_def::expr::HirCallExpression; -use crate::macros_api::Signedness; -use crate::{ - hir::{resolution::resolver::verify_mutable_reference, type_check::errors::Source}, - hir_def::{ - expr::{ - self, HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirExpression, HirIdent, - HirLiteral, HirMethodCallExpression, HirMethodReference, HirPrefixExpression, ImplKind, - }, - types::Type, - }, - node_interner::{DefinitionKind, ExprId, FuncId, TraitId, TraitImplKind, TraitMethodId}, - TypeBinding, TypeBindings, TypeVariableKind, -}; - -use super::NoMatchingImplFoundError; -use super::{errors::TypeCheckError, TypeChecker}; - -impl<'interner> TypeChecker<'interner> { - fn check_if_deprecated(&mut self, expr: &ExprId) { - if let HirExpression::Ident(expr::HirIdent { location, id, impl_kind: _ }, _) = - self.interner.expression(expr) - { - if let Some(DefinitionKind::Function(func_id)) = - self.interner.try_definition(id).map(|def| &def.kind) - { - let attributes = self.interner.function_attributes(func_id); - if let Some(note) = attributes.get_deprecated_note() { - self.errors.push(TypeCheckError::CallDeprecated { - name: self.interner.definition_name(id).to_string(), - note, - span: location.span, - }); - } - } - } - } - - fn is_unconstrained_call(&self, expr: &ExprId) -> bool { - if let HirExpression::Ident(expr::HirIdent { id, .. }, _) = self.interner.expression(expr) { - if let Some(DefinitionKind::Function(func_id)) = - self.interner.try_definition(id).map(|def| &def.kind) - { - let modifiers = self.interner.function_modifiers(func_id); - return modifiers.is_unconstrained; - } - } - false - } - - fn check_hir_array_literal( - &mut self, - hir_array_literal: HirArrayLiteral, - ) -> (Result>, Box) { - match hir_array_literal { - HirArrayLiteral::Standard(arr) => { - let elem_types = vecmap(&arr, |arg| self.check_expression(arg)); - - let first_elem_type = elem_types - .first() - .cloned() - .unwrap_or_else(|| self.interner.next_type_variable()); - - // Check if the array is homogeneous - for (index, elem_type) in elem_types.iter().enumerate().skip(1) { - let location = self.interner.expr_location(&arr[index]); - - elem_type.unify(&first_elem_type, &mut self.errors, || { - TypeCheckError::NonHomogeneousArray { - first_span: self.interner.expr_location(&arr[0]).span, - first_type: first_elem_type.to_string(), - first_index: index, - second_span: location.span, - second_type: elem_type.to_string(), - second_index: index + 1, - } - .add_context("elements in an array must have the same type") - }); - } - - (Ok(arr.len() as u32), Box::new(first_elem_type.clone())) - } - HirArrayLiteral::Repeated { repeated_element, length } => { - let elem_type = self.check_expression(&repeated_element); - let length = match length { - Type::Constant(length) => Ok(length), - other => Err(Box::new(other)), - }; - (length, Box::new(elem_type)) - } - } - } - - /// Infers a type for a given expression, and return this type. - /// As a side-effect, this function will also remember this type in the NodeInterner - /// for the given expr_id key. - /// - /// This function also converts any HirExpression::MethodCalls `a.foo(b, c)` into - /// an equivalent HirExpression::Call in the form `foo(a, b, c)`. This cannot - /// be done earlier since we need to know the type of the object `a` to resolve which - /// function `foo` to refer to. - pub(crate) fn check_expression(&mut self, expr_id: &ExprId) -> Type { - let typ = match self.interner.expression(expr_id) { - HirExpression::Ident(ident, generics) => self.check_ident(ident, expr_id, generics), - HirExpression::Literal(literal) => match literal { - HirLiteral::Array(hir_array_literal) => { - let (length, elem_type) = self.check_hir_array_literal(hir_array_literal); - Type::Array( - length.map_or_else( - |typ| typ, - |constant| Box::new(Type::constant_variable(constant, self.interner)), - ), - elem_type, - ) - } - HirLiteral::Slice(hir_array_literal) => { - let (length_type, elem_type) = self.check_hir_array_literal(hir_array_literal); - match length_type { - Ok(_length) => Type::Slice(elem_type), - Err(_non_constant) => { - self.errors.push(TypeCheckError::NonConstantSliceLength { - span: self.interner.expr_span(expr_id), - }); - Type::Error - } - } - } - HirLiteral::Bool(_) => Type::Bool, - HirLiteral::Integer(_, _) => self.polymorphic_integer_or_field(), - HirLiteral::Str(string) => { - let len = Type::Constant(string.len() as u32); - Type::String(Box::new(len)) - } - HirLiteral::FmtStr(string, idents) => { - let len = Type::Constant(string.len() as u32); - let types = vecmap(&idents, |elem| self.check_expression(elem)); - Type::FmtString(Box::new(len), Box::new(Type::Tuple(types))) - } - HirLiteral::Unit => Type::Unit, - }, - HirExpression::Infix(infix_expr) => { - // The type of the infix expression must be looked up from a type table - let lhs_type = self.check_expression(&infix_expr.lhs); - let rhs_type = self.check_expression(&infix_expr.rhs); - - let lhs_span = self.interner.expr_span(&infix_expr.lhs); - let rhs_span = self.interner.expr_span(&infix_expr.rhs); - let span = lhs_span.merge(rhs_span); - - let operator = &infix_expr.operator; - match self.infix_operand_type_rules(&lhs_type, operator, &rhs_type, span) { - Ok((typ, use_impl)) => { - if use_impl { - let id = infix_expr.trait_method_id; - - // Delay checking the trait constraint until the end of the function. - // Checking it now could bind an unbound type variable to any type - // that implements the trait. - let constraint = crate::hir_def::traits::TraitConstraint { - typ: lhs_type.clone(), - trait_id: id.trait_id, - trait_generics: Vec::new(), - }; - self.trait_constraints.push((constraint, *expr_id)); - self.typecheck_operator_method(*expr_id, id, &lhs_type, span); - } - typ - } - Err(error) => { - self.errors.push(error); - Type::Error - } - } - } - HirExpression::Index(index_expr) => self.check_index_expression(expr_id, index_expr), - HirExpression::Call(call_expr) => { - let function = self.check_expression(&call_expr.func); - - let args = vecmap(&call_expr.arguments, |arg| { - let typ = self.check_expression(arg); - (typ, *arg, self.interner.expr_span(arg)) - }); - - let span = self.interner.expr_span(expr_id); - self.check_call(&call_expr, function, args, span) - } - HirExpression::MethodCall(mut method_call) => { - let method_call_span = self.interner.expr_span(expr_id); - let object = method_call.object; - let object_span = self.interner.expr_span(&method_call.object); - let mut object_type = self.check_expression(&method_call.object).follow_bindings(); - let method_name = method_call.method.0.contents.as_str(); - match self.lookup_method(&object_type, method_name, expr_id) { - Some(method_ref) => { - // Desugar the method call into a normal, resolved function call - // so that the backend doesn't need to worry about methods - let location = method_call.location; - - // Automatically add `&mut` if the method expects a mutable reference and - // the object is not already one. - let func_id = match &method_ref { - HirMethodReference::FuncId(func_id) => *func_id, - HirMethodReference::TraitMethodId(method_id, _) => { - let id = self.interner.trait_method_id(*method_id); - let definition = self.interner.definition(id); - let DefinitionKind::Function(func_id) = definition.kind else { - unreachable!( - "Expected trait function to be a DefinitionKind::Function" - ) - }; - func_id - } - }; - - if func_id != FuncId::dummy_id() { - let function_type = self.interner.function_meta(&func_id).typ.clone(); - self.try_add_mutable_reference_to_object( - &mut method_call, - &function_type, - &mut object_type, - ); - } - - // These arguments will be given to the desugared function call. - // Compared to the method arguments, they also contain the object. - let mut function_args = Vec::with_capacity(method_call.arguments.len() + 1); - - function_args.push((object_type.clone(), object, object_span)); - - for arg in method_call.arguments.iter() { - let span = self.interner.expr_span(arg); - let typ = self.check_expression(arg); - function_args.push((typ, *arg, span)); - } - - // TODO: update object_type here? - let ((function_id, _), function_call) = method_call.into_function_call( - &method_ref, - object_type, - location, - self.interner, - ); - - let func_type = self.check_expression(&function_id); - - // Type check the new call now that it has been changed from a method call - // to a function call. This way we avoid duplicating code. - // We call `check_call` rather than `check_expression` directly as we want to avoid - // resolving the object type again once it is part of the arguments. - let typ = self.check_call( - &function_call, - func_type, - function_args, - method_call_span, - ); - - self.interner.replace_expr(expr_id, HirExpression::Call(function_call)); - - typ - } - None => Type::Error, - } - } - HirExpression::Cast(cast_expr) => { - // Evaluate the LHS - let lhs_type = self.check_expression(&cast_expr.lhs); - let span = self.interner.expr_span(expr_id); - self.check_cast(lhs_type, cast_expr.r#type, span) - } - HirExpression::Block(block_expr) => self.check_block(block_expr), - HirExpression::Prefix(prefix_expr) => { - let rhs_type = self.check_expression(&prefix_expr.rhs); - let span = self.interner.expr_span(&prefix_expr.rhs); - self.type_check_prefix_operand(&prefix_expr.operator, &rhs_type, span) - } - HirExpression::If(if_expr) => self.check_if_expr(&if_expr, expr_id), - HirExpression::Constructor(constructor) => self.check_constructor(constructor, expr_id), - HirExpression::MemberAccess(access) => self.check_member_access(access, *expr_id), - HirExpression::Error => Type::Error, - HirExpression::Tuple(elements) => { - Type::Tuple(vecmap(&elements, |elem| self.check_expression(elem))) - } - HirExpression::Lambda(lambda) => { - let captured_vars = vecmap(lambda.captures, |capture| { - self.interner.definition_type(capture.ident.id) - }); - - let env_type: Type = - if captured_vars.is_empty() { Type::Unit } else { Type::Tuple(captured_vars) }; - - let params = vecmap(lambda.parameters, |(pattern, typ)| { - self.bind_pattern(&pattern, typ.clone()); - typ - }); - - let actual_return = self.check_expression(&lambda.body); - - let span = self.interner.expr_span(&lambda.body); - self.unify(&actual_return, &lambda.return_type, || TypeCheckError::TypeMismatch { - expected_typ: lambda.return_type.to_string(), - expr_typ: actual_return.to_string(), - expr_span: span, - }); - - Type::Function(params, Box::new(lambda.return_type), Box::new(env_type)) - } - HirExpression::Quote(_) => Type::Quoted(crate::QuotedType::Quoted), - HirExpression::Comptime(block) => self.check_block(block), - - // Unquote should be inserted & removed by the comptime interpreter. - // Even if we allowed it here, we wouldn't know what type to give to the result. - HirExpression::Unquote(block) => { - unreachable!("Unquote remaining during type checking {block:?}") - } - }; - - self.interner.push_expr_type(*expr_id, typ.clone()); - typ - } - - fn check_call( - &mut self, - call: &HirCallExpression, - func_type: Type, - args: Vec<(Type, ExprId, Span)>, - span: Span, - ) -> Type { - // Need to setup these flags here as `self` is borrowed mutably to type check the rest of the call expression - // These flags are later used to type check calls to unconstrained functions from constrained functions - let func_mod = self.current_function.map(|func| self.interner.function_modifiers(&func)); - let is_current_func_constrained = - func_mod.map_or(true, |func_mod| !func_mod.is_unconstrained); - - let is_unconstrained_call = self.is_unconstrained_call(&call.func); - self.check_if_deprecated(&call.func); - - // Check that we are not passing a mutable reference from a constrained runtime to an unconstrained runtime - if is_current_func_constrained && is_unconstrained_call { - for (typ, _, _) in args.iter() { - if !typ.is_valid_for_unconstrained_boundary() { - self.errors.push(TypeCheckError::ConstrainedReferenceToUnconstrained { span }); - } - } - } - - let return_type = self.bind_function_type(func_type, args, span); - - // Check that we are not passing a slice from an unconstrained runtime to a constrained runtime - if is_current_func_constrained && is_unconstrained_call { - if return_type.contains_slice() { - self.errors.push(TypeCheckError::UnconstrainedSliceReturnToConstrained { span }); - } else if matches!(&return_type.follow_bindings(), Type::MutableReference(_)) { - self.errors.push(TypeCheckError::UnconstrainedReferenceToConstrained { span }); - } - }; - - return_type - } - - fn check_block(&mut self, block: HirBlockExpression) -> Type { - let mut block_type = Type::Unit; - - let statements = block.statements(); - for (i, stmt) in statements.iter().enumerate() { - let expr_type = self.check_statement(stmt); - - if let crate::hir_def::stmt::HirStatement::Semi(expr) = self.interner.statement(stmt) { - let inner_expr_type = self.interner.id_type(expr); - let span = self.interner.expr_span(&expr); - - self.unify(&inner_expr_type, &Type::Unit, || TypeCheckError::UnusedResultError { - expr_type: inner_expr_type.clone(), - expr_span: span, - }); - } - - if i + 1 == statements.len() { - block_type = expr_type; - } - } - - block_type - } - - /// Returns the type of the given identifier - fn check_ident( - &mut self, - ident: HirIdent, - expr_id: &ExprId, - generics: Option>, - ) -> Type { - let mut bindings = TypeBindings::new(); - - // Add type bindings from any constraints that were used. - // We need to do this first since otherwise instantiating the type below - // will replace each trait generic with a fresh type variable, rather than - // the type used in the trait constraint (if it exists). See #4088. - if let ImplKind::TraitMethod(_, constraint, assumed) = &ident.impl_kind { - let the_trait = self.interner.get_trait(constraint.trait_id); - assert_eq!(the_trait.generics.len(), constraint.trait_generics.len()); - - for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics) { - // Avoid binding t = t - if !arg.occurs(param.type_var.id()) { - bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.clone())); - } - } - - // If the trait impl is already assumed to exist we should add any type bindings for `Self`. - // Otherwise `self` will be replaced with a fresh type variable, which will require the user - // to specify a redundant type annotation. - if *assumed { - bindings.insert( - the_trait.self_type_typevar_id, - (the_trait.self_type_typevar.clone(), constraint.typ.clone()), - ); - } - } - - // An identifiers type may be forall-quantified in the case of generic functions. - // E.g. `fn foo(t: T, field: Field) -> T` has type `forall T. fn(T, Field) -> T`. - // We must instantiate identifiers at every call site to replace this T with a new type - // variable to handle generic functions. - let t = self.interner.id_type_substitute_trait_as_type(ident.id); - - let definition = self.interner.try_definition(ident.id); - let function_generic_count = definition.map_or(0, |definition| match &definition.kind { - DefinitionKind::Function(function) => { - self.interner.function_modifiers(function).generic_count - } - _ => 0, - }); - - let span = self.interner.expr_span(expr_id); - // This instantiates a trait's generics as well which need to be set - // when the constraint below is later solved for when the function is - // finished. How to link the two? - let (typ, bindings) = self.instantiate(t, bindings, generics, function_generic_count, span); - - // Push any trait constraints required by this definition to the context - // to be checked later when the type of this variable is further constrained. - if let Some(definition) = self.interner.try_definition(ident.id) { - if let DefinitionKind::Function(func_id) = definition.kind { - let function = self.interner.function_meta(&func_id); - for mut constraint in function.trait_constraints.clone() { - constraint.apply_bindings(&bindings); - self.trait_constraints.push((constraint, *expr_id)); - } - } - } - - if let ImplKind::TraitMethod(_, mut constraint, assumed) = ident.impl_kind { - constraint.apply_bindings(&bindings); - if assumed { - let trait_impl = TraitImplKind::Assumed { - object_type: constraint.typ, - trait_generics: constraint.trait_generics, - }; - self.interner.select_impl_for_expression(*expr_id, trait_impl); - } else { - // Currently only one impl can be selected per expr_id, so this - // constraint needs to be pushed after any other constraints so - // that monomorphization can resolve this trait method to the correct impl. - self.trait_constraints.push((constraint, *expr_id)); - } - } - - self.interner.store_instantiation_bindings(*expr_id, bindings); - typ - } - - fn instantiate( - &mut self, - typ: Type, - bindings: TypeBindings, - turbofish_generics: Option>, - function_generic_count: usize, - span: Span, - ) -> (Type, TypeBindings) { - match turbofish_generics { - Some(turbofish_generics) => { - if turbofish_generics.len() != function_generic_count { - self.errors.push(TypeCheckError::IncorrectTurbofishGenericCount { - expected_count: function_generic_count, - actual_count: turbofish_generics.len(), - span, - }); - typ.instantiate_with_bindings(bindings, self.interner) - } else { - // Fetch the count of any implicit generics on the function, such as - // for a method within a generic impl. - let implicit_generic_count = match &typ { - Type::Forall(generics, _) => generics.len() - function_generic_count, - _ => 0, - }; - typ.instantiate_with(turbofish_generics, self.interner, implicit_generic_count) - } - } - None => typ.instantiate_with_bindings(bindings, self.interner), - } - } - - pub fn verify_trait_constraint( - &mut self, - object_type: &Type, - trait_id: TraitId, - trait_generics: &[Type], - function_ident_id: ExprId, - span: Span, - ) { - match self.interner.lookup_trait_implementation(object_type, trait_id, trait_generics) { - Ok(impl_kind) => { - self.interner.select_impl_for_expression(function_ident_id, impl_kind); - } - Err(erroring_constraints) => { - if erroring_constraints.is_empty() { - self.errors.push(TypeCheckError::TypeAnnotationsNeeded { span }); - } else if let Some(error) = - NoMatchingImplFoundError::new(self.interner, erroring_constraints, span) - { - self.errors.push(TypeCheckError::NoMatchingImplFound(error)); - } - } - } - } - - /// Check if the given method type requires a mutable reference to the object type, and check - /// if the given object type is already a mutable reference. If not, add one. - /// This is used to automatically transform a method call: `foo.bar()` into a function - /// call: `bar(&mut foo)`. - /// - /// A notable corner case of this function is where it interacts with auto-deref of `.`. - /// If a field is being mutated e.g. `foo.bar.mutate_bar()` where `foo: &mut Foo`, the compiler - /// will insert a dereference before bar `(*foo).bar.mutate_bar()` which would cause us to - /// mutate a copy of bar rather than a reference to it. We must check for this corner case here - /// and remove the implicitly added dereference operator if we find one. - fn try_add_mutable_reference_to_object( - &mut self, - method_call: &mut HirMethodCallExpression, - function_type: &Type, - object_type: &mut Type, - ) { - let expected_object_type = match function_type { - Type::Function(args, _, _) => args.first(), - Type::Forall(_, typ) => match typ.as_ref() { - Type::Function(args, _, _) => args.first(), - typ => unreachable!("Unexpected type for function: {typ}"), - }, - typ => unreachable!("Unexpected type for function: {typ}"), - }; - - if let Some(expected_object_type) = expected_object_type { - let actual_type = object_type.follow_bindings(); - - if matches!(expected_object_type.follow_bindings(), Type::MutableReference(_)) { - if !matches!(actual_type, Type::MutableReference(_)) { - if let Err(error) = verify_mutable_reference(self.interner, method_call.object) - { - self.errors.push(TypeCheckError::ResolverError(error)); - } - - let new_type = Type::MutableReference(Box::new(actual_type)); - *object_type = new_type.clone(); - - // First try to remove a dereference operator that may have been implicitly - // inserted by a field access expression `foo.bar` on a mutable reference `foo`. - let new_object = self.try_remove_implicit_dereference(method_call.object); - - // If that didn't work, then wrap the whole expression in an `&mut` - method_call.object = new_object.unwrap_or_else(|| { - let location = self.interner.id_location(method_call.object); - - let new_object = - self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { - operator: UnaryOp::MutableReference, - rhs: method_call.object, - trait_method_id: None, - })); - self.interner.push_expr_type(new_object, new_type); - self.interner.push_expr_location(new_object, location.span, location.file); - new_object - }); - } - // Otherwise if the object type is a mutable reference and the method is not, insert as - // many dereferences as needed. - } else if matches!(actual_type, Type::MutableReference(_)) { - let (object, new_type) = - self.insert_auto_dereferences(method_call.object, actual_type); - *object_type = new_type; - method_call.object = object; - } - } - } - - /// Insert as many dereference operations as necessary to automatically dereference a method - /// call object to its base value type T. - pub(crate) fn insert_auto_dereferences(&mut self, object: ExprId, typ: Type) -> (ExprId, Type) { - if let Type::MutableReference(element) = typ { - let location = self.interner.id_location(object); - - let object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { - operator: UnaryOp::Dereference { implicitly_added: true }, - rhs: object, - trait_method_id: None, - })); - self.interner.push_expr_type(object, element.as_ref().clone()); - self.interner.push_expr_location(object, location.span, location.file); - - // Recursively dereference to allow for converting &mut &mut T to T - self.insert_auto_dereferences(object, *element) - } else { - (object, typ) - } - } - - /// Given a method object: `(*foo).bar` of a method call `(*foo).bar.baz()`, remove the - /// implicitly added dereference operator if one is found. - /// - /// Returns Some(new_expr_id) if a dereference was removed and None otherwise. - fn try_remove_implicit_dereference(&mut self, object: ExprId) -> Option { - match self.interner.expression(&object) { - HirExpression::MemberAccess(mut access) => { - let new_lhs = self.try_remove_implicit_dereference(access.lhs)?; - access.lhs = new_lhs; - access.is_offset = true; - - // `object` will have a different type now, which will be filled in - // later when type checking the method call as a function call. - self.interner.replace_expr(&object, HirExpression::MemberAccess(access)); - Some(object) - } - HirExpression::Prefix(prefix) => match prefix.operator { - // Found a dereference we can remove. Now just replace it with its rhs to remove it. - UnaryOp::Dereference { implicitly_added: true } => Some(prefix.rhs), - _ => None, - }, - _ => None, - } - } - - fn check_index_expression( - &mut self, - id: &ExprId, - mut index_expr: expr::HirIndexExpression, - ) -> Type { - let index_type = self.check_expression(&index_expr.index); - let span = self.interner.expr_span(&index_expr.index); - - index_type.unify(&self.polymorphic_integer_or_field(), &mut self.errors, || { - TypeCheckError::TypeMismatch { - expected_typ: "an integer".to_owned(), - expr_typ: index_type.to_string(), - expr_span: span, - } - }); - - // When writing `a[i]`, if `a : &mut ...` then automatically dereference `a` as many - // times as needed to get the underlying array. - let lhs_type = self.check_expression(&index_expr.collection); - let (new_lhs, lhs_type) = self.insert_auto_dereferences(index_expr.collection, lhs_type); - index_expr.collection = new_lhs; - self.interner.replace_expr(id, HirExpression::Index(index_expr)); - - match lhs_type.follow_bindings() { - // XXX: We can check the array bounds here also, but it may be better to constant fold first - // and have ConstId instead of ExprId for constants - Type::Array(_, base_type) => *base_type, - Type::Slice(base_type) => *base_type, - Type::Error => Type::Error, - typ => { - let span = self.interner.expr_span(&new_lhs); - self.errors.push(TypeCheckError::TypeMismatch { - expected_typ: "Array".to_owned(), - expr_typ: typ.to_string(), - expr_span: span, - }); - Type::Error - } - } - } - - fn check_cast(&mut self, from: Type, to: Type, span: Span) -> Type { - match from.follow_bindings() { - Type::Integer(..) - | Type::FieldElement - | Type::TypeVariable(_, TypeVariableKind::IntegerOrField) - | Type::TypeVariable(_, TypeVariableKind::Integer) - | Type::Bool => (), - - Type::TypeVariable(_, _) => { - self.errors.push(TypeCheckError::TypeAnnotationsNeeded { span }); - return Type::Error; - } - Type::Error => return Type::Error, - from => { - self.errors.push(TypeCheckError::InvalidCast { from, span }); - return Type::Error; - } - } - - match to { - Type::Integer(sign, bits) => Type::Integer(sign, bits), - Type::FieldElement => Type::FieldElement, - Type::Bool => Type::Bool, - Type::Error => Type::Error, - _ => { - self.errors.push(TypeCheckError::UnsupportedCast { span }); - Type::Error - } - } - } - - fn check_if_expr(&mut self, if_expr: &expr::HirIfExpression, expr_id: &ExprId) -> Type { - let cond_type = self.check_expression(&if_expr.condition); - let then_type = self.check_expression(&if_expr.consequence); - - let expr_span = self.interner.expr_span(&if_expr.condition); - - self.unify(&cond_type, &Type::Bool, || TypeCheckError::TypeMismatch { - expected_typ: Type::Bool.to_string(), - expr_typ: cond_type.to_string(), - expr_span, - }); - - match if_expr.alternative { - None => Type::Unit, - Some(alternative) => { - let else_type = self.check_expression(&alternative); - - let expr_span = self.interner.expr_span(expr_id); - self.unify(&then_type, &else_type, || { - let err = TypeCheckError::TypeMismatch { - expected_typ: then_type.to_string(), - expr_typ: else_type.to_string(), - expr_span, - }; - - let context = if then_type == Type::Unit { - "Are you missing a semicolon at the end of your 'else' branch?" - } else if else_type == Type::Unit { - "Are you missing a semicolon at the end of the first block of this 'if'?" - } else { - "Expected the types of both if branches to be equal" - }; - - err.add_context(context) - }); - - then_type - } - } - } - - fn check_constructor( - &mut self, - constructor: expr::HirConstructorExpression, - expr_id: &ExprId, - ) -> Type { - let typ = constructor.r#type; - let generics = constructor.struct_generics; - - // Sort argument types by name so we can zip with the struct type in the same ordering. - // Note that we use a Vec to store the original arguments (rather than a BTreeMap) to - // preserve the evaluation order of the source code. - let mut args = constructor.fields; - sort_by_key_ref(&mut args, |(name, _)| name); - - let mut fields = typ.borrow().get_fields(&generics); - sort_by_key_ref(&mut fields, |(name, _)| name); - - for ((param_name, param_type), (arg_ident, arg)) in fields.into_iter().zip(args) { - // This can be false if the user provided an incorrect field count. That error should - // be caught during name resolution so it is fine to skip typechecking if there is a - // mismatch here as long as we continue typechecking the rest of the program to the best - // of our ability. - if param_name == arg_ident.0.contents { - let arg_type = self.check_expression(&arg); - - let span = self.interner.expr_span(expr_id); - self.unify_with_coercions(&arg_type, ¶m_type, arg, || { - TypeCheckError::TypeMismatch { - expected_typ: param_type.to_string(), - expr_typ: arg_type.to_string(), - expr_span: span, - } - }); - } - } - - Type::Struct(typ, generics) - } - - fn check_member_access(&mut self, mut access: expr::HirMemberAccess, expr_id: ExprId) -> Type { - let lhs_type = self.check_expression(&access.lhs).follow_bindings(); - let span = self.interner.expr_span(&expr_id); - let access_lhs = &mut access.lhs; - - let dereference_lhs = |this: &mut Self, lhs_type, element| { - let old_lhs = *access_lhs; - - *access_lhs = this.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { - operator: crate::ast::UnaryOp::Dereference { implicitly_added: true }, - rhs: old_lhs, - trait_method_id: None, - })); - this.interner.push_expr_type(old_lhs, lhs_type); - this.interner.push_expr_type(*access_lhs, element); - - let old_location = this.interner.id_location(old_lhs); - this.interner.push_expr_location(*access_lhs, span, old_location.file); - }; - - // If this access is just a field offset, we want to avoid dereferencing - let dereference_lhs = (!access.is_offset).then_some(dereference_lhs); - - match self.check_field_access(&lhs_type, &access.rhs.0.contents, span, dereference_lhs) { - Some((element_type, index)) => { - self.interner.set_field_index(expr_id, index); - // We must update `access` in case we added any dereferences to it - self.interner.replace_expr(&expr_id, HirExpression::MemberAccess(access)); - element_type - } - None => Type::Error, - } - } - - /// This will verify that an expression in the form `lhs.rhs_name` has the given field and will push - /// a type error if it does not. If there is no error, the type of the struct/tuple field is returned - /// along with the index of the field in question. - /// - /// This function is abstracted from check_member_access so that it can be shared between - /// there and the HirLValue::MemberAccess case of check_lvalue. - /// - /// `dereference_lhs` is called when the lhs type is a Type::MutableReference that should be - /// automatically dereferenced so its field can be extracted. This function is expected to - /// perform any mutations necessary to wrap the lhs in a UnaryOp::Dereference prefix - /// expression. The second parameter of this function represents the lhs_type (which should - /// always be a Type::MutableReference if `dereference_lhs` is called) and the third - /// represents the element type. - /// - /// If `dereference_lhs` is None, this will assume we're taking the offset of a struct field - /// rather than dereferencing it. So the result of `foo.bar` with a `foo : &mut Foo` will - /// be a `&mut Bar` rather than just a `Bar`. - pub(super) fn check_field_access( - &mut self, - lhs_type: &Type, - field_name: &str, - span: Span, - dereference_lhs: Option, - ) -> Option<(Type, usize)> { - let lhs_type = lhs_type.follow_bindings(); - - match &lhs_type { - Type::Struct(s, args) => { - let s = s.borrow(); - if let Some((field, index)) = s.get_field(field_name, args) { - return Some((field, index)); - } - } - Type::Tuple(elements) => { - if let Ok(index) = field_name.parse::() { - let length = elements.len(); - if index < length { - return Some((elements[index].clone(), index)); - } else { - self.errors.push(TypeCheckError::TupleIndexOutOfBounds { - index, - lhs_type, - length, - span, - }); - return None; - } - } - } - // If the lhs is a mutable reference we automatically transform - // lhs.field into (*lhs).field - Type::MutableReference(element) => { - if let Some(mut dereference_lhs) = dereference_lhs { - dereference_lhs(self, lhs_type.clone(), element.as_ref().clone()); - return self.check_field_access( - element, - field_name, - span, - Some(dereference_lhs), - ); - } else { - let (element, index) = - self.check_field_access(element, field_name, span, dereference_lhs)?; - return Some((Type::MutableReference(Box::new(element)), index)); - } - } - _ => (), - } - - // If we get here the type has no field named 'access.rhs'. - // Now we specialize the error message based on whether we know the object type in question yet. - if let Type::TypeVariable(..) = &lhs_type { - self.errors.push(TypeCheckError::TypeAnnotationsNeeded { span }); - } else if lhs_type != Type::Error { - self.errors.push(TypeCheckError::AccessUnknownMember { - lhs_type, - field_name: field_name.to_string(), - span, - }); - } - - None - } - - // Given a binary comparison operator and another type. This method will produce the output type - // and a boolean indicating whether to use the trait impl corresponding to the operator - // or not. A value of false indicates the caller to use a primitive operation for this - // operator, while a true value indicates a user-provided trait impl is required. - fn comparator_operand_type_rules( - &mut self, - lhs_type: &Type, - rhs_type: &Type, - op: &HirBinaryOp, - span: Span, - ) -> Result<(Type, bool), TypeCheckError> { - use Type::*; - - match (lhs_type, rhs_type) { - // Avoid reporting errors multiple times - (Error, _) | (_, Error) => Ok((Bool, false)), - (Alias(alias, args), other) | (other, Alias(alias, args)) => { - let alias = alias.borrow().get_type(args); - self.comparator_operand_type_rules(&alias, other, op, span) - } - - // Matches on TypeVariable must be first to follow any type - // bindings. - (TypeVariable(var, _), other) | (other, TypeVariable(var, _)) => { - if let TypeBinding::Bound(binding) = &*var.borrow() { - return self.comparator_operand_type_rules(other, binding, op, span); - } - - let use_impl = self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); - Ok((Bool, use_impl)) - } - (Integer(sign_x, bit_width_x), Integer(sign_y, bit_width_y)) => { - if sign_x != sign_y { - return Err(TypeCheckError::IntegerSignedness { - sign_x: *sign_x, - sign_y: *sign_y, - span, - }); - } - if bit_width_x != bit_width_y { - return Err(TypeCheckError::IntegerBitWidth { - bit_width_x: *bit_width_x, - bit_width_y: *bit_width_y, - span, - }); - } - Ok((Bool, false)) - } - (FieldElement, FieldElement) => { - if op.kind.is_valid_for_field_type() { - Ok((Bool, false)) - } else { - Err(TypeCheckError::FieldComparison { span }) - } - } - - // <= and friends are technically valid for booleans, just not very useful - (Bool, Bool) => Ok((Bool, false)), - - (lhs, rhs) => { - self.unify(lhs, rhs, || TypeCheckError::TypeMismatchWithSource { - expected: lhs.clone(), - actual: rhs.clone(), - span: op.location.span, - source: Source::Binary, - }); - Ok((Bool, true)) - } - } - } - - fn lookup_method( - &mut self, - object_type: &Type, - method_name: &str, - expr_id: &ExprId, - ) -> Option { - match object_type.follow_bindings() { - Type::Struct(typ, _args) => { - let id = typ.borrow().id; - match self.interner.lookup_method(object_type, id, method_name, false) { - Some(method_id) => Some(HirMethodReference::FuncId(method_id)), - None => { - self.errors.push(TypeCheckError::UnresolvedMethodCall { - method_name: method_name.to_string(), - object_type: object_type.clone(), - span: self.interner.expr_span(expr_id), - }); - None - } - } - } - // TODO: We should allow method calls on `impl Trait`s eventually. - // For now it is fine since they are only allowed on return types. - Type::TraitAsType(..) => { - self.errors.push(TypeCheckError::UnresolvedMethodCall { - method_name: method_name.to_string(), - object_type: object_type.clone(), - span: self.interner.expr_span(expr_id), - }); - None - } - Type::NamedGeneric(_, _, _) => { - let func_meta = self.interner.function_meta( - &self.current_function.expect("unexpected method outside a function"), - ); - - for constraint in &func_meta.trait_constraints { - if *object_type == constraint.typ { - if let Some(the_trait) = self.interner.try_get_trait(constraint.trait_id) { - for (method_index, method) in the_trait.methods.iter().enumerate() { - if method.name.0.contents == method_name { - let trait_method = TraitMethodId { - trait_id: constraint.trait_id, - method_index, - }; - return Some(HirMethodReference::TraitMethodId( - trait_method, - constraint.trait_generics.clone(), - )); - } - } - } - } - } - - self.errors.push(TypeCheckError::UnresolvedMethodCall { - method_name: method_name.to_string(), - object_type: object_type.clone(), - span: self.interner.expr_span(expr_id), - }); - None - } - // Mutable references to another type should resolve to methods of their element type. - // This may be a struct or a primitive type. - Type::MutableReference(element) => self - .interner - .lookup_primitive_trait_method_mut(element.as_ref(), method_name) - .map(HirMethodReference::FuncId) - .or_else(|| self.lookup_method(&element, method_name, expr_id)), - - // If we fail to resolve the object to a struct type, we have no way of type - // checking its arguments as we can't even resolve the name of the function - Type::Error => None, - - // The type variable must be unbound at this point since follow_bindings was called - Type::TypeVariable(_, TypeVariableKind::Normal) => { - let span = self.interner.expr_span(expr_id); - self.errors.push(TypeCheckError::TypeAnnotationsNeeded { span }); - None - } - - other => match self.interner.lookup_primitive_method(&other, method_name) { - Some(method_id) => Some(HirMethodReference::FuncId(method_id)), - None => { - self.errors.push(TypeCheckError::UnresolvedMethodCall { - method_name: method_name.to_string(), - object_type: object_type.clone(), - span: self.interner.expr_span(expr_id), - }); - None - } - }, - } - } - - fn bind_function_type_impl( - &mut self, - fn_params: &[Type], - fn_ret: &Type, - callsite_args: &[(Type, ExprId, Span)], - span: Span, - ) -> Type { - if fn_params.len() != callsite_args.len() { - self.errors.push(TypeCheckError::ParameterCountMismatch { - expected: fn_params.len(), - found: callsite_args.len(), - span, - }); - return Type::Error; - } - - for (param, (arg, _, arg_span)) in fn_params.iter().zip(callsite_args) { - self.unify(arg, param, || TypeCheckError::TypeMismatch { - expected_typ: param.to_string(), - expr_typ: arg.to_string(), - expr_span: *arg_span, - }); - } - - fn_ret.clone() - } - - fn bind_function_type( - &mut self, - function: Type, - args: Vec<(Type, ExprId, Span)>, - span: Span, - ) -> Type { - // Could do a single unification for the entire function type, but matching beforehand - // lets us issue a more precise error on the individual argument that fails to type check. - match function { - Type::TypeVariable(binding, TypeVariableKind::Normal) => { - if let TypeBinding::Bound(typ) = &*binding.borrow() { - return self.bind_function_type(typ.clone(), args, span); - } - - let ret = self.interner.next_type_variable(); - let args = vecmap(args, |(arg, _, _)| arg); - let env_type = self.interner.next_type_variable(); - let expected = Type::Function(args, Box::new(ret.clone()), Box::new(env_type)); - - if let Err(error) = binding.try_bind(expected, span) { - self.errors.push(error); - } - ret - } - // ignoring env for subtype on purpose - Type::Function(parameters, ret, _env) => { - self.bind_function_type_impl(¶meters, &ret, &args, span) - } - Type::Error => Type::Error, - found => { - self.errors.push(TypeCheckError::ExpectedFunction { found, span }); - Type::Error - } - } - } - - /// Handles the TypeVariable case for checking binary operators. - /// Returns true if we should use the impl for the operator instead of the primitive - /// version of it. - fn bind_type_variables_for_infix( - &mut self, - lhs_type: &Type, - op: &HirBinaryOp, - rhs_type: &Type, - span: Span, - ) -> bool { - self.unify(lhs_type, rhs_type, || TypeCheckError::TypeMismatchWithSource { - expected: lhs_type.clone(), - actual: rhs_type.clone(), - source: Source::Binary, - span, - }); - - let use_impl = !lhs_type.is_numeric(); - - // If this operator isn't valid for fields we have to possibly narrow - // TypeVariableKind::IntegerOrField to TypeVariableKind::Integer. - // Doing so also ensures a type error if Field is used. - // The is_numeric check is to allow impls for custom types to bypass this. - if !op.kind.is_valid_for_field_type() && lhs_type.is_numeric() { - let target = Type::polymorphic_integer(self.interner); - - use crate::ast::BinaryOpKind::*; - use TypeCheckError::*; - self.unify(lhs_type, &target, || match op.kind { - Less | LessEqual | Greater | GreaterEqual => FieldComparison { span }, - And | Or | Xor | ShiftRight | ShiftLeft => FieldBitwiseOp { span }, - Modulo => FieldModulo { span }, - other => unreachable!("Operator {other:?} should be valid for Field"), - }); - } - - use_impl - } - - // Given a binary operator and another type. This method will produce the output type - // and a boolean indicating whether to use the trait impl corresponding to the operator - // or not. A value of false indicates the caller to use a primitive operation for this - // operator, while a true value indicates a user-provided trait impl is required. - fn infix_operand_type_rules( - &mut self, - lhs_type: &Type, - op: &HirBinaryOp, - rhs_type: &Type, - span: Span, - ) -> Result<(Type, bool), TypeCheckError> { - if op.kind.is_comparator() { - return self.comparator_operand_type_rules(lhs_type, rhs_type, op, span); - } - - use Type::*; - match (lhs_type, rhs_type) { - // An error type on either side will always return an error - (Error, _) | (_, Error) => Ok((Error, false)), - (Alias(alias, args), other) | (other, Alias(alias, args)) => { - let alias = alias.borrow().get_type(args); - self.infix_operand_type_rules(&alias, op, other, span) - } - - // Matches on TypeVariable must be first so that we follow any type - // bindings. - (TypeVariable(int, _), other) | (other, TypeVariable(int, _)) => { - if let TypeBinding::Bound(binding) = &*int.borrow() { - return self.infix_operand_type_rules(binding, op, other, span); - } - if op.kind == BinaryOpKind::ShiftLeft || op.kind == BinaryOpKind::ShiftRight { - self.unify( - rhs_type, - &Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight), - || TypeCheckError::InvalidShiftSize { span }, - ); - let use_impl = if lhs_type.is_numeric() { - let integer_type = Type::polymorphic_integer(self.interner); - self.bind_type_variables_for_infix(lhs_type, op, &integer_type, span) - } else { - true - }; - return Ok((lhs_type.clone(), use_impl)); - } - let use_impl = self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); - Ok((other.clone(), use_impl)) - } - (Integer(sign_x, bit_width_x), Integer(sign_y, bit_width_y)) => { - if op.kind == BinaryOpKind::ShiftLeft || op.kind == BinaryOpKind::ShiftRight { - if *sign_y != Signedness::Unsigned || *bit_width_y != IntegerBitSize::Eight { - return Err(TypeCheckError::InvalidShiftSize { span }); - } - return Ok((Integer(*sign_x, *bit_width_x), false)); - } - if sign_x != sign_y { - return Err(TypeCheckError::IntegerSignedness { - sign_x: *sign_x, - sign_y: *sign_y, - span, - }); - } - if bit_width_x != bit_width_y { - return Err(TypeCheckError::IntegerBitWidth { - bit_width_x: *bit_width_x, - bit_width_y: *bit_width_y, - span, - }); - } - Ok((Integer(*sign_x, *bit_width_x), false)) - } - // The result of two Fields is always a witness - (FieldElement, FieldElement) => { - if !op.kind.is_valid_for_field_type() { - if op.kind == BinaryOpKind::Modulo { - return Err(TypeCheckError::FieldModulo { span }); - } else { - return Err(TypeCheckError::FieldBitwiseOp { span }); - } - } - Ok((FieldElement, false)) - } - - (Bool, Bool) => Ok((Bool, false)), - - (lhs, rhs) => { - if op.kind == BinaryOpKind::ShiftLeft || op.kind == BinaryOpKind::ShiftRight { - if rhs == &Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight) { - return Ok((lhs.clone(), true)); - } - return Err(TypeCheckError::InvalidShiftSize { span }); - } - self.unify(lhs, rhs, || TypeCheckError::TypeMismatchWithSource { - expected: lhs.clone(), - actual: rhs.clone(), - span: op.location.span, - source: Source::Binary, - }); - Ok((lhs.clone(), true)) - } - } - } - - fn type_check_prefix_operand( - &mut self, - op: &crate::ast::UnaryOp, - rhs_type: &Type, - span: Span, - ) -> Type { - let mut unify = |expected| { - rhs_type.unify(&expected, &mut self.errors, || TypeCheckError::TypeMismatch { - expr_typ: rhs_type.to_string(), - expected_typ: expected.to_string(), - expr_span: span, - }); - expected - }; - - match op { - crate::ast::UnaryOp::Minus => { - if rhs_type.is_unsigned() { - self.errors - .push(TypeCheckError::InvalidUnaryOp { kind: rhs_type.to_string(), span }); - } - let expected = self.polymorphic_integer_or_field(); - rhs_type.unify(&expected, &mut self.errors, || TypeCheckError::InvalidUnaryOp { - kind: rhs_type.to_string(), - span, - }); - expected - } - crate::ast::UnaryOp::Not => { - let rhs_type = rhs_type.follow_bindings(); - - // `!` can work on booleans or integers - if matches!(rhs_type, Type::Integer(..)) { - return rhs_type; - } - - unify(Type::Bool) - } - crate::ast::UnaryOp::MutableReference => { - Type::MutableReference(Box::new(rhs_type.follow_bindings())) - } - crate::ast::UnaryOp::Dereference { implicitly_added: _ } => { - let element_type = self.interner.next_type_variable(); - unify(Type::MutableReference(Box::new(element_type.clone()))); - element_type - } - } - } - - /// Prerequisite: verify_trait_constraint of the operator's trait constraint. - /// - /// Although by this point the operator is expected to already have a trait impl, - /// we still need to match the operator's type against the method's instantiated type - /// to ensure the instantiation bindings are correct and the monomorphizer can - /// re-apply the needed bindings. - fn typecheck_operator_method( - &mut self, - expr_id: ExprId, - trait_method_id: TraitMethodId, - object_type: &Type, - span: Span, - ) { - let the_trait = self.interner.get_trait(trait_method_id.trait_id); - - let method = &the_trait.methods[trait_method_id.method_index]; - let (method_type, mut bindings) = method.typ.instantiate(self.interner); - - match method_type { - Type::Function(args, _, _) => { - // We can cheat a bit and match against only the object type here since no operator - // overload uses other generic parameters or return types aside from the object type. - let expected_object_type = &args[0]; - self.unify(object_type, expected_object_type, || TypeCheckError::TypeMismatch { - expected_typ: expected_object_type.to_string(), - expr_typ: object_type.to_string(), - expr_span: span, - }); - } - other => { - unreachable!("Expected operator method to have a function type, but found {other}") - } - } - - // We must also remember to apply these substitutions to the object_type - // referenced by the selected trait impl, if one has yet to be selected. - let impl_kind = self.interner.get_selected_impl_for_expression(expr_id); - if let Some(TraitImplKind::Assumed { object_type, trait_generics }) = impl_kind { - let the_trait = self.interner.get_trait(trait_method_id.trait_id); - let object_type = object_type.substitute(&bindings); - bindings.insert( - the_trait.self_type_typevar_id, - (the_trait.self_type_typevar.clone(), object_type.clone()), - ); - self.interner.select_impl_for_expression( - expr_id, - TraitImplKind::Assumed { object_type, trait_generics }, - ); - } - - self.interner.store_instantiation_bindings(expr_id, bindings); - } -} - -/// Taken from: https://stackoverflow.com/a/47127500 -fn sort_by_key_ref(xs: &mut [T], key: F) -where - F: Fn(&T) -> &K, - K: ?Sized + Ord, -{ - xs.sort_by(|x, y| key(x).cmp(key(y))); -} diff --git a/compiler/noirc_frontend/src/hir/type_check/mod.rs b/compiler/noirc_frontend/src/hir/type_check/mod.rs index 57125e1e2dd..b6efa17a529 100644 --- a/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -8,802 +8,5 @@ //! as all functions are required to give their full signatures. Closures are inferred but are //! never generalized and thus cannot be used polymorphically. mod errors; -mod expr; -mod stmt; - -pub use errors::{NoMatchingImplFoundError, TypeCheckError}; -use noirc_errors::Span; - -use crate::{ - hir_def::{ - expr::HirExpression, - function::{Param, Parameters}, - stmt::HirStatement, - traits::TraitConstraint, - }, - node_interner::{ExprId, FuncId, GlobalId, NodeInterner}, - Kind, ResolvedGeneric, Type, TypeBindings, -}; - pub use self::errors::Source; - -pub struct TypeChecker<'interner> { - interner: &'interner mut NodeInterner, - errors: Vec, - current_function: Option, - - /// Trait constraints are collected during type checking until they are - /// verified at the end of a function. This is because constraints arise - /// on each variable, but it is only until function calls when the types - /// needed for the trait constraint may become known. - trait_constraints: Vec<(TraitConstraint, ExprId)>, - - /// All type variables created in the current function. - /// This map is used to default any integer type variables at the end of - /// a function (before checking trait constraints) if a type wasn't already chosen. - type_variables: Vec, -} - -/// Type checks a function and assigns the -/// appropriate types to expressions in a side table -pub fn type_check_func(interner: &mut NodeInterner, func_id: FuncId) -> Vec { - let meta = interner.function_meta(&func_id); - let declared_return_type = meta.return_type().clone(); - let can_ignore_ret = meta.is_stub(); - - let function_body_id = &interner.function(&func_id).as_expr(); - - let mut type_checker = TypeChecker::new(interner); - type_checker.current_function = Some(func_id); - - let meta = type_checker.interner.function_meta(&func_id); - let parameters = meta.parameters.clone(); - let expected_return_type = meta.return_type.clone(); - let expected_trait_constraints = meta.trait_constraints.clone(); - let name_span = meta.name.location.span; - - let mut errors = Vec::new(); - - // Temporarily add any impls in this function's `where` clause to scope - for constraint in &expected_trait_constraints { - let object = constraint.typ.clone(); - let trait_id = constraint.trait_id; - let generics = constraint.trait_generics.clone(); - - if !type_checker.interner.add_assumed_trait_implementation(object, trait_id, generics) { - if let Some(the_trait) = type_checker.interner.try_get_trait(trait_id) { - let trait_name = the_trait.name.to_string(); - let typ = constraint.typ.clone(); - let span = name_span; - errors.push(TypeCheckError::UnneededTraitConstraint { trait_name, typ, span }); - } - } - } - - // Bind each parameter to its annotated type. - // This is locally obvious, but it must be bound here so that the - // Definition object of the parameter in the NodeInterner is given the correct type. - for param in parameters { - check_if_type_is_valid_for_program_input(&type_checker, func_id, ¶m, &mut errors); - type_checker.bind_pattern(¶m.0, param.1); - } - - let function_last_type = type_checker.check_function_body(function_body_id); - // Check declared return type and actual return type - if !can_ignore_ret { - let (expr_span, empty_function) = function_info(type_checker.interner, function_body_id); - let func_span = type_checker.interner.expr_span(function_body_id); // XXX: We could be more specific and return the span of the last stmt, however stmts do not have spans yet - if let Type::TraitAsType(trait_id, _, generics) = &declared_return_type { - if type_checker - .interner - .lookup_trait_implementation(&function_last_type, *trait_id, generics) - .is_err() - { - let error = TypeCheckError::TypeMismatchWithSource { - expected: declared_return_type.clone(), - actual: function_last_type, - span: func_span, - source: Source::Return(expected_return_type, expr_span), - }; - errors.push(error); - } - } else { - function_last_type.unify_with_coercions( - &declared_return_type, - *function_body_id, - type_checker.interner, - &mut errors, - || { - let mut error = TypeCheckError::TypeMismatchWithSource { - expected: declared_return_type.clone(), - actual: function_last_type.clone(), - span: func_span, - source: Source::Return(expected_return_type, expr_span), - }; - - if empty_function { - error = error.add_context("implicitly returns `()` as its body has no tail or `return` expression"); - } - error - }, - ); - } - } - - // Default any type variables that still need defaulting. - // This is done before trait impl search since leaving them bindable can lead to errors - // when multiple impls are available. Instead we default first to choose the Field or u64 impl. - for typ in &type_checker.type_variables { - if let Type::TypeVariable(variable, kind) = typ.follow_bindings() { - let msg = "TypeChecker should only track defaultable type vars"; - variable.bind(kind.default_type().expect(msg)); - } - } - - // Verify any remaining trait constraints arising from the function body - for (mut constraint, expr_id) in std::mem::take(&mut type_checker.trait_constraints) { - let span = type_checker.interner.expr_span(&expr_id); - - if matches!(&constraint.typ, Type::MutableReference(_)) { - let (_, dereferenced_typ) = - type_checker.insert_auto_dereferences(expr_id, constraint.typ.clone()); - constraint.typ = dereferenced_typ; - } - - type_checker.verify_trait_constraint( - &constraint.typ, - constraint.trait_id, - &constraint.trait_generics, - expr_id, - span, - ); - } - - // Now remove all the `where` clause constraints we added - for constraint in &expected_trait_constraints { - type_checker.interner.remove_assumed_trait_implementations_for_trait(constraint.trait_id); - } - - errors.append(&mut type_checker.errors); - errors -} - -/// Only sized types are valid to be used as main's parameters or the parameters to a contract -/// function. If the given type is not sized (e.g. contains a slice or NamedGeneric type), an -/// error is issued. -fn check_if_type_is_valid_for_program_input( - type_checker: &TypeChecker<'_>, - func_id: FuncId, - param: &Param, - errors: &mut Vec, -) { - let meta = type_checker.interner.function_meta(&func_id); - if (meta.is_entry_point && !param.1.is_valid_for_program_input()) - || (meta.has_inline_attribute && !param.1.is_valid_non_inlined_function_input()) - { - let span = param.0.span(); - errors.push(TypeCheckError::InvalidTypeForEntryPoint { span }); - } -} - -fn function_info(interner: &NodeInterner, function_body_id: &ExprId) -> (noirc_errors::Span, bool) { - let (expr_span, empty_function) = - if let HirExpression::Block(block) = interner.expression(function_body_id) { - let last_stmt = block.statements().last(); - let mut span = interner.expr_span(function_body_id); - - if let Some(last_stmt) = last_stmt { - if let HirStatement::Expression(expr) = interner.statement(last_stmt) { - span = interner.expr_span(&expr); - } - } - - (span, last_stmt.is_none()) - } else { - (interner.expr_span(function_body_id), false) - }; - (expr_span, empty_function) -} - -/// Checks that the type of a function in a trait impl matches the type -/// of the corresponding function declaration in the trait itself. -/// -/// To do this, given a trait such as: -/// `trait Foo { fn foo(...); }` -/// -/// And an impl such as: -/// `impl Foo for Bar { fn foo(...); } ` -/// -/// We have to substitute: -/// - Self for Bar -/// - A for D -/// - B for F -/// -/// Before we can type check. Finally, we must also check that the unification -/// result does not introduce any new bindings. This can happen if the impl -/// function's type is more general than that of the trait function. E.g. -/// `fn baz(a: A, b: B)` when the impl required `fn baz(a: A, b: A)`. -/// -/// This does not type check the body of the impl function. -pub(crate) fn check_trait_impl_method_matches_declaration( - interner: &mut NodeInterner, - function: FuncId, -) -> Vec { - let meta = interner.function_meta(&function); - let method_name = interner.function_name(&function); - let mut errors = Vec::new(); - - let definition_type = meta.typ.as_monotype(); - - let impl_ = - meta.trait_impl.expect("Trait impl function should have a corresponding trait impl"); - - // If the trait implementation is not defined in the interner then there was a previous - // error in resolving the trait path and there is likely no trait for this impl. - let Some(impl_) = interner.try_get_trait_implementation(impl_) else { - return errors; - }; - - let impl_ = impl_.borrow(); - let trait_info = interner.get_trait(impl_.trait_id); - - let mut bindings = TypeBindings::new(); - bindings.insert( - trait_info.self_type_typevar_id, - (trait_info.self_type_typevar.clone(), impl_.typ.clone()), - ); - - if trait_info.generics.len() != impl_.trait_generics.len() { - let expected = trait_info.generics.len(); - let found = impl_.trait_generics.len(); - let span = impl_.ident.span(); - let item = trait_info.name.to_string(); - errors.push(TypeCheckError::GenericCountMismatch { item, expected, found, span }); - } - - // Substitute each generic on the trait with the corresponding generic on the impl - for (generic, arg) in trait_info.generics.iter().zip(&impl_.trait_generics) { - bindings.insert(generic.type_var.id(), (generic.type_var.clone(), arg.clone())); - } - - // If this is None, the trait does not have the corresponding function. - // This error should have been caught in name resolution already so we don't - // issue an error for it here. - if let Some(trait_fn_id) = trait_info.method_ids.get(method_name) { - let trait_fn_meta = interner.function_meta(trait_fn_id); - - if trait_fn_meta.direct_generics.len() != meta.direct_generics.len() { - let expected = trait_fn_meta.direct_generics.len(); - let found = meta.direct_generics.len(); - let span = meta.name.location.span; - let item = method_name.to_string(); - errors.push(TypeCheckError::GenericCountMismatch { item, expected, found, span }); - } - - // Substitute each generic on the trait function with the corresponding generic on the impl function - for ( - ResolvedGeneric { type_var: trait_fn_generic, .. }, - ResolvedGeneric { name, type_var: impl_fn_generic, .. }, - ) in trait_fn_meta.direct_generics.iter().zip(&meta.direct_generics) - { - let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone(), Kind::Normal); - bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), arg)); - } - - let (declaration_type, _) = trait_fn_meta.typ.instantiate_with_bindings(bindings, interner); - - check_function_type_matches_expected_type( - &declaration_type, - definition_type, - method_name, - &meta.parameters, - meta.name.location.span, - &trait_info.name.0.contents, - &mut errors, - ); - } - - errors -} - -fn check_function_type_matches_expected_type( - expected: &Type, - actual: &Type, - method_name: &str, - actual_parameters: &Parameters, - span: Span, - trait_name: &str, - errors: &mut Vec, -) { - let mut bindings = TypeBindings::new(); - // Shouldn't need to unify envs, they should always be equal since they're both free functions - if let (Type::Function(params_a, ret_a, _env_a), Type::Function(params_b, ret_b, _env_b)) = - (expected, actual) - { - if params_a.len() == params_b.len() { - for (i, (a, b)) in params_a.iter().zip(params_b.iter()).enumerate() { - if a.try_unify(b, &mut bindings).is_err() { - errors.push(TypeCheckError::TraitMethodParameterTypeMismatch { - method_name: method_name.to_string(), - expected_typ: a.to_string(), - actual_typ: b.to_string(), - parameter_span: actual_parameters.0[i].0.span(), - parameter_index: i + 1, - }); - } - } - - if ret_b.try_unify(ret_a, &mut bindings).is_err() { - errors.push(TypeCheckError::TypeMismatch { - expected_typ: ret_a.to_string(), - expr_typ: ret_b.to_string(), - expr_span: span, - }); - } - } else { - errors.push(TypeCheckError::MismatchTraitImplNumParameters { - actual_num_parameters: params_b.len(), - expected_num_parameters: params_a.len(), - trait_name: trait_name.to_string(), - method_name: method_name.to_string(), - span, - }); - } - } - - // If result bindings is not empty, a type variable was bound which means the two - // signatures were not a perfect match. Note that this relies on us already binding - // all the expected generics to each other prior to this check. - if !bindings.is_empty() { - let expected_typ = expected.to_string(); - let expr_typ = actual.to_string(); - errors.push(TypeCheckError::TypeMismatch { expected_typ, expr_typ, expr_span: span }); - } -} - -impl<'interner> TypeChecker<'interner> { - fn new(interner: &'interner mut NodeInterner) -> Self { - Self { - interner, - errors: Vec::new(), - trait_constraints: Vec::new(), - type_variables: Vec::new(), - current_function: None, - } - } - - fn check_function_body(&mut self, body: &ExprId) -> Type { - self.check_expression(body) - } - - pub fn check_global( - id: GlobalId, - interner: &'interner mut NodeInterner, - ) -> Vec { - let mut this = Self { - interner, - errors: Vec::new(), - trait_constraints: Vec::new(), - type_variables: Vec::new(), - current_function: None, - }; - let statement = this.interner.get_global(id).let_statement; - this.check_statement(&statement); - this.errors - } - - /// Wrapper of Type::unify using self.errors - fn unify( - &mut self, - actual: &Type, - expected: &Type, - make_error: impl FnOnce() -> TypeCheckError, - ) { - actual.unify(expected, &mut self.errors, make_error); - } - - /// Wrapper of Type::unify_with_coercions using self.errors - fn unify_with_coercions( - &mut self, - actual: &Type, - expected: &Type, - expression: ExprId, - make_error: impl FnOnce() -> TypeCheckError, - ) { - actual.unify_with_coercions( - expected, - expression, - self.interner, - &mut self.errors, - make_error, - ); - } - - /// Return a fresh integer or field type variable and log it - /// in self.type_variables to default it later. - fn polymorphic_integer_or_field(&mut self) -> Type { - let typ = Type::polymorphic_integer_or_field(self.interner); - self.type_variables.push(typ.clone()); - typ - } - - /// Return a fresh integer type variable and log it - /// in self.type_variables to default it later. - fn polymorphic_integer(&mut self) -> Type { - let typ = Type::polymorphic_integer(self.interner); - self.type_variables.push(typ.clone()); - typ - } -} - -// XXX: These tests are all manual currently. -/// We can either build a test apparatus or pass raw code through the resolver -#[cfg(test)] -pub mod test { - use std::collections::{BTreeMap, HashMap}; - use std::vec; - - use fm::FileId; - use iter_extended::btree_map; - use noirc_errors::{Location, Span}; - - use crate::ast::{BinaryOpKind, FunctionKind, FunctionReturnType, Path, Visibility}; - use crate::graph::CrateId; - use crate::hir::def_map::{ModuleData, ModuleId}; - use crate::hir::resolution::import::{ - PathResolution, PathResolutionError, PathResolutionResult, - }; - use crate::hir_def::expr::HirIdent; - use crate::hir_def::function::FunctionBody; - use crate::hir_def::stmt::HirLetStatement; - use crate::hir_def::stmt::HirPattern::Identifier; - use crate::hir_def::types::Type; - use crate::hir_def::{ - expr::{HirBinaryOp, HirBlockExpression, HirExpression, HirInfixExpression}, - function::{FuncMeta, HirFunction}, - stmt::HirStatement, - }; - use crate::node_interner::{ - DefinitionKind, FuncId, NodeInterner, ReferenceId, TraitId, TraitMethodId, - }; - use crate::{ - hir::{ - def_map::{CrateDefMap, LocalModuleId, ModuleDefId}, - resolution::{path_resolver::PathResolver, resolver::Resolver}, - }, - parse_program, - }; - - #[test] - fn basic_let() { - let mut interner = NodeInterner::default(); - interner.populate_dummy_operator_traits(); - - // Safety: The FileId in a location isn't used for tests - let file = FileId::default(); - let location = Location::new(Span::default(), file); - - // Add a simple let Statement into the interner - // let z = x + y; - // - // Push x variable - let x_id = interner.push_definition( - "x".into(), - false, - false, - DefinitionKind::Local(None), - location, - ); - - let x = HirIdent::non_trait_method(x_id, location); - - // Push y variable - let y_id = interner.push_definition( - "y".into(), - false, - false, - DefinitionKind::Local(None), - location, - ); - let y = HirIdent::non_trait_method(y_id, location); - - // Push z variable - let z_id = interner.push_definition( - "z".into(), - false, - false, - DefinitionKind::Local(None), - location, - ); - let z = HirIdent::non_trait_method(z_id, location); - - // Push x and y as expressions - let x_expr_id = interner.push_expr(HirExpression::Ident(x.clone(), None)); - let y_expr_id = interner.push_expr(HirExpression::Ident(y.clone(), None)); - - // Create Infix - let operator = HirBinaryOp { location, kind: BinaryOpKind::Add }; - let trait_id = TraitId(ModuleId::dummy_id()); - let trait_method_id = TraitMethodId { trait_id, method_index: 0 }; - let expr = HirInfixExpression { lhs: x_expr_id, operator, rhs: y_expr_id, trait_method_id }; - let expr_id = interner.push_expr(HirExpression::Infix(expr)); - interner.push_expr_location(expr_id, Span::single_char(0), file); - - interner.push_expr_location(x_expr_id, Span::single_char(0), file); - interner.push_expr_location(y_expr_id, Span::single_char(0), file); - - // Create let statement - let let_stmt = HirLetStatement { - pattern: Identifier(z), - r#type: Type::FieldElement, - expression: expr_id, - attributes: vec![], - comptime: false, - }; - let stmt_id = interner.push_stmt(HirStatement::Let(let_stmt)); - let expr_id = interner - .push_expr(HirExpression::Block(HirBlockExpression { statements: vec![stmt_id] })); - interner.push_expr_location(expr_id, Span::single_char(0), file); - - // Create function to enclose the let statement - let func = HirFunction::unchecked_from_expr(expr_id); - let func_id = interner.push_fn(func); - - let definition = DefinitionKind::Local(None); - let id = interner.push_definition("test_func".into(), false, false, definition, location); - let name = HirIdent::non_trait_method(id, location); - - // Add function meta - let func_meta = FuncMeta { - name, - kind: FunctionKind::Normal, - location, - typ: Type::Function( - vec![Type::FieldElement, Type::FieldElement], - Box::new(Type::Unit), - Box::new(Type::Unit), - ), - parameters: vec![ - (Identifier(x), Type::FieldElement, Visibility::Private), - (Identifier(y), Type::FieldElement, Visibility::Private), - ] - .into(), - return_visibility: Visibility::Private, - has_body: true, - struct_id: None, - trait_impl: None, - return_type: FunctionReturnType::Default(Span::default()), - trait_constraints: Vec::new(), - direct_generics: Vec::new(), - is_entry_point: true, - is_trait_function: false, - has_inline_attribute: false, - all_generics: Vec::new(), - parameter_idents: Vec::new(), - function_body: FunctionBody::Resolved, - source_crate: CrateId::dummy_id(), - }; - interner.push_fn_meta(func_meta, func_id); - - let errors = super::type_check_func(&mut interner, func_id); - assert!(errors.is_empty()); - } - - #[test] - #[should_panic] - fn basic_let_stmt() { - let src = r#" - fn main(x : Field) { - let k = [x,x]; - let _z = x + k; - } - "#; - - type_check_src_code(src, vec![String::from("main")]); - } - - #[test] - fn basic_index_expr() { - let src = r#" - fn main(x : Field) { - let k = [x,x]; - let _z = x + k[0]; - } - "#; - - type_check_src_code(src, vec![String::from("main")]); - } - #[test] - fn basic_call_expr() { - let src = r#" - fn main(x : Field) { - let _z = x + foo(x); - } - - fn foo(x : Field) -> Field { - x - } - "#; - - type_check_src_code(src, vec![String::from("main"), String::from("foo")]); - } - #[test] - fn basic_for_expr() { - let src = r#" - fn main(_x : Field) { - for _i in 0..10 { - for _k in 0..100 { - - } - } - } - - "#; - - type_check_src_code(src, vec![String::from("main")]); - } - #[test] - fn basic_closure() { - let src = r#" - fn main(x : Field) -> pub Field { - let closure = |y| y + x; - closure(x) - } - "#; - - type_check_src_code(src, vec![String::from("main")]); - } - - #[test] - fn closure_with_no_args() { - let src = r#" - fn main(x : Field) -> pub Field { - let closure = || x; - closure() - } - "#; - - type_check_src_code(src, vec![String::from("main")]); - } - - #[test] - fn fold_entry_point() { - let src = r#" - #[fold] - fn fold(x: &mut Field) -> Field { - *x - } - "#; - - type_check_src_code_errors_expected(src, vec![String::from("fold")], 1); - } - - #[test] - fn fold_numeric_generic() { - let src = r#" - #[fold] - fn fold(x: T) -> T { - x - } - "#; - - type_check_src_code(src, vec![String::from("fold")]); - } - // This is the same Stub that is in the resolver, maybe we can pull this out into a test module and re-use? - struct TestPathResolver(HashMap); - - impl PathResolver for TestPathResolver { - fn resolve( - &self, - _def_maps: &BTreeMap, - path: Path, - _path_references: &mut Option<&mut Vec>>, - ) -> PathResolutionResult { - // Not here that foo::bar and hello::foo::bar would fetch the same thing - let name = path.segments.last().unwrap(); - self.0 - .get(&name.0.contents) - .cloned() - .map(|module_def_id| PathResolution { module_def_id, error: None }) - .ok_or_else(move || PathResolutionError::Unresolved(name.clone())) - } - - fn local_module_id(&self) -> LocalModuleId { - LocalModuleId(noirc_arena::Index::unsafe_zeroed()) - } - - fn module_id(&self) -> ModuleId { - ModuleId { krate: CrateId::dummy_id(), local_id: self.local_module_id() } - } - } - - impl TestPathResolver { - fn insert_func(&mut self, name: String, func_id: FuncId) { - self.0.insert(name, func_id.into()); - } - } - - pub fn type_check_src_code(src: &str, func_namespace: Vec) -> (NodeInterner, FuncId) { - type_check_src_code_errors_expected(src, func_namespace, 0) - } - - // This function assumes that there is only one function and this is the - // func id that is returned - fn type_check_src_code_errors_expected( - src: &str, - func_namespace: Vec, - expected_num_type_check_errs: usize, - ) -> (NodeInterner, FuncId) { - let (program, errors) = parse_program(src); - let mut interner = NodeInterner::default(); - interner.populate_dummy_operator_traits(); - - if !errors.iter().all(|error| error.is_warning()) { - assert_eq!( - errors.len(), - 0, - "expected 0 parser errors, but got {}, errors: {:?}", - errors.len(), - errors - ); - } - - let func_ids = btree_map(&func_namespace, |name| { - (name.to_string(), interner.push_test_function_definition(name.into())) - }); - - let main_id = - *func_ids.get("main").unwrap_or_else(|| func_ids.first_key_value().unwrap().1); - - let mut path_resolver = TestPathResolver(HashMap::new()); - for (name, id) in func_ids.iter() { - path_resolver.insert_func(name.to_owned(), *id); - } - - let mut def_maps = BTreeMap::new(); - let file = FileId::default(); - - let mut modules = noirc_arena::Arena::default(); - let location = Location::new(Default::default(), file); - modules.insert(ModuleData::new(None, location, false)); - - def_maps.insert( - CrateId::dummy_id(), - CrateDefMap { - root: path_resolver.local_module_id(), - modules, - krate: CrateId::dummy_id(), - extern_prelude: BTreeMap::new(), - }, - ); - - for nf in program.into_sorted().functions { - let resolver = Resolver::new(&mut interner, &path_resolver, &def_maps, file); - - let function_id = *func_ids.get(nf.name()).unwrap(); - let (hir_func, func_meta, resolver_errors) = resolver.resolve_function(nf, function_id); - - interner.push_fn_meta(func_meta, function_id); - interner.update_fn(function_id, hir_func); - assert_eq!(resolver_errors, vec![]); - } - - // Type check section - let mut errors = Vec::new(); - - for function in func_ids.values() { - errors.extend(super::type_check_func(&mut interner, *function)); - } - - assert_eq!( - errors.len(), - expected_num_type_check_errs, - "expected {} type check errors, but got {}, errors: {:?}", - expected_num_type_check_errs, - errors.len(), - errors - ); - - (interner, main_id) - } -} +pub use errors::{NoMatchingImplFoundError, TypeCheckError}; diff --git a/compiler/noirc_frontend/src/hir/type_check/stmt.rs b/compiler/noirc_frontend/src/hir/type_check/stmt.rs deleted file mode 100644 index 9abd1b34690..00000000000 --- a/compiler/noirc_frontend/src/hir/type_check/stmt.rs +++ /dev/null @@ -1,395 +0,0 @@ -use acvm::acir::AcirField; -use iter_extended::vecmap; -use noirc_errors::Span; - -use crate::ast::UnaryOp; -use crate::hir_def::expr::{HirExpression, HirIdent, HirLiteral}; -use crate::hir_def::stmt::{ - HirAssignStatement, HirConstrainStatement, HirForStatement, HirLValue, HirLetStatement, - HirPattern, HirStatement, -}; -use crate::hir_def::types::Type; -use crate::node_interner::{DefinitionId, ExprId, StmtId}; - -use super::errors::{Source, TypeCheckError}; -use super::TypeChecker; - -impl<'interner> TypeChecker<'interner> { - /// Type checks a statement and all expressions/statements contained within. - /// - /// All statements have a unit type `()` as their type so the type of the statement - /// is not interesting. Type checking must still be done on statements to ensure any - /// expressions used within them are typed correctly. - pub(crate) fn check_statement(&mut self, stmt_id: &StmtId) -> Type { - match self.interner.statement(stmt_id) { - // Lets lay out a convincing argument that the handling of - // SemiExpressions and Expressions below is correct. - // - // The only time you will get a Semi expression is if - // you have an expression by itself - // - // Example: - // - // 5; or x; or x+a; - // - // In these cases, you cannot even get the expr_id because - // it is not bound to anything. We could therefore. - // - // However since TypeChecking checks the return type of the last statement - // the type checker could in the future incorrectly return the type. - // - // As it stands, this is also impossible because the ret_type function - // does not use the interner to get the type. It returns Unit. - // - // The reason why we still modify the database, is to make sure it is future-proof - HirStatement::Expression(expr_id) => { - return self.check_expression(&expr_id); - } - HirStatement::Semi(expr_id) => { - self.check_expression(&expr_id); - } - HirStatement::Let(let_stmt) => self.check_let_stmt(let_stmt), - HirStatement::Constrain(constrain_stmt) => self.check_constrain_stmt(constrain_stmt), - HirStatement::Assign(assign_stmt) => self.check_assign_stmt(assign_stmt, stmt_id), - HirStatement::For(for_loop) => self.check_for_loop(for_loop), - HirStatement::Comptime(statement) => return self.check_statement(&statement), - HirStatement::Break | HirStatement::Continue | HirStatement::Error => (), - } - Type::Unit - } - - fn check_for_loop(&mut self, for_loop: HirForStatement) { - let start_range_type = self.check_expression(&for_loop.start_range); - let end_range_type = self.check_expression(&for_loop.end_range); - - let start_span = self.interner.expr_span(&for_loop.start_range); - let end_span = self.interner.expr_span(&for_loop.end_range); - - // Check that start range and end range have the same types - let range_span = start_span.merge(end_span); - self.unify(&start_range_type, &end_range_type, || TypeCheckError::TypeMismatch { - expected_typ: start_range_type.to_string(), - expr_typ: end_range_type.to_string(), - expr_span: range_span, - }); - - let expected_type = self.polymorphic_integer(); - - self.unify(&start_range_type, &expected_type, || TypeCheckError::TypeCannotBeUsed { - typ: start_range_type.clone(), - place: "for loop", - span: range_span, - }); - - self.interner.push_definition_type(for_loop.identifier.id, start_range_type); - - self.check_expression(&for_loop.block); - } - - /// Associate a given HirPattern with the given Type, and remember - /// this association in the NodeInterner. - pub(crate) fn bind_pattern(&mut self, pattern: &HirPattern, typ: Type) { - match pattern { - HirPattern::Identifier(ident) => self.interner.push_definition_type(ident.id, typ), - HirPattern::Mutable(pattern, _) => self.bind_pattern(pattern, typ), - HirPattern::Tuple(fields, location) => match typ.follow_bindings() { - Type::Tuple(field_types) if field_types.len() == fields.len() => { - for (field, field_type) in fields.iter().zip(field_types) { - self.bind_pattern(field, field_type); - } - } - Type::Error => (), - other => { - let expected = - Type::Tuple(vecmap(fields, |_| self.interner.next_type_variable())); - - self.errors.push(TypeCheckError::TypeMismatchWithSource { - expected, - actual: other, - span: location.span, - source: Source::Assignment, - }); - } - }, - HirPattern::Struct(struct_type, fields, location) => { - self.unify(struct_type, &typ, || TypeCheckError::TypeMismatchWithSource { - expected: struct_type.clone(), - actual: typ.clone(), - span: location.span, - source: Source::Assignment, - }); - - if let Type::Struct(struct_type, generics) = struct_type.follow_bindings() { - let struct_type = struct_type.borrow(); - - for (field_name, field_pattern) in fields { - if let Some((type_field, _)) = - struct_type.get_field(&field_name.0.contents, &generics) - { - self.bind_pattern(field_pattern, type_field); - } - } - } - } - } - } - - fn check_assign_stmt(&mut self, assign_stmt: HirAssignStatement, stmt_id: &StmtId) { - let expr_type = self.check_expression(&assign_stmt.expression); - let span = self.interner.expr_span(&assign_stmt.expression); - let (lvalue_type, new_lvalue, mutable) = self.check_lvalue(&assign_stmt.lvalue, span); - - if !mutable { - let (name, span) = self.get_lvalue_name_and_span(&assign_stmt.lvalue); - self.errors.push(TypeCheckError::VariableMustBeMutable { name, span }); - } - - // Must push new lvalue to the interner, we've resolved any field indices - self.interner.update_statement(stmt_id, |stmt| match stmt { - HirStatement::Assign(assign) => assign.lvalue = new_lvalue, - _ => unreachable!("statement is known to be assignment"), - }); - - let span = self.interner.expr_span(&assign_stmt.expression); - self.unify_with_coercions(&expr_type, &lvalue_type, assign_stmt.expression, || { - TypeCheckError::TypeMismatchWithSource { - actual: expr_type.clone(), - expected: lvalue_type.clone(), - span, - source: Source::Assignment, - } - }); - } - - fn get_lvalue_name_and_span(&self, lvalue: &HirLValue) -> (String, Span) { - match lvalue { - HirLValue::Ident(name, _) => { - let span = name.location.span; - - if let Some(definition) = self.interner.try_definition(name.id) { - (definition.name.clone(), span) - } else { - ("(undeclared variable)".into(), span) - } - } - HirLValue::MemberAccess { object, .. } => self.get_lvalue_name_and_span(object), - HirLValue::Index { array, .. } => self.get_lvalue_name_and_span(array), - HirLValue::Dereference { lvalue, .. } => self.get_lvalue_name_and_span(lvalue), - } - } - - /// Type check an lvalue - the left hand side of an assignment statement. - fn check_lvalue(&mut self, lvalue: &HirLValue, assign_span: Span) -> (Type, HirLValue, bool) { - match lvalue { - HirLValue::Ident(ident, _) => { - let mut mutable = true; - - let typ = if ident.id == DefinitionId::dummy_id() { - Type::Error - } else { - if let Some(definition) = self.interner.try_definition(ident.id) { - mutable = definition.mutable; - } - - let typ = self.interner.definition_type(ident.id).instantiate(self.interner).0; - typ.follow_bindings() - }; - - (typ.clone(), HirLValue::Ident(ident.clone(), typ), mutable) - } - HirLValue::MemberAccess { object, field_name, location, .. } => { - let (lhs_type, object, mut mutable) = self.check_lvalue(object, assign_span); - let mut object = Box::new(object); - let field_name = field_name.clone(); - - let object_ref = &mut object; - let mutable_ref = &mut mutable; - let location = *location; - - let dereference_lhs = move |_: &mut Self, _, element_type| { - // We must create a temporary value first to move out of object_ref before - // we eventually reassign to it. - let id = DefinitionId::dummy_id(); - let ident = HirIdent::non_trait_method(id, location); - let tmp_value = HirLValue::Ident(ident, Type::Error); - - let lvalue = std::mem::replace(object_ref, Box::new(tmp_value)); - *object_ref = - Box::new(HirLValue::Dereference { lvalue, element_type, location }); - *mutable_ref = true; - }; - - let name = &field_name.0.contents; - let (object_type, field_index) = self - .check_field_access(&lhs_type, name, field_name.span(), Some(dereference_lhs)) - .unwrap_or((Type::Error, 0)); - - let field_index = Some(field_index); - let typ = object_type.clone(); - let lvalue = - HirLValue::MemberAccess { object, field_name, field_index, typ, location }; - (object_type, lvalue, mutable) - } - HirLValue::Index { array, index, location, .. } => { - let index_type = self.check_expression(index); - let expr_span = self.interner.expr_span(index); - let location = *location; - - index_type.unify(&self.polymorphic_integer_or_field(), &mut self.errors, || { - TypeCheckError::TypeMismatch { - expected_typ: "an integer".to_owned(), - expr_typ: index_type.to_string(), - expr_span, - } - }); - - let (mut lvalue_type, mut lvalue, mut mutable) = - self.check_lvalue(array, assign_span); - - // Before we check that the lvalue is an array, try to dereference it as many times - // as needed to unwrap any &mut wrappers. - while let Type::MutableReference(element) = lvalue_type.follow_bindings() { - let element_type = element.as_ref().clone(); - lvalue = - HirLValue::Dereference { lvalue: Box::new(lvalue), element_type, location }; - lvalue_type = *element; - // We know this value to be mutable now since we found an `&mut` - mutable = true; - } - - let typ = match lvalue_type.follow_bindings() { - Type::Array(_, elem_type) => *elem_type, - Type::Slice(elem_type) => *elem_type, - Type::Error => Type::Error, - Type::String(_) => { - let (_lvalue_name, lvalue_span) = self.get_lvalue_name_and_span(&lvalue); - self.errors.push(TypeCheckError::StringIndexAssign { span: lvalue_span }); - Type::Error - } - other => { - // TODO: Need a better span here - self.errors.push(TypeCheckError::TypeMismatch { - expected_typ: "array".to_string(), - expr_typ: other.to_string(), - expr_span: assign_span, - }); - Type::Error - } - }; - - let array = Box::new(lvalue); - (typ.clone(), HirLValue::Index { array, index: *index, typ, location }, mutable) - } - HirLValue::Dereference { lvalue, element_type: _, location } => { - let (reference_type, lvalue, _) = self.check_lvalue(lvalue, assign_span); - let lvalue = Box::new(lvalue); - let location = *location; - - let element_type = Type::type_variable(self.interner.next_type_variable_id()); - let expected_type = Type::MutableReference(Box::new(element_type.clone())); - - self.unify(&reference_type, &expected_type, || TypeCheckError::TypeMismatch { - expected_typ: expected_type.to_string(), - expr_typ: reference_type.to_string(), - expr_span: assign_span, - }); - - // Dereferences are always mutable since we already type checked against a &mut T - ( - element_type.clone(), - HirLValue::Dereference { lvalue, element_type, location }, - true, - ) - } - } - } - - fn check_let_stmt(&mut self, let_stmt: HirLetStatement) { - let resolved_type = self.check_declaration(let_stmt.expression, let_stmt.r#type); - - // Set the type of the pattern to be equal to the annotated type - self.bind_pattern(&let_stmt.pattern, resolved_type); - } - - fn check_constrain_stmt(&mut self, stmt: HirConstrainStatement) { - let expr_type = self.check_expression(&stmt.0); - let expr_span = self.interner.expr_span(&stmt.0); - - // Must type check the assertion message expression so that we instantiate bindings - stmt.2.map(|assert_msg_expr| self.check_expression(&assert_msg_expr)); - - self.unify(&expr_type, &Type::Bool, || TypeCheckError::TypeMismatch { - expr_typ: expr_type.to_string(), - expected_typ: Type::Bool.to_string(), - expr_span, - }); - } - - /// All declaration statements check that the user specified type(UST) is equal to the - /// expression on the RHS, unless the UST is unspecified in which case - /// the type of the declaration is inferred to match the RHS. - fn check_declaration(&mut self, rhs_expr: ExprId, annotated_type: Type) -> Type { - // Type check the expression on the RHS - let expr_type = self.check_expression(&rhs_expr); - - // First check if the LHS is unspecified - // If so, then we give it the same type as the expression - if annotated_type != Type::Error { - // Now check if LHS is the same type as the RHS - // Importantly, we do not coerce any types implicitly - let expr_span = self.interner.expr_span(&rhs_expr); - - self.unify_with_coercions(&expr_type, &annotated_type, rhs_expr, || { - TypeCheckError::TypeMismatch { - expected_typ: annotated_type.to_string(), - expr_typ: expr_type.to_string(), - expr_span, - } - }); - if annotated_type.is_unsigned() { - self.lint_overflowing_uint(&rhs_expr, &annotated_type); - } - annotated_type - } else { - expr_type - } - } - - /// Check if an assignment is overflowing with respect to `annotated_type` - /// in a declaration statement where `annotated_type` is an unsigned integer - fn lint_overflowing_uint(&mut self, rhs_expr: &ExprId, annotated_type: &Type) { - let expr = self.interner.expression(rhs_expr); - let span = self.interner.expr_span(rhs_expr); - match expr { - HirExpression::Literal(HirLiteral::Integer(value, false)) => { - let v = value.to_u128(); - if let Type::Integer(_, bit_count) = annotated_type { - let bit_count: u32 = (*bit_count).into(); - let max = 1 << bit_count; - if v >= max { - self.errors.push(TypeCheckError::OverflowingAssignment { - expr: -value, - ty: annotated_type.clone(), - range: format!("0..={}", max - 1), - span, - }); - }; - }; - } - HirExpression::Prefix(expr) => { - self.lint_overflowing_uint(&expr.rhs, annotated_type); - if matches!(expr.operator, UnaryOp::Minus) { - self.errors.push(TypeCheckError::InvalidUnaryOp { - kind: "annotated_type".to_string(), - span, - }); - } - } - HirExpression::Infix(expr) => { - self.lint_overflowing_uint(&expr.lhs, annotated_type); - self.lint_overflowing_uint(&expr.rhs, annotated_type); - } - _ => {} - } - } -}