diff --git a/corelib/src/test/language_features/block_level_items_test.cairo b/corelib/src/test/language_features/block_level_items_test.cairo index d7ccde535a5..30d9933a07b 100644 --- a/corelib/src/test/language_features/block_level_items_test.cairo +++ b/corelib/src/test/language_features/block_level_items_test.cairo @@ -46,25 +46,25 @@ fn test_global_const_to_let_shadowing() { assert_eq!(A, 4); } -pub mod X { +pub mod SingleConstMod { pub const A: u8 = 1; } #[test] fn test_use_usage() { - use X::A; + use SingleConstMod::A; assert_eq!(A, 1); } #[test] fn test_use_usage_with_alias() { - use X::A as B; + use SingleConstMod::A as B; assert_eq!(B, 1); } #[test] fn test_use_constant_shadowing() { - use X::A; + use SingleConstMod::A; assert_eq!(A, 1); { const A: u8 = 4; @@ -73,17 +73,17 @@ fn test_use_constant_shadowing() { assert_eq!(A, 1); } -pub mod Y { +pub mod DoubleConstMod { pub const A: u8 = 4; pub const B: u8 = 6; } #[test] fn test_use_use_shadowing() { - use X::A; + use SingleConstMod::A; assert_eq!(A, 1); { - use Y::A; + use DoubleConstMod::A; assert_eq!(A, 4); } assert_eq!(A, 1); @@ -94,7 +94,7 @@ fn test_const_use_shadowing() { const A: u8 = 1; assert_eq!(A, 1); { - use Y::A; + use DoubleConstMod::A; assert_eq!(A, 4); } assert_eq!(A, 1); @@ -102,7 +102,7 @@ fn test_const_use_shadowing() { #[test] fn test_use_let_shadowing() { - use X::A; + use SingleConstMod::A; assert_eq!(A, 1); { let A = 4; @@ -116,7 +116,7 @@ fn test_let_use_shadowing() { let A = 1; assert_eq!(A, 1); { - use Y::A; + use DoubleConstMod::A; assert_eq!(A, 4); } assert_eq!(A, 1); @@ -124,7 +124,61 @@ fn test_let_use_shadowing() { #[test] fn test_multiple_use() { - use Y::{A, B}; + use DoubleConstMod::{A, B}; assert_eq!(A, 4); assert_eq!(B, 6); } + +pub mod GenericTypeMod { + pub struct S { + pub x: u8, + } + pub enum E { + A: u8, + B: u16, + } +} + +#[test] +fn test_type_struct_usage() { + use GenericTypeMod::S; + let s = S { x: 1 }; + assert_eq!(s.x, 1); +} + +#[test] +fn test_type_enum_usage() { + use GenericTypeMod::E; + let e = E::A(1); + match e { + E::A(val) => assert_eq!(val, 1), + E::B(_) => panic!("Shouldn't get here"), + } +} + +pub mod GenericTypeModGenerics { + pub struct S { + pub x: T, + } + pub enum E { + A: T, + B: u16, + } +} + +#[test] +fn test_type_struct_generic_usage() { + use GenericTypeModGenerics::S; + let s = S:: { x: 1 }; + assert_eq!(s.x, 1); +} + +#[test] +fn test_type_enum_generic_usage() { + use GenericTypeModGenerics::E; + let e = E::A::(1); + match e { + E::A(val) => assert_eq!(val, 1), + E::B(_) => panic!("Shouldn't get here"), + } +} diff --git a/crates/cairo-lang-semantic/src/diagnostic.rs b/crates/cairo-lang-semantic/src/diagnostic.rs index 5447498c4c5..0ba97f65b8e 100644 --- a/crates/cairo-lang-semantic/src/diagnostic.rs +++ b/crates/cairo-lang-semantic/src/diagnostic.rs @@ -490,6 +490,11 @@ impl DiagnosticEntry for SemanticDiagnostic { identifier_name ) } + SemanticDiagnosticKind::MultipleTypeDefinition(type_name) => { + format!(r#"Multiple definitions of type "{}"."#, type_name) + } + SemanticDiagnosticKind::UnusedType => "Unused type.".into(), + SemanticDiagnosticKind::UnsupportedUseItemInStatement => { "Unsupported use item in statement.".into() } @@ -899,7 +904,8 @@ impl DiagnosticEntry for SemanticDiagnostic { | SemanticDiagnosticKind::UnusedImport { .. } | SemanticDiagnosticKind::CallingShadowedFunction { .. } | SemanticDiagnosticKind::UnusedConstant - | SemanticDiagnosticKind::UnusedUse => Severity::Warning, + | SemanticDiagnosticKind::UnusedUse + | SemanticDiagnosticKind::UnusedType => Severity::Warning, SemanticDiagnosticKind::PluginDiagnostic(diag) => diag.severity, _ => Severity::Error, } @@ -1102,8 +1108,10 @@ pub enum SemanticDiagnosticKind { UnusedVariable, UnusedConstant, UnusedUse, + UnusedType, MultipleConstantDefinition(SmolStr), MultipleDefinitionforBinding(SmolStr), + MultipleTypeDefinition(SmolStr), UnsupportedUseItemInStatement, ConstGenericParamNotSupported, NegativeImplsNotEnabled, diff --git a/crates/cairo-lang-semantic/src/expr/compute.rs b/crates/cairo-lang-semantic/src/expr/compute.rs index 20f6a16b886..dadff726ee8 100644 --- a/crates/cairo-lang-semantic/src/expr/compute.rs +++ b/crates/cairo-lang-semantic/src/expr/compute.rs @@ -19,6 +19,7 @@ use cairo_lang_defs::plugin::MacroPluginMetadata; use cairo_lang_diagnostics::{skip_diagnostic, Maybe, ToOption}; use cairo_lang_filesystem::cfg::CfgSet; use cairo_lang_filesystem::ids::{FileKind, FileLongId, VirtualFile}; +use cairo_lang_proc_macros::DebugWithDb; use cairo_lang_syntax::node::ast::{ BinaryOperator, BlockOrIf, ClosureParamWrapper, ExprPtr, OptionReturnTypeClause, PatternListOr, PatternStructParam, UnaryOperator, @@ -77,7 +78,7 @@ use crate::semantic::{self, Binding, FunctionId, LocalVariable, TypeId, TypeLong use crate::substitution::SemanticRewriter; use crate::types::{ add_type_based_diagnostics, are_coupons_enabled, extract_fixed_size_array_size, peel_snapshots, - peel_snapshots_ex, resolve_type, verify_fixed_size_array_size, wrap_in_snapshots, + peel_snapshots_ex, resolve_type_ex, verify_fixed_size_array_size, wrap_in_snapshots, ClosureTypeLongId, ConcreteTypeId, }; use crate::usage::Usages; @@ -203,6 +204,9 @@ impl<'ctx> ComputationContext<'ctx> { for (var_name, var) in std::mem::take(&mut self.environment.variables) { self.add_unused_binding_warning(&var_name, &var); } + for (ty_name, statement_ty) in std::mem::take(&mut self.environment.types) { + self.add_unused_types_warning(&ty_name, &statement_ty); + } self.environment = parent.unwrap(); res } @@ -226,6 +230,13 @@ impl<'ctx> ComputationContext<'ctx> { } } + /// Adds warning for unused types if required. + fn add_unused_types_warning(&mut self, ty_name: &str, statement_ty: &StatementType) { + if !self.environment.used_types.contains(ty_name) && !ty_name.starts_with('_') { + self.diagnostics.report(statement_ty.stable_ptr, UnusedType); + } + } + /// Returns [Self::signature] if it exists. Otherwise, reports a diagnostic and returns `Err`. fn get_signature( &mut self, @@ -278,6 +289,16 @@ impl<'ctx> ComputationContext<'ctx> { // TODO(ilya): Change value to VarId. pub type EnvVariables = OrderedHashMap; +type EnvTypes = OrderedHashMap; + +/// Struct that holds the resolved generic type of a statement item. +#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb)] +#[debug_db(dyn SemanticGroup + 'static)] +struct StatementType { + resolved_generic_item: ResolvedGenericItem, + stable_ptr: SyntaxStablePtrId, +} + // TODO(spapini): Consider using identifiers instead of SmolStr everywhere in the code. /// A state which contains all the variables defined at the current resolver until now, and a /// pointer to the parent environment. @@ -286,6 +307,8 @@ pub struct Environment { parent: Option>, variables: EnvVariables, used_variables: UnorderedHashSet, + types: EnvTypes, + used_types: UnorderedHashSet, } impl Environment { /// Adds a parameter to the environment. @@ -310,10 +333,32 @@ impl Environment { } pub fn empty() -> Self { - Self { parent: None, variables: Default::default(), used_variables: Default::default() } + Self { + parent: None, + variables: Default::default(), + used_variables: Default::default(), + types: Default::default(), + used_types: Default::default(), + } } } +/// Returns the requested type from the environment if it exists. Returns None otherwise. +pub fn get_statement_type_by_name( + env: &mut Environment, + type_name: &SmolStr, +) -> Option { + let mut maybe_env = Some(&mut *env); + while let Some(curr_env) = maybe_env { + if let Some(var) = curr_env.types.get(type_name) { + curr_env.used_types.insert(type_name.clone()); + return Some(var.resolved_generic_item.clone()); + } + maybe_env = curr_env.parent.as_deref_mut(); + } + None +} + /// Computes the semantic model of an expression. /// Note that this expr will always be "registered" in the arena, so it can be looked up in the /// language server. @@ -856,8 +901,12 @@ fn compute_expr_function_call_semantic( } } - let item = - ctx.resolver.resolve_concrete_path(ctx.diagnostics, &path, NotFoundItemType::Function)?; + let item = ctx.resolver.resolve_concrete_path_ex( + ctx.diagnostics, + &path, + NotFoundItemType::Function, + Some(&mut ctx.environment), + )?; match item { ResolvedConcreteItem::Variant(variant) => { @@ -1619,11 +1668,12 @@ fn compute_expr_closure_semantic( vec![] }; let return_type = match syntax.ret_ty(syntax_db) { - OptionReturnTypeClause::ReturnTypeClause(ty_syntax) => resolve_type( + OptionReturnTypeClause::ReturnTypeClause(ty_syntax) => resolve_type_ex( new_ctx.db, new_ctx.diagnostics, &mut new_ctx.resolver, &ty_syntax.ty(syntax_db), + Some(&mut new_ctx.environment), ), OptionReturnTypeClause::Empty(missing) => { new_ctx.resolver.inference().new_type_var(Some(missing.stable_ptr().untyped())) @@ -1989,6 +2039,7 @@ fn maybe_compute_pattern_semantic( ctx.diagnostics, &path, NotFoundItemType::Identifier, + Some(&mut ctx.environment), )?; let generic_variant = try_extract_matches!(item, ResolvedGenericItem::Variant) .ok_or_else(|| ctx.diagnostics.report(&path, NotAVariant))?; @@ -2056,10 +2107,11 @@ fn maybe_compute_pattern_semantic( ), ast::Pattern::Struct(pattern_struct) => { let pattern_ty = try_extract_matches!( - ctx.resolver.resolve_concrete_path( + ctx.resolver.resolve_concrete_path_ex( ctx.diagnostics, &pattern_struct.path(syntax_db), - NotFoundItemType::Type + NotFoundItemType::Type, + Some(&mut ctx.environment) )?, ResolvedConcreteItem::Type ) @@ -2359,7 +2411,13 @@ fn struct_ctor_expr( let path = ctor_syntax.path(syntax_db); // Extract struct. - let ty = resolve_type(db, ctx.diagnostics, &mut ctx.resolver, &ast::Expr::Path(path.clone())); + let ty = resolve_type_ex( + db, + ctx.diagnostics, + &mut ctx.resolver, + &ast::Expr::Path(path.clone()), + Some(&mut ctx.environment), + ); ty.check_not_missing(db)?; let concrete_struct_id = try_extract_matches!(ty.lookup_intern(ctx.db), TypeLongId::Concrete) @@ -3053,8 +3111,12 @@ fn resolve_expr_path(ctx: &mut ComputationContext<'_>, path: &ast::ExprPath) -> } } - let resolved_item: ResolvedConcreteItem = - ctx.resolver.resolve_concrete_path(ctx.diagnostics, path, NotFoundItemType::Identifier)?; + let resolved_item: ResolvedConcreteItem = ctx.resolver.resolve_concrete_path_ex( + ctx.diagnostics, + path, + NotFoundItemType::Identifier, + Some(&mut ctx.environment), + )?; match resolved_item { ResolvedConcreteItem::Constant(const_value_id) => Ok(Expr::Constant(ExprConstant { @@ -3328,8 +3390,13 @@ pub fn compute_statement_semantic( } ast::OptionTypeClause::TypeClause(type_clause) => { let var_type_path = type_clause.ty(syntax_db); - let explicit_type = - resolve_type(db, ctx.diagnostics, &mut ctx.resolver, &var_type_path); + let explicit_type = resolve_type_ex( + db, + ctx.diagnostics, + &mut ctx.resolver, + &var_type_path, + Some(&mut ctx.environment), + ); let rhs_expr = compute_expr_semantic(ctx, rhs_syntax); let inferred_type = ctx.reduce_ty(rhs_expr.ty()); @@ -3506,7 +3573,13 @@ pub fn compute_statement_semantic( let lhs = const_syntax.type_clause(db.upcast()).ty(db.upcast()); let rhs = const_syntax.value(db.upcast()); let rhs_expr = compute_expr_semantic(ctx, &rhs); - let explicit_type = resolve_type(db, ctx.diagnostics, &mut ctx.resolver, &lhs); + let explicit_type = resolve_type_ex( + db, + ctx.diagnostics, + &mut ctx.resolver, + &lhs, + Some(&mut ctx.environment), + ); let rhs_resolved_expr = resolve_const_expr_and_evaluate( db, ctx, @@ -3537,6 +3610,7 @@ pub fn compute_statement_semantic( ctx.diagnostics, segments, NotFoundItemType::Identifier, + Some(&mut ctx.environment), )?; let var_def_id = StatementItemId::Use( StatementUseLongId(ctx.resolver.module_file_id, stable_ptr).intern(db), @@ -3553,10 +3627,17 @@ pub fn compute_statement_semantic( }); add_item_to_statement_environment(ctx, name, var_def, stable_ptr); } + ResolvedGenericItem::GenericType(generic_type_id) => { + add_type_to_statement_environment( + ctx, + name, + ResolvedGenericItem::GenericType(generic_type_id), + stable_ptr, + ); + } ResolvedGenericItem::Module(_) | ResolvedGenericItem::GenericFunction(_) | ResolvedGenericItem::TraitFunction(_) - | ResolvedGenericItem::GenericType(_) | ResolvedGenericItem::GenericTypeAlias(_) | ResolvedGenericItem::GenericImplAlias(_) | ResolvedGenericItem::Variant(_) @@ -3618,6 +3699,27 @@ fn add_item_to_statement_environment( ctx.semantic_defs.insert(var_def.id(), var_def); } +/// Adds a type to the statement environment and reports a diagnostic if the type is already +/// defined. +fn add_type_to_statement_environment( + ctx: &mut ComputationContext<'_>, + name: SmolStr, + resolved_generic_item: ResolvedGenericItem, + stable_ptr: impl Into + std::marker::Copy, +) { + if ctx + .environment + .types + .insert( + name.clone(), + StatementType { resolved_generic_item, stable_ptr: stable_ptr.into() }, + ) + .is_some() + { + ctx.diagnostics.report(stable_ptr, MultipleTypeDefinition(name)); + } +} + /// Computes the semantic model of an expression and reports diagnostics if the expression does not /// evaluate to a boolean value. fn compute_bool_condition_semantic( diff --git a/crates/cairo-lang-semantic/src/expr/semantic_test_data/use b/crates/cairo-lang-semantic/src/expr/semantic_test_data/use index 6bf094af7b0..b0cd0a1b0c7 100644 --- a/crates/cairo-lang-semantic/src/expr/semantic_test_data/use +++ b/crates/cairo-lang-semantic/src/expr/semantic_test_data/use @@ -102,3 +102,529 @@ error: Multiple definitions of constant "A". --> lib.cairo:5:7 const A: u8 = 4; ^ + +//! > ========================================================================== + +//! > Test use type struct usage + +//! > test_runner_name +test_expr_semantics(expect_diagnostics: warnings_only) + +//! > module_code +mod X { + struct R { + a: u8, + } +} +use X::R as RR; + +//! > function_body + +//! > expr_code +{ + let y = RR { a: 3 }; + use X::R; + let x = R { a: 4 }; +} + +//! > expected_semantics +Block( + ExprBlock { + statements: [ + Let( + StatementLet { + pattern: Variable( + y, + ), + expr: StructCtor( + ExprStructCtor { + concrete_struct_id: test::X::R, + members: [ + ( + MemberId(test::X::a), + Literal( + ExprLiteral { + value: 3, + ty: core::integer::u8, + }, + ), + ), + ], + base_struct: None, + ty: test::X::R, + }, + ), + }, + ), + Item( + StatementItem, + ), + Let( + StatementLet { + pattern: Variable( + x, + ), + expr: StructCtor( + ExprStructCtor { + concrete_struct_id: test::X::R, + members: [ + ( + MemberId(test::X::a), + Literal( + ExprLiteral { + value: 4, + ty: core::integer::u8, + }, + ), + ), + ], + base_struct: None, + ty: test::X::R, + }, + ), + }, + ), + ], + tail: None, + ty: (), + }, +) + +//! > expected_diagnostics +warning[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> lib.cairo:9:9 + let y = RR { a: 3 }; + ^ + +warning[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> lib.cairo:11:9 + let x = R { a: 4 }; + ^ + +//! > ========================================================================== + +//! > Test use type struct generic usage + +//! > test_runner_name +test_expr_semantics(expect_diagnostics: warnings_only) + +//! > module_code +mod X { + struct R { + a: u8, + } +} +use X::R as RR; + +//! > function_body + +//! > expr_code +{ + let y = RR:: { a: 3 }; + use X::R; + let x = R:: { a: 4 }; +} + +//! > expected_semantics +Block( + ExprBlock { + statements: [ + Let( + StatementLet { + pattern: Variable( + y, + ), + expr: StructCtor( + ExprStructCtor { + concrete_struct_id: test::X::R::, + members: [ + ( + MemberId(test::X::a), + Literal( + ExprLiteral { + value: 3, + ty: core::integer::u8, + }, + ), + ), + ], + base_struct: None, + ty: test::X::R::, + }, + ), + }, + ), + Item( + StatementItem, + ), + Let( + StatementLet { + pattern: Variable( + x, + ), + expr: StructCtor( + ExprStructCtor { + concrete_struct_id: test::X::R::, + members: [ + ( + MemberId(test::X::a), + Literal( + ExprLiteral { + value: 4, + ty: core::integer::u8, + }, + ), + ), + ], + base_struct: None, + ty: test::X::R::, + }, + ), + }, + ), + ], + tail: None, + ty: (), + }, +) + +//! > expected_diagnostics +warning[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> lib.cairo:9:9 + let y = RR:: { a: 3 }; + ^ + +warning[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> lib.cairo:11:9 + let x = R:: { a: 4 }; + ^ + +//! > ========================================================================== + +//! > Test use type enum usage + +//! > test_runner_name +test_expr_semantics(expect_diagnostics: warnings_only) + +//! > module_code +mod X { + enum R { + A: u8, + B: u16, + } +} +use X::R as RR; + +//! > function_body + +//! > expr_code +{ + let y = RR::A(2); + use X::R; + let x = R::B(3); +} + +//! > expected_semantics +Block( + ExprBlock { + statements: [ + Let( + StatementLet { + pattern: Variable( + y, + ), + expr: EnumVariantCtor( + ExprEnumVariantCtor { + variant: R::A, + value_expr: Literal( + ExprLiteral { + value: 2, + ty: core::integer::u8, + }, + ), + ty: test::X::R, + }, + ), + }, + ), + Item( + StatementItem, + ), + Let( + StatementLet { + pattern: Variable( + x, + ), + expr: EnumVariantCtor( + ExprEnumVariantCtor { + variant: R::B, + value_expr: Literal( + ExprLiteral { + value: 3, + ty: core::integer::u16, + }, + ), + ty: test::X::R, + }, + ), + }, + ), + ], + tail: None, + ty: (), + }, +) + +//! > expected_diagnostics +warning[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> lib.cairo:10:9 + let y = RR::A(2); + ^ + +warning[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> lib.cairo:12:9 + let x = R::B(3); + ^ + +//! > ========================================================================== + +//! > Test use type enum generic usage + +//! > test_runner_name +test_expr_semantics(expect_diagnostics: warnings_only) + +//! > module_code +mod X { + enum R { + A: u8, + B: u16, + } +} +use X::R as RR; + +//! > function_body + +//! > expr_code +{ + let y = RR::::A(2); + use X::R; + let x = R::::B(3); +} + +//! > expected_semantics +Block( + ExprBlock { + statements: [ + Let( + StatementLet { + pattern: Variable( + y, + ), + expr: EnumVariantCtor( + ExprEnumVariantCtor { + variant: R::A, + value_expr: Literal( + ExprLiteral { + value: 2, + ty: core::integer::u8, + }, + ), + ty: test::X::R::, + }, + ), + }, + ), + Item( + StatementItem, + ), + Let( + StatementLet { + pattern: Variable( + x, + ), + expr: EnumVariantCtor( + ExprEnumVariantCtor { + variant: R::B, + value_expr: Literal( + ExprLiteral { + value: 3, + ty: core::integer::u16, + }, + ), + ty: test::X::R::, + }, + ), + }, + ), + ], + tail: None, + ty: (), + }, +) + +//! > expected_diagnostics +warning[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> lib.cairo:10:9 + let y = RR::::A(2); + ^ + +warning[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> lib.cairo:12:9 + let x = R::::B(3); + ^ + +//! > ========================================================================== + +//! > Test use type extern usage + +//! > test_runner_name +test_expr_semantics(expect_diagnostics: warnings_only) + +//! > module_code +mod X { + extern type R; + extern fn new_r() -> R nopanic; +} +use X::R as RR; +use X::new_r; + +//! > function_body + +//! > expr_code +{ + let y: RR = new_r(); + use X::R; + let x: R = new_r(); +} + +//! > expected_semantics +Block( + ExprBlock { + statements: [ + Let( + StatementLet { + pattern: Variable( + y, + ), + expr: FunctionCall( + ExprFunctionCall { + function: test::X::new_r, + args: [], + coupon_arg: None, + ty: test::X::R, + }, + ), + }, + ), + Item( + StatementItem, + ), + Let( + StatementLet { + pattern: Variable( + x, + ), + expr: FunctionCall( + ExprFunctionCall { + function: test::X::new_r, + args: [], + coupon_arg: None, + ty: test::X::R, + }, + ), + }, + ), + ], + tail: None, + ty: (), + }, +) + +//! > expected_diagnostics +warning[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> lib.cairo:9:9 + let y: RR = new_r(); + ^ + +warning[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> lib.cairo:11:9 + let x: R = new_r(); + ^ + +//! > ========================================================================== + +//! > Test use type extern generic usage + +//! > test_runner_name +test_expr_semantics(expect_diagnostics: warnings_only) + +//! > module_code +mod X { + extern type R; + extern fn new_r() -> R nopanic; +} +use X::R as RR; +use X::new_r; + +//! > function_body + +//! > expr_code +{ + let y: RR:: = new_r(); + use X::R; + let x: R:: = new_r(); +} + +//! > expected_semantics +Block( + ExprBlock { + statements: [ + Let( + StatementLet { + pattern: Variable( + y, + ), + expr: FunctionCall( + ExprFunctionCall { + function: test::X::new_r::, + args: [], + coupon_arg: None, + ty: test::X::R::, + }, + ), + }, + ), + Item( + StatementItem, + ), + Let( + StatementLet { + pattern: Variable( + x, + ), + expr: FunctionCall( + ExprFunctionCall { + function: test::X::new_r::, + args: [], + coupon_arg: None, + ty: test::X::R::, + }, + ), + }, + ), + ], + tail: None, + ty: (), + }, +) + +//! > expected_diagnostics +warning[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> lib.cairo:9:9 + let y: RR:: = new_r(); + ^ + +warning[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> lib.cairo:11:9 + let x: R:: = new_r(); + ^ diff --git a/crates/cairo-lang-semantic/src/items/impl_alias.rs b/crates/cairo-lang-semantic/src/items/impl_alias.rs index 6a0be4e48e4..659b53c71f4 100644 --- a/crates/cairo-lang-semantic/src/items/impl_alias.rs +++ b/crates/cairo-lang-semantic/src/items/impl_alias.rs @@ -268,6 +268,7 @@ pub fn impl_alias_impl_def(db: &dyn SemanticGroup, impl_alias_id: ImplAliasId) - &mut diagnostics, &impl_path_syntax, NotFoundItemType::Impl, + None, ) { Ok(ResolvedGenericItem::Impl(imp)) => Ok(imp), Ok(ResolvedGenericItem::GenericImplAlias(impl_alias)) => db.impl_alias_impl_def(impl_alias), diff --git a/crates/cairo-lang-semantic/src/items/mod.rs b/crates/cairo-lang-semantic/src/items/mod.rs index a37224fe3bc..0add5ac7d23 100644 --- a/crates/cairo-lang-semantic/src/items/mod.rs +++ b/crates/cairo-lang-semantic/src/items/mod.rs @@ -46,6 +46,7 @@ fn resolve_trait_path( diagnostics, trait_path_syntax, NotFoundItemType::Trait, + None, )?, ResolvedGenericItem::Trait ) diff --git a/crates/cairo-lang-semantic/src/items/us.rs b/crates/cairo-lang-semantic/src/items/us.rs index d944d7cd78d..909195ae2f1 100644 --- a/crates/cairo-lang-semantic/src/items/us.rs +++ b/crates/cairo-lang-semantic/src/items/us.rs @@ -38,8 +38,12 @@ pub fn priv_use_semantic_data(db: &dyn SemanticGroup, use_id: UseId) -> Maybe Resolver<'db> { diagnostics: &mut SemanticDiagnostics, path: impl AsSegments, item_type: NotFoundItemType, + mut statement_env: Option<&mut Environment>, mut callbacks: ResolvePathInnerCallbacks< ResolvedItem, impl FnMut( &mut Resolver<'_>, &mut SemanticDiagnostics, &mut Peekable>, + Option<&mut Environment>, ) -> Maybe, impl FnMut( &mut Resolver<'_>, @@ -294,6 +297,7 @@ impl<'db> Resolver<'db> { &ResolvedItem, &ast::PathSegment, NotFoundItemType, + Option<&mut Environment>, ) -> Maybe, impl FnMut(&mut SemanticDiagnostics, &ast::PathSegment) -> Maybe<()>, impl FnMut( @@ -310,8 +314,12 @@ impl<'db> Resolver<'db> { let mut segments = elements_vec.iter().peekable(); // Find where the first segment lies in. - let mut item: ResolvedItem = - (callbacks.resolve_path_first_segment)(self, diagnostics, &mut segments)?; + let mut item: ResolvedItem = (callbacks.resolve_path_first_segment)( + self, + diagnostics, + &mut segments, + statement_env.as_deref_mut(), + )?; // Follow modules. while let Some(segment) = segments.next() { @@ -329,6 +337,7 @@ impl<'db> Resolver<'db> { &item, segment, cur_item_type, + statement_env.as_deref_mut(), )?; (callbacks.mark)(&mut self.resolved_items, db, segment, item.clone()); } @@ -337,29 +346,49 @@ impl<'db> Resolver<'db> { /// Resolves a concrete item, given a path. /// Guaranteed to result in at most one diagnostic. + /// Item not inside a statement. pub fn resolve_concrete_path( &mut self, diagnostics: &mut SemanticDiagnostics, path: impl AsSegments, item_type: NotFoundItemType, + ) -> Maybe { + self.resolve_concrete_path_ex(diagnostics, path, item_type, None) + } + + /// Resolves a concrete item, given a path. + /// Guaranteed to result in at most one diagnostic. + pub fn resolve_concrete_path_ex( + &mut self, + diagnostics: &mut SemanticDiagnostics, + path: impl AsSegments, + item_type: NotFoundItemType, + statement_env: Option<&mut Environment>, ) -> Maybe { self.resolve_path_inner::( diagnostics, path, item_type, + statement_env, ResolvePathInnerCallbacks { resolved_item_type: PhantomData, - resolve_path_first_segment: |resolver, diagnostics, segments| { - resolver.resolve_concrete_path_first_segment(diagnostics, segments) - }, - resolve_path_next_segment: |resolver, diagnostics, item, segment, item_type| { - resolver.resolve_path_next_segment_concrete( + resolve_path_first_segment: |resolver, diagnostics, segments, statement_env| { + resolver.resolve_concrete_path_first_segment( diagnostics, - item, - segment, - item_type, + segments, + statement_env, ) }, + resolve_path_next_segment: + |resolver, diagnostics, item, segment, item_type, statement_env| { + resolver.resolve_path_next_segment_concrete( + diagnostics, + item, + segment, + item_type, + statement_env, + ) + }, validate_segment: |_, _| Ok(()), mark: |resolved_items, db, segment, item| { resolved_items.mark_concrete(db, segment, item.clone()); @@ -373,6 +402,7 @@ impl<'db> Resolver<'db> { &mut self, diagnostics: &mut SemanticDiagnostics, segments: &mut Peekable>, + statement_env: Option<&mut Environment>, ) -> Maybe { if let Some(base_module) = self.try_handle_super_segments(diagnostics, segments) { return Ok(ResolvedConcreteItem::Module(base_module?)); @@ -384,7 +414,9 @@ impl<'db> Resolver<'db> { syntax::node::ast::PathSegment::WithGenericArgs(generic_segment) => { let identifier = generic_segment.ident(syntax_db); // Identifier with generic args cannot be a local item. - if let ResolvedBase::Module(module_id) = self.determine_base(&identifier) { + if let ResolvedBase::Module(module_id) = + self.determine_base(&identifier, statement_env) + { ResolvedConcreteItem::Module(module_id) } else { // Crates do not have generics. @@ -406,7 +438,7 @@ impl<'db> Resolver<'db> { if let Some(local_item) = self.determine_base_item_in_local_scope(&identifier) { self.resolved_items.mark_concrete(db, segments.next().unwrap(), local_item) } else { - match self.determine_base(&identifier) { + match self.determine_base(&identifier, statement_env) { // This item lies inside a module. ResolvedBase::Module(module_id) => ResolvedConcreteItem::Module(module_id), ResolvedBase::Crate(crate_id) => self.resolved_items.mark_concrete( @@ -427,8 +459,9 @@ impl<'db> Resolver<'db> { diagnostics: &mut SemanticDiagnostics, path: impl AsSegments, item_type: NotFoundItemType, + statement_env: Option<&mut Environment>, ) -> Maybe { - self.resolve_generic_path_inner(diagnostics, path, item_type, false) + self.resolve_generic_path_inner(diagnostics, path, item_type, false, statement_env) } /// Resolves a generic item, given a concrete item path, while ignoring the generic args. /// Guaranteed to result in at most one diagnostic. @@ -437,8 +470,9 @@ impl<'db> Resolver<'db> { diagnostics: &mut SemanticDiagnostics, path: impl AsSegments, item_type: NotFoundItemType, + statement_env: Option<&mut Environment>, ) -> Maybe { - self.resolve_generic_path_inner(diagnostics, path, item_type, true) + self.resolve_generic_path_inner(diagnostics, path, item_type, true, statement_env) } /// Resolves a generic item, given a path. @@ -451,6 +485,7 @@ impl<'db> Resolver<'db> { path: impl AsSegments, item_type: NotFoundItemType, allow_generic_args: bool, + statement_env: Option<&mut Environment>, ) -> Maybe { let validate_segment = |diagnostics: &mut SemanticDiagnostics, segment: &ast::PathSegment| match segment { @@ -463,24 +498,28 @@ impl<'db> Resolver<'db> { diagnostics, path, item_type, + statement_env, ResolvePathInnerCallbacks { resolved_item_type: PhantomData, - resolve_path_first_segment: |resolver, diagnostics, segments| { + resolve_path_first_segment: |resolver, diagnostics, segments, statement_env| { resolver.resolve_generic_path_first_segment( diagnostics, segments, allow_generic_args, + statement_env, ) }, - resolve_path_next_segment: |resolver, diagnostics, item, segment, item_type| { - let identifier = segment.identifier_ast(self.db.upcast()); - resolver.resolve_path_next_segment_generic( - diagnostics, - item, - &identifier, - item_type, - ) - }, + resolve_path_next_segment: + |resolver, diagnostics, item, segment, item_type, statement_env| { + let identifier = segment.identifier_ast(self.db.upcast()); + resolver.resolve_path_next_segment_generic( + diagnostics, + item, + &identifier, + item_type, + statement_env, + ) + }, validate_segment, mark: |resolved_items, db, segment, item| { resolved_items.mark_generic(db, segment, item.clone()); @@ -496,6 +535,7 @@ impl<'db> Resolver<'db> { diagnostics: &mut SemanticDiagnostics, segments: &mut Peekable>, allow_generic_args: bool, + statement_env: Option<&mut Environment>, ) -> Maybe { if let Some(base_module) = self.try_handle_super_segments(diagnostics, segments) { return Ok(ResolvedGenericItem::Module(base_module?)); @@ -510,7 +550,9 @@ impl<'db> Resolver<'db> { } let identifier = generic_segment.ident(syntax_db); // Identifier with generic args cannot be a local item. - if let ResolvedBase::Module(module_id) = self.determine_base(&identifier) { + if let ResolvedBase::Module(module_id) = + self.determine_base(&identifier, statement_env) + { ResolvedGenericItem::Module(module_id) } else { // Crates do not have generics. @@ -520,7 +562,7 @@ impl<'db> Resolver<'db> { } syntax::node::ast::PathSegment::Simple(simple_segment) => { let identifier = simple_segment.ident(syntax_db); - match self.determine_base(&identifier) { + match self.determine_base(&identifier, statement_env) { // This item lies inside a module. ResolvedBase::Module(module_id) => ResolvedGenericItem::Module(module_id), ResolvedBase::Crate(crate_id) => self.resolved_items.mark_generic( @@ -564,6 +606,7 @@ impl<'db> Resolver<'db> { containing_item: &ResolvedConcreteItem, segment: &ast::PathSegment, item_type: NotFoundItemType, + statement_env: Option<&mut Environment>, ) -> Maybe { let syntax_db = self.db.upcast(); let identifier = &segment.identifier_ast(syntax_db); @@ -582,12 +625,30 @@ impl<'db> Resolver<'db> { if ident == SUPER_KW { return Err(diagnostics.report(identifier, InvalidPath)); } + let segment_stable_ptr = segment.stable_ptr().untyped(); + + if let Some(env) = statement_env { + if let Some(inner_generic_item) = get_statement_type_by_name(env, &ident) { + let specialized_item = self.specialize_generic_module_item( + diagnostics, + identifier, + inner_generic_item, + generic_args_syntax.clone(), + )?; + self.warn_same_impl_trait( + diagnostics, + &specialized_item, + &generic_args_syntax.unwrap_or_default(), + segment_stable_ptr, + ); + return Ok(specialized_item); + } + } let inner_item_info = self .db .module_item_info_by_name(*module_id, ident)? .ok_or_else(|| diagnostics.report(identifier, PathNotFound(item_type)))?; - let segment_stable_ptr = segment.stable_ptr().untyped(); self.validate_item_usability(diagnostics, *module_id, identifier, &inner_item_info); self.data.used_items.insert(LookupItemId::ModuleItem(inner_item_info.item_id)); let inner_generic_item = @@ -919,11 +980,17 @@ impl<'db> Resolver<'db> { containing_item: &ResolvedGenericItem, identifier: &ast::TerminalIdentifier, item_type: NotFoundItemType, + statement_env: Option<&mut Environment>, ) -> Maybe { let syntax_db = self.db.upcast(); let ident = identifier.text(syntax_db); match containing_item { ResolvedGenericItem::Module(module_id) => { + if let Some(env) = statement_env { + if let Some(inner_generic_item) = get_statement_type_by_name(env, &ident) { + return Ok(inner_generic_item); + } + } let inner_item_info = self .db .module_item_info_by_name(*module_id, ident)? @@ -978,10 +1045,20 @@ impl<'db> Resolver<'db> { /// Determines the base module or crate for the path resolving. Looks only in non-local scope /// (i.e. current module, or crates). - fn determine_base(&mut self, identifier: &ast::TerminalIdentifier) -> ResolvedBase { + fn determine_base( + &mut self, + identifier: &ast::TerminalIdentifier, + statement_env: Option<&mut Environment>, + ) -> ResolvedBase { let syntax_db = self.db.upcast(); let ident = identifier.text(syntax_db); + if let Some(env) = statement_env { + if get_statement_type_by_name(env, &ident).is_some() { + return ResolvedBase::Module(self.module_file_id.0); + } + } + // If an item with this name is found inside the current module, use the current module. if let Ok(Some(_)) = self.db.module_item_by_name(self.module_file_id.0, ident.clone()) { return ResolvedBase::Module(self.module_file_id.0); @@ -1028,7 +1105,6 @@ impl<'db> Resolver<'db> { .db .trait_generic_params(trait_id) .map_err(|_| diagnostics.report(stable_ptr, UnknownTrait))?; - let generic_args = self.resolve_generic_args(diagnostics, &generic_params, generic_args, stable_ptr)?; @@ -1048,7 +1124,6 @@ impl<'db> Resolver<'db> { .db .impl_def_generic_params(impl_def_id) .map_err(|_| diagnostics.report(stable_ptr, UnknownImpl))?; - let generic_args = self.resolve_generic_args(diagnostics, &generic_params, generic_args, stable_ptr)?; @@ -1065,7 +1140,6 @@ impl<'db> Resolver<'db> { ) -> Maybe { // TODO(lior): Should we report diagnostic if `impl_def_generic_params` failed? let generic_params: Vec<_> = generic_function.generic_params(self.db)?; - let generic_args = self.resolve_generic_args(diagnostics, &generic_params, generic_args, stable_ptr)?; @@ -1085,7 +1159,6 @@ impl<'db> Resolver<'db> { .db .generic_type_generic_params(generic_type) .map_err(|_| diagnostics.report(stable_ptr, UnknownType))?; - let generic_args = self.resolve_generic_args(diagnostics, &generic_params, generic_args, stable_ptr)?; @@ -1109,6 +1182,9 @@ impl<'db> Resolver<'db> { lookup_context } + /// Resolves generic arguments. + /// For each generic argument, if the syntax is provided, it will be resolved by the inference. + /// Otherwise, resolved by type. pub fn resolve_generic_args( &mut self, diagnostics: &mut SemanticDiagnostics, @@ -1207,6 +1283,9 @@ impl<'db> Resolver<'db> { Ok(arg_syntax_per_param) } + /// Resolves a generic argument. + /// If no syntax Expr is provided, inference will be used. + /// If a syntax Expr is provided, it will be resolved by type. fn resolve_generic_arg( &mut self, generic_param: GenericParam, @@ -1268,7 +1347,7 @@ impl<'db> Resolver<'db> { let expr_path = try_extract_matches!(generic_arg_syntax, ast::Expr::Path) .ok_or_else(|| diagnostics.report(generic_arg_syntax, UnknownImpl))?; let resolved_impl = try_extract_matches!( - self.resolve_concrete_path(diagnostics, expr_path, NotFoundItemType::Impl,)?, + self.resolve_concrete_path(diagnostics, expr_path, NotFoundItemType::Impl)?, ResolvedConcreteItem::Impl ) .ok_or_else(|| diagnostics.report(generic_arg_syntax, UnknownImpl))?; @@ -1504,7 +1583,6 @@ impl<'db> Resolver<'db> { if current_segment_generic_args.len() < generic_params.len() { return Err(diagnostics.report(segment_stable_ptr, must_be_explicit_error)); } - let resolved_args = self.resolve_generic_args( diagnostics, &generic_params, @@ -1589,6 +1667,7 @@ where &mut Resolver<'_>, &mut SemanticDiagnostics, &mut Peekable>, + Option<&mut Environment>, ) -> Maybe, ResolveNext: FnMut( &mut Resolver<'_>, @@ -1596,6 +1675,7 @@ where &ResolvedItem, &ast::PathSegment, NotFoundItemType, + Option<&mut Environment>, ) -> Maybe, Validate: FnMut(&mut SemanticDiagnostics, &ast::PathSegment) -> Maybe<()>, Mark: FnMut( diff --git a/crates/cairo-lang-semantic/src/types.rs b/crates/cairo-lang-semantic/src/types.rs index 4db682ec2a3..589e26f65a9 100644 --- a/crates/cairo-lang-semantic/src/types.rs +++ b/crates/cairo-lang-semantic/src/types.rs @@ -477,13 +477,24 @@ impl DebugWithDb for ImplTypeId { // TODO(spapini): add a query wrapper. /// Resolves a type given a module and a path. +/// Type called not inside a statement pub fn resolve_type( db: &dyn SemanticGroup, diagnostics: &mut SemanticDiagnostics, resolver: &mut Resolver<'_>, ty_syntax: &ast::Expr, ) -> TypeId { - maybe_resolve_type(db, diagnostics, resolver, ty_syntax) + resolve_type_ex(db, diagnostics, resolver, ty_syntax, None) +} +/// Resolves a type given a module and a path. +pub fn resolve_type_ex( + db: &dyn SemanticGroup, + diagnostics: &mut SemanticDiagnostics, + resolver: &mut Resolver<'_>, + ty_syntax: &ast::Expr, + statement_env: Option<&mut Environment>, +) -> TypeId { + maybe_resolve_type(db, diagnostics, resolver, ty_syntax, statement_env) .unwrap_or_else(|diag_added| TypeId::missing(db, diag_added)) } pub fn maybe_resolve_type( @@ -491,11 +502,17 @@ pub fn maybe_resolve_type( diagnostics: &mut SemanticDiagnostics, resolver: &mut Resolver<'_>, ty_syntax: &ast::Expr, + mut statement_env: Option<&mut Environment>, ) -> Maybe { let syntax_db = db.upcast(); Ok(match ty_syntax { ast::Expr::Path(path) => { - match resolver.resolve_concrete_path(diagnostics, path, NotFoundItemType::Type)? { + match resolver.resolve_concrete_path_ex( + diagnostics, + path, + NotFoundItemType::Type, + statement_env, + )? { ResolvedConcreteItem::Type(ty) => ty, _ => { return Err(diagnostics.report(path, NotAType)); @@ -503,27 +520,47 @@ pub fn maybe_resolve_type( } } ast::Expr::Parenthesized(expr_syntax) => { - resolve_type(db, diagnostics, resolver, &expr_syntax.expr(syntax_db)) + resolve_type_ex(db, diagnostics, resolver, &expr_syntax.expr(syntax_db), statement_env) } ast::Expr::Tuple(tuple_syntax) => { let sub_tys = tuple_syntax .expressions(syntax_db) .elements(syntax_db) .into_iter() - .map(|subexpr_syntax| resolve_type(db, diagnostics, resolver, &subexpr_syntax)) + .map(|subexpr_syntax| { + resolve_type_ex( + db, + diagnostics, + resolver, + &subexpr_syntax, + statement_env.as_deref_mut(), + ) + }) .collect(); TypeLongId::Tuple(sub_tys).intern(db) } ast::Expr::Unary(unary_syntax) if matches!(unary_syntax.op(syntax_db), ast::UnaryOperator::At(_)) => { - let ty = resolve_type(db, diagnostics, resolver, &unary_syntax.expr(syntax_db)); + let ty = resolve_type_ex( + db, + diagnostics, + resolver, + &unary_syntax.expr(syntax_db), + statement_env, + ); TypeLongId::Snapshot(ty).intern(db) } ast::Expr::Unary(unary_syntax) if matches!(unary_syntax.op(syntax_db), ast::UnaryOperator::Desnap(_)) => { - let ty = resolve_type(db, diagnostics, resolver, &unary_syntax.expr(syntax_db)); + let ty = resolve_type_ex( + db, + diagnostics, + resolver, + &unary_syntax.expr(syntax_db), + statement_env, + ); if let Some(desnapped_ty) = try_extract_matches!(ty.lookup_intern(db), TypeLongId::Snapshot) { @@ -536,7 +573,7 @@ pub fn maybe_resolve_type( let [ty] = &array_syntax.exprs(syntax_db).elements(syntax_db)[..] else { return Err(diagnostics.report(ty_syntax, FixedSizeArrayTypeNonSingleType)); }; - let ty = resolve_type(db, diagnostics, resolver, ty); + let ty = resolve_type_ex(db, diagnostics, resolver, ty, statement_env); let size = match extract_fixed_size_array_size(db, diagnostics, array_syntax, resolver)? { Some(size) => size,