// SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Gleam contributors use std::sync::Arc; use camino::Utf8PathBuf; use ecow::EcoString; use crate::analyse::TargetSupport; use crate::build::{ExpressionPosition, Origin, Target}; use crate::config::PackageConfig; use crate::line_numbers::LineNumbers; use crate::type_::error::{VariableDeclaration, VariableOrigin, VariableSyntax}; use crate::type_::expression::{FunctionDefinition, Purity}; use crate::type_::{Deprecation, PRELUDE_MODULE_NAME, Problems}; use crate::warning::WarningEmitter; use crate::{ ast::{BinOp, SrcSpan, TypedExpr}, build::Located, type_::{ self, AccessorsMap, EnvironmentArguments, ExprTyper, FieldMap, ModuleValueConstructor, RecordAccessor, Type, ValueConstructor, ValueConstructorVariant, }, uid::UniqueIdGenerator, warning::TypeWarningEmitter, }; use super::{Publicity, Statement, TypedModule, TypedStatement}; fn compile_module(src: &str) -> TypedModule { use crate::type_::build_prelude; let parsed = crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null()) .expect("syntax error"); let ast = parsed.module; let ids = UniqueIdGenerator::new(); let mut config = PackageConfig::default(); config.name = "thepackage".into(); let mut modules = im::HashMap::new(); // DUPE: preludeinsertion // TODO: Currently we do this here and also in the tests. It would be better // to have one place where we create all this required state for use in each // place. let _ = modules.insert(PRELUDE_MODULE_NAME.into(), build_prelude(&ids)); let line_numbers = LineNumbers::new(src); let mut config = PackageConfig::default(); config.name = "thepackage".into(); crate::analyse::ModuleAnalyzerConstructor::<()> { target: Target::Erlang, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &std::collections::HashMap::new(), dev_dependencies: &std::collections::HashSet::new(), target_support: TargetSupport::Enforced, package_config: &config, } .infer_module(ast, line_numbers, "".into()) .expect("should successfully infer") } fn get_bare_expression(statement: &TypedStatement) -> &TypedExpr { match statement { Statement::Expression(expression) => expression, Statement::Use(_) | Statement::Assignment(_) | Statement::Assert(_) => { panic!("Expected expression, got {statement:?}") } } } fn cat_type() -> Arc { Arc::new(Type::Named { publicity: Publicity::Public, package: "mypackage".into(), module: "mymod".into(), name: "Cat".into(), arguments: vec![], inferred_variant: None, }) } fn compile_expression(src: &str) -> TypedStatement { let ast = crate::parse::parse_statement_sequence(src).expect("syntax error"); let mut modules = im::HashMap::new(); let ids = UniqueIdGenerator::new(); // DUPE: preludeinsertion // TODO: Currently we do this here and also in the tests. It would be better // to have one place where we create all this required state for use in each // place. let _ = modules.insert(PRELUDE_MODULE_NAME.into(), type_::build_prelude(&ids)); let dev_dependencies = std::collections::HashSet::new(); let mut environment = EnvironmentArguments { ids, current_package: "thepackage".into(), gleam_version: None, current_module: "mymod".into(), target: Target::Erlang, importable_modules: &modules, target_support: TargetSupport::Enforced, current_origin: Origin::Src, dev_dependencies: &dev_dependencies, } .build(); // Insert a cat record to use in the tests let cat_type = cat_type(); let variant = ValueConstructorVariant::Record { documentation: Some("wibble".into()), variants_count: 1, name: "Cat".into(), arity: 2, location: SrcSpan { start: 12, end: 15 }, field_map: Some(FieldMap { arity: 2, fields: [("name".into(), 0), ("age".into(), 1)].into(), }), module: "mymod".into(), variant_index: 0, }; environment.insert_variable( "Cat".into(), variant, type_::fn_(vec![type_::string(), type_::int()], cat_type.clone()), Publicity::Public, Deprecation::NotDeprecated, ); let accessors = [ ( "name".into(), RecordAccessor { index: 0, label: "name".into(), type_: type_::string(), documentation: None, }, ), ( "age".into(), RecordAccessor { index: 1, label: "age".into(), type_: type_::int(), documentation: None, }, ), ]; environment.insert_accessors( "Cat".into(), AccessorsMap { publicity: Publicity::Public, type_: cat_type, shared_accessors: accessors.clone().into(), variant_specific_accessors: vec![accessors.into()], variant_positional_accessors: vec![vec![]], }, ); let mut problems = Problems::new(); ExprTyper::new( &mut environment, FunctionDefinition { has_body: true, has_erlang_external: false, has_javascript_external: false, has_cranelift_external: false, }, &mut problems, ) .infer_statements(ast) .first() .clone() } #[test] fn find_node_todo() { let statement = compile_expression(r#" todo "#); let expr = get_bare_expression(&statement); assert_eq!(expr.find_node(0), None); assert_eq!( expr.find_node(1), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(4), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(5), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!(expr.find_node(6), None); } #[test] fn find_node_todo_with_string() { let statement = compile_expression(r#" todo as "ok" "#); let expr = get_bare_expression(&statement); let message = TypedExpr::String { location: SrcSpan { start: 9, end: 13 }, type_: type_::string(), value: "ok".into(), }; assert_eq!(expr.find_node(0), None); assert_eq!( expr.find_node(1), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(12), Some(Located::Expression { expression: &message, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(13), Some(Located::Expression { expression: &message, position: ExpressionPosition::Expression }) ); assert_eq!(expr.find_node(14), None); } #[test] fn find_node_string() { let statement = compile_expression(r#" "ok" "#); let expr = get_bare_expression(&statement); assert_eq!(expr.find_node(0), None); assert_eq!( expr.find_node(1), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(4), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(5), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!(expr.find_node(6), None); } #[test] fn find_node_float() { let statement = compile_expression(r#" 1.02 "#); let expr = get_bare_expression(&statement); assert_eq!(expr.find_node(0), None); assert_eq!( expr.find_node(1), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(4), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(5), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!(expr.find_node(6), None); } #[test] fn find_node_int() { let statement = compile_expression(r#" 1302 "#); let expr = get_bare_expression(&statement); assert_eq!(expr.find_node(0), None); assert_eq!( expr.find_node(1), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(4), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(5), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!(expr.find_node(6), None); } #[test] fn find_node_var() { let statement = compile_expression( r#"{let wibble = 1 wibble}"#, ); let expr = get_bare_expression(&statement); let int1 = TypedExpr::Int { location: SrcSpan { start: 14, end: 15 }, value: "1".into(), int_value: 1.into(), type_: type_::int(), }; let var = TypedExpr::Var { location: SrcSpan { start: 16, end: 22 }, constructor: ValueConstructor { deprecation: Deprecation::NotDeprecated, publicity: Publicity::Private, variant: ValueConstructorVariant::LocalVariable { location: SrcSpan { start: 5, end: 11 }, origin: VariableOrigin { syntax: VariableSyntax::Variable("wibble".into()), declaration: VariableDeclaration::LetPattern, }, }, type_: type_::int(), }, name: "wibble".into(), }; assert_eq!( expr.find_node(15), Some(Located::Expression { expression: &int1, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(16), Some(Located::Expression { expression: &var, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(21), Some(Located::Expression { expression: &var, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(22), Some(Located::Expression { expression: &var, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_sequence() { let block = compile_expression(r#"{ 1 2 3 }"#); assert!(block.find_node(0).is_none()); assert!(block.find_node(1).is_none()); assert!(block.find_node(2).is_some()); assert!(block.find_node(3).is_some()); assert!(block.find_node(4).is_some()); assert!(block.find_node(5).is_some()); assert!(block.find_node(6).is_some()); assert!(block.find_node(7).is_some()); } #[test] fn find_node_list() { let statement = compile_expression(r#"[1, 2, 3]"#); let list = get_bare_expression(&statement); let int1 = TypedExpr::Int { location: SrcSpan { start: 1, end: 2 }, type_: type_::int(), value: "1".into(), int_value: 1.into(), }; let int2 = TypedExpr::Int { location: SrcSpan { start: 4, end: 5 }, type_: type_::int(), value: "2".into(), int_value: 2.into(), }; let int3 = TypedExpr::Int { location: SrcSpan { start: 7, end: 8 }, type_: type_::int(), value: "3".into(), int_value: 3.into(), }; assert_eq!( list.find_node(0), Some(Located::Expression { expression: list, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(1), Some(Located::Expression { expression: &int1, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(2), Some(Located::Expression { expression: &int1, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(3), Some(Located::Expression { expression: list, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(4), Some(Located::Expression { expression: &int2, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(5), Some(Located::Expression { expression: &int2, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(6), Some(Located::Expression { expression: list, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(7), Some(Located::Expression { expression: &int3, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(8), Some(Located::Expression { expression: &int3, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(9), Some(Located::Expression { expression: list, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_tuple() { let statement = compile_expression(r#"#(1, 2, 3)"#); let tuple = get_bare_expression(&statement); let int1 = TypedExpr::Int { location: SrcSpan { start: 2, end: 3 }, type_: type_::int(), value: "1".into(), int_value: 1.into(), }; let int2 = TypedExpr::Int { location: SrcSpan { start: 5, end: 6 }, type_: type_::int(), value: "2".into(), int_value: 2.into(), }; let int3 = TypedExpr::Int { location: SrcSpan { start: 8, end: 9 }, type_: type_::int(), value: "3".into(), int_value: 3.into(), }; assert_eq!( tuple.find_node(0), Some(Located::Expression { expression: tuple, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(1), Some(Located::Expression { expression: tuple, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(2), Some(Located::Expression { expression: &int1, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(3), Some(Located::Expression { expression: &int1, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(4), Some(Located::Expression { expression: tuple, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(5), Some(Located::Expression { expression: &int2, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(6), Some(Located::Expression { expression: &int2, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(7), Some(Located::Expression { expression: tuple, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(8), Some(Located::Expression { expression: &int3, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(9), Some(Located::Expression { expression: &int3, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(10), Some(Located::Expression { expression: tuple, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_binop() { let statement = compile_expression(r#"1 + 2"#); let expr = get_bare_expression(&statement); assert!(expr.find_node(0).is_some()); assert!(expr.find_node(1).is_some()); assert!(expr.find_node(2).is_none()); assert!(expr.find_node(3).is_none()); assert!(expr.find_node(4).is_some()); assert!(expr.find_node(5).is_some()); } #[test] fn subtraction_is_left_associative_in_ast() { // `1 - 2 - 3` must parse as `(1 - 2) - 3`, not `1 - (2 - 3)`. let statement = compile_expression("1 - 2 - 3"); let expr = get_bare_expression(&statement); let TypedExpr::BinOp { operator: BinOp::SubInt, left, right, .. } = expr else { panic!("expected outer SubInt, got {expr:?}"); }; let TypedExpr::Int { value: right_val, .. } = right.as_ref() else { panic!("expected int on right, got {right:?}"); }; assert_eq!(right_val, "3"); let TypedExpr::BinOp { operator: BinOp::SubInt, left: ll, right: lr, .. } = left.as_ref() else { panic!("expected inner SubInt on left, got {left:?}"); }; let TypedExpr::Int { value: ll_val, .. } = ll.as_ref() else { panic!("expected int as left-left, got {ll:?}") }; let TypedExpr::Int { value: lr_val, .. } = lr.as_ref() else { panic!("expected int as left-right, got {lr:?}") }; assert_eq!(ll_val, "1"); assert_eq!(lr_val, "2"); } #[test] fn find_node_tuple_index() { let statement = compile_expression(r#"#(1).0"#); let expr = get_bare_expression(&statement); let int = TypedExpr::Int { location: SrcSpan { start: 2, end: 3 }, value: "1".into(), int_value: 1.into(), type_: type_::int(), }; assert_eq!( expr.find_node(2), Some(Located::Expression { expression: &int, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(5), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(6), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_module_select() { let expr = TypedExpr::ModuleSelect { location: SrcSpan { start: 1, end: 4 }, field_start: 2, type_: type_::int(), label: "label".into(), module_name: "name".into(), module_alias: "alias".into(), constructor: ModuleValueConstructor::Fn { module: "module".into(), name: "function".into(), external_erlang: None, external_javascript: None, location: SrcSpan { start: 1, end: 55 }, documentation: None, field_map: None, purity: Purity::Pure, }, }; assert_eq!(expr.find_node(0), None); assert_eq!( expr.find_node(1), Some(Located::ModuleName { location: SrcSpan::new(1, 6), module_name: "name".into(), module_alias: "alias".into(), layer: super::Layer::Value }) ); assert_eq!( expr.find_node(2), Some(Located::Expression { expression: &expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(3), Some(Located::Expression { expression: &expr, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_fn() { let statement = compile_expression("fn() { 1 }"); let expr = get_bare_expression(&statement); let int = TypedExpr::Int { location: SrcSpan { start: 7, end: 8 }, value: "1".into(), int_value: 1.into(), type_: type_::int(), }; assert_eq!( expr.find_node(0), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(6), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(7), Some(Located::Expression { expression: &int, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(8), Some(Located::Expression { expression: &int, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(9), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(10), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_call() { let statement = compile_expression("fn(_, _) { 1 }(1, 2)"); let expr = get_bare_expression(&statement); let return_ = TypedExpr::Int { location: SrcSpan { start: 11, end: 12 }, value: "1".into(), int_value: 1.into(), type_: type_::int(), }; let arg1 = TypedExpr::Int { location: SrcSpan { start: 15, end: 16 }, value: "1".into(), int_value: 1.into(), type_: type_::int(), }; let arg2 = TypedExpr::Int { location: SrcSpan { start: 18, end: 19 }, value: "2".into(), int_value: 2.into(), type_: type_::int(), }; let TypedExpr::Call { fun: called_function, arguments: function_arguments, .. } = expr else { panic!("Expression was not a function call"); }; assert_eq!( expr.find_node(11), Some(Located::Expression { expression: &return_, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(15), Some(Located::Expression { expression: &arg1, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(16), Some(Located::Expression { expression: &arg1, position: ExpressionPosition::ArgumentOrLabel { called_function, function_arguments } }) ); assert_eq!( expr.find_node(17), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(18), Some(Located::Expression { expression: &arg2, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(19), Some(Located::Expression { expression: &arg2, position: ExpressionPosition::ArgumentOrLabel { called_function, function_arguments } }) ); assert_eq!( expr.find_node(20), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_record_access() { let statement = compile_expression(r#"Cat("Nubi", 3).name"#); let access = get_bare_expression(&statement); let string = TypedExpr::String { location: SrcSpan { start: 4, end: 10 }, value: "Nubi".into(), type_: type_::string(), }; let int = TypedExpr::Int { location: SrcSpan { start: 12, end: 13 }, value: "3".into(), int_value: 3.into(), type_: type_::int(), }; assert_eq!( access.find_node(4), Some(Located::Expression { expression: &string, position: ExpressionPosition::Expression }) ); assert_eq!( access.find_node(9), Some(Located::Expression { expression: &string, position: ExpressionPosition::Expression }) ); assert_eq!( access.find_node(12), Some(Located::Expression { expression: &int, position: ExpressionPosition::Expression }) ); let label = Located::RecordAccessLabel { location: SrcSpan { start: 15, end: 19 }, field_type: type_::string(), label: "name".into(), record_type: cat_type(), documentation: None, }; assert_eq!(access.find_node(15), Some(label.clone())); assert_eq!(access.find_node(18), Some(label.clone())); assert_eq!(access.find_node(19), Some(label)); } #[test] fn find_node_record_update() { let statement = compile_expression(r#"Cat(..Cat("Nubi", 3), age: 4)"#); let update = get_bare_expression(&statement); let cat = TypedExpr::Var { location: SrcSpan { start: 0, end: 3 }, constructor: ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, variant: ValueConstructorVariant::Record { name: "Cat".into(), arity: 2, field_map: Some(FieldMap { arity: 2, fields: [(EcoString::from("age"), 1), (EcoString::from("name"), 0)].into(), }), location: SrcSpan { start: 12, end: 15 }, module: "mymod".into(), variants_count: 1, variant_index: 0, documentation: Some("wibble".into()), }, type_: type_::fn_( vec![type_::string(), type_::int()], type_::named("mypackage", "mymod", "Cat", Publicity::Public, vec![]), ), }, name: "Cat".into(), }; let int = TypedExpr::Int { location: SrcSpan { start: 27, end: 28 }, value: "4".into(), int_value: 4.into(), type_: type_::int(), }; assert_eq!( update.find_node(0), Some(Located::Expression { expression: &cat, position: ExpressionPosition::Expression }) ); assert_eq!( update.find_node(3), Some(Located::Expression { expression: &cat, position: ExpressionPosition::Expression }) ); assert_eq!( update.find_node(27), Some(Located::Expression { expression: &int, position: ExpressionPosition::Expression }) ); assert_eq!( update.find_node(28), Some(Located::Expression { expression: &int, position: ExpressionPosition::Expression }) ); assert_eq!( update.find_node(29), Some(Located::Expression { expression: update, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_case() { let statement = compile_expression( r#" case 1, 2 { _, _ -> 3 } "#, ); let case = get_bare_expression(&statement); let int1 = TypedExpr::Int { location: SrcSpan { start: 6, end: 7 }, value: "1".into(), int_value: 1.into(), type_: type_::int(), }; let int2 = TypedExpr::Int { location: SrcSpan { start: 9, end: 10 }, value: "2".into(), int_value: 2.into(), type_: type_::int(), }; let int3 = TypedExpr::Int { location: SrcSpan { start: 23, end: 24 }, value: "3".into(), int_value: 3.into(), type_: type_::int(), }; assert_eq!( case.find_node(1), Some(Located::Expression { expression: case, position: ExpressionPosition::Expression }) ); assert_eq!( case.find_node(6), Some(Located::Expression { expression: &int1, position: ExpressionPosition::Expression }) ); assert_eq!( case.find_node(9), Some(Located::Expression { expression: &int2, position: ExpressionPosition::Expression }) ); assert_eq!( case.find_node(23), Some(Located::Expression { expression: &int3, position: ExpressionPosition::Expression }) ); assert_eq!( case.find_node(25), Some(Located::Expression { expression: case, position: ExpressionPosition::Expression }) ); assert_eq!( case.find_node(26), Some(Located::Expression { expression: case, position: ExpressionPosition::Expression }) ); assert_eq!(case.find_node(27), None); } #[test] fn find_node_bool() { let statement = compile_expression(r#"!True"#); let negate = get_bare_expression(&statement); let bool = TypedExpr::Var { location: SrcSpan { start: 1, end: 5 }, constructor: ValueConstructor { deprecation: Deprecation::NotDeprecated, publicity: Publicity::Public, variant: ValueConstructorVariant::Record { documentation: None, variants_count: 2, name: "True".into(), arity: 0, field_map: None, location: SrcSpan { start: 0, end: 0 }, module: PRELUDE_MODULE_NAME.into(), variant_index: 0, }, type_: type_::bool_with_variant(Some(true)), }, name: "True".into(), }; assert_eq!( negate.find_node(0), Some(Located::Expression { expression: negate, position: ExpressionPosition::Expression }) ); assert_eq!( negate.find_node(1), Some(Located::Expression { expression: &bool, position: ExpressionPosition::Expression }) ); assert_eq!( negate.find_node(2), Some(Located::Expression { expression: &bool, position: ExpressionPosition::Expression }) ); assert_eq!( negate.find_node(3), Some(Located::Expression { expression: &bool, position: ExpressionPosition::Expression }) ); assert_eq!( negate.find_node(4), Some(Located::Expression { expression: &bool, position: ExpressionPosition::Expression }) ); assert_eq!( negate.find_node(5), Some(Located::Expression { expression: &bool, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_statement_fn() { let module = compile_module( r#" pub fn main() { Nil } "#, ); assert!(module.find_node(0).is_none()); assert!(module.find_node(1).is_none()); // The fn assert!(module.find_node(2).is_some()); assert!(module.find_node(24).is_some()); assert!(module.find_node(25).is_some()); assert!(module.find_node(26).is_none()); } #[test] fn find_node_statement_import() { let module = compile_module( r#" import gleam "#, ); assert!(module.find_node(0).is_none()); // The import assert!(module.find_node(1).is_some()); assert!(module.find_node(12).is_some()); assert!(module.find_node(13).is_some()); assert!(module.find_node(14).is_none()); } #[test] fn find_node_use() { let use_ = compile_expression( r#" use x <- fn(f) { f(1) } 124 "#, ); assert!(use_.find_node(0).is_none()); assert!(use_.find_node(1).is_some()); // The use assert!(use_.find_node(23).is_some()); assert!(use_.find_node(26).is_some()); // The int }