diff --git a/crates/cairo-lang-semantic/src/diagnostic.rs b/crates/cairo-lang-semantic/src/diagnostic.rs index 99e342e7148..bbb66c44bae 100644 --- a/crates/cairo-lang-semantic/src/diagnostic.rs +++ b/crates/cairo-lang-semantic/src/diagnostic.rs @@ -429,6 +429,12 @@ impl DiagnosticEntry for SemanticDiagnostic { r#"Usage of unstable feature `{feature_name}` with no `#[feature({feature_name})]` attribute."# ) } + SemanticDiagnosticKind::MultipleFeatureAttributes => { + "Multiple feature attributes.".into() + } + SemanticDiagnosticKind::UnsupportedUnstableAttrArguments => { + "Unsupported unstable attribute arguments.".into() + } SemanticDiagnosticKind::UnusedVariable => { "Unused variable. Consider ignoring by prefixing with `_`.".into() } @@ -959,6 +965,8 @@ pub enum SemanticDiagnosticKind { UnstableFeature { feature_name: SmolStr, }, + MultipleFeatureAttributes, + UnsupportedUnstableAttrArguments, UnhandledMustUseFunction, UnusedVariable, ConstGenericParamNotSupported, diff --git a/crates/cairo-lang-semantic/src/diagnostic_test_data/tests b/crates/cairo-lang-semantic/src/diagnostic_test_data/tests index 813925d9ae4..63afd34841c 100644 --- a/crates/cairo-lang-semantic/src/diagnostic_test_data/tests +++ b/crates/cairo-lang-semantic/src/diagnostic_test_data/tests @@ -500,4 +500,28 @@ fn unstable_function() -> felt252 { error: Usage of unstable feature `"testing"` with no `#[feature("testing")]` attribute. --> lib.cairo:7:15 let _fail = unstable_function(); - ^*****************^ + ^***************^ + +//! > ========================================================================== + +//! > Test bad unstable declaration. + +//! > test_runner_name +test_expr_diagnostics(expect_diagnostics: true) + +//! > expr_code +{} + +//! > module_code +#[unstable(feature: "testing", extra)] +fn unstable_function() -> felt252 { + 0 +} + +//! > function_body + +//! > expected_diagnostics +error: Unsupported unstable attribute arguments. + --> lib.cairo:1:11 +#[unstable(feature: "testing", extra)] + ^*************************^ diff --git a/crates/cairo-lang-semantic/src/expr/compute.rs b/crates/cairo-lang-semantic/src/expr/compute.rs index 998fd9e3780..1fd414906c2 100644 --- a/crates/cairo-lang-semantic/src/expr/compute.rs +++ b/crates/cairo-lang-semantic/src/expr/compute.rs @@ -9,19 +9,15 @@ use ast::PathSegment; use cairo_lang_defs::db::validate_attributes_flat; use cairo_lang_defs::ids::{ EnumId, FunctionTitleId, FunctionWithBodyId, GenericKind, LanguageElementId, LocalVarLongId, - LookupItemId, MemberId, ModuleId, TraitFunctionId, TraitId, + MemberId, TraitFunctionId, TraitId, }; use cairo_lang_diagnostics::{DiagnosticAdded, Maybe, ToOption}; use cairo_lang_filesystem::ids::{FileKind, FileLongId, VirtualFile}; -use cairo_lang_syntax::attribute::consts::FEATURE_ATTR; -use cairo_lang_syntax::attribute::structured::{ - Attribute, AttributeArg, AttributeArgVariant, AttributeStructurize, -}; use cairo_lang_syntax::node::ast::{ BlockOrIf, ExprPtr, PatternListOr, PatternStructParam, UnaryOperator, }; use cairo_lang_syntax::node::db::SyntaxGroup; -use cairo_lang_syntax::node::helpers::{GetIdentifier, PathSegmentEx, QueryAttrs}; +use cairo_lang_syntax::node::helpers::{GetIdentifier, PathSegmentEx}; use cairo_lang_syntax::node::ids::SyntaxStablePtrId; use cairo_lang_syntax::node::{ast, Terminal, TypedStablePtr, TypedSyntaxNode}; use cairo_lang_utils as utils; @@ -58,6 +54,7 @@ use crate::diagnostic::{ }; use crate::items::constant::ConstValue; use crate::items::enm::SemanticEnumEx; +use crate::items::feature_kind::extract_item_allowed_features; use crate::items::imp::{filter_candidate_traits, infer_impl_by_self}; use crate::items::modifiers::compute_mutability; use crate::items::structure::SemanticStructEx; @@ -219,7 +216,6 @@ pub struct Environment { parent: Option>, variables: EnvVariables, used_variables: UnorderedHashSet, - allowed_features: UnorderedHashSet, } impl Environment { /// Adds a parameter to the environment. @@ -244,73 +240,7 @@ impl Environment { } pub fn empty() -> Self { - Self { - parent: None, - variables: Default::default(), - used_variables: Default::default(), - allowed_features: Default::default(), - } - } - - pub fn from_lookup_item_id( - db: &dyn SemanticGroup, - lookup_item_id: LookupItemId, - diagnostics: &mut SemanticDiagnostics, - ) -> Self { - let defs_db = db.upcast(); - let semantic_db = db.upcast(); - let allowed_features = match lookup_item_id { - LookupItemId::ModuleItem(id) => extract_allowed_features( - semantic_db, - &id.stable_location(defs_db).syntax_node(defs_db), - diagnostics, - ), - LookupItemId::TraitItem(id) => extract_allowed_features( - semantic_db, - &id.stable_location(defs_db).syntax_node(defs_db), - diagnostics, - ), - LookupItemId::ImplItem(id) => extract_allowed_features( - semantic_db, - &id.stable_location(defs_db).syntax_node(defs_db), - diagnostics, - ), - }; - - Self::from_element_id(db, lookup_item_id, allowed_features.into_iter().collect()) - } - - fn from_element_id( - db: &dyn SemanticGroup, - element_id: impl LanguageElementId, - mut allowed_features: UnorderedHashSet, - ) -> Environment { - let defs_db = db.upcast(); - let syntax_db = db.upcast(); - let ignored_diagnostics = &mut SemanticDiagnostics::new( - element_id.module_file_id(defs_db).file_id(defs_db).unwrap(), - ); - let mut curr_module_id = element_id.parent_module(defs_db); - loop { - let submodule_id = match curr_module_id { - ModuleId::CrateRoot(_) => break, - ModuleId::Submodule(id) => id, - }; - let parent = submodule_id.parent_module(defs_db); - let module = &defs_db.module_submodules(parent).unwrap()[&submodule_id]; - // TODO(orizi): Add parent module diagnostics. - for allowed_feature in extract_allowed_features(syntax_db, module, ignored_diagnostics) - { - allowed_features.insert(allowed_feature); - } - curr_module_id = parent; - } - Self { - parent: None, - variables: Default::default(), - used_variables: Default::default(), - allowed_features, - } + Self { parent: None, variables: Default::default(), used_variables: Default::default() } } } @@ -321,11 +251,6 @@ pub fn compute_expr_semantic(ctx: &mut ComputationContext<'_>, syntax: &ast::Exp let expr = maybe_compute_expr_semantic(ctx, syntax); let expr = wrap_maybe_with_missing(ctx, expr, syntax.stable_ptr()); let id = ctx.exprs.alloc(expr.clone()); - if let TypeLongId::Concrete(concrete) = ctx.db.lookup_intern_type(expr.ty()) { - if let Ok(Some(attr)) = concrete.unstable_attr(ctx.db.upcast()) { - validate_unstable_feature_usage(ctx, attr, syntax.stable_ptr()); - } - } ExprAndId { expr, id } } @@ -2455,15 +2380,6 @@ fn expr_function_call( mut named_args: Vec, stable_ptr: ast::ExprPtr, ) -> Maybe { - if let Ok(Some(attr)) = ctx - .db - .lookup_intern_function(function_id) - .function - .generic_function - .unstable_feature(ctx.db.upcast()) - { - validate_unstable_feature_usage(ctx, attr, stable_ptr); - } // TODO(spapini): Better location for these diagnostics after the refactor for generics resolve. // TODO(lior): Check whether concrete_function_signature should be `Option` instead of `Maybe`. let signature = ctx.db.concrete_function_signature(function_id)?; @@ -2642,8 +2558,8 @@ pub fn compute_statement_semantic( // are allowed. validate_statement_attributes(ctx, &syntax); let mut features_to_remove = vec![]; - for feature_name in extract_allowed_features(syntax_db, &syntax, ctx.diagnostics) { - if ctx.environment.allowed_features.insert(feature_name.clone()) { + for feature_name in extract_item_allowed_features(syntax_db, &syntax, ctx.diagnostics) { + if ctx.resolver.data.allowed_features.insert(feature_name.clone()) { features_to_remove.push(feature_name); } } @@ -2824,38 +2740,11 @@ pub fn compute_statement_semantic( ast::Statement::Missing(_) => todo!(), }; for feature_name in features_to_remove { - ctx.environment.allowed_features.remove(&feature_name); + ctx.resolver.data.allowed_features.swap_remove(&feature_name); } Ok(ctx.statements.alloc(statement)) } -/// Returns the allowed features of an object which supports attributes. -fn extract_allowed_features( - db: &dyn SyntaxGroup, - syntax: &impl QueryAttrs, - diagnostics: &mut SemanticDiagnostics, -) -> Vec { - let mut features = vec![]; - for attr_syntax in syntax.query_attr(db, FEATURE_ATTR) { - let attr = attr_syntax.structurize(db); - let feature_name = match &attr.args[..] { - [ - AttributeArg { - variant: AttributeArgVariant::Unnamed { value: ast::Expr::String(value), .. }, - .. - }, - ] => value.text(db), - _ => { - diagnostics - .report_by_ptr(attr.args_stable_ptr.untyped(), UnsupportedFeatureAttrArguments); - continue; - } - }; - features.push(feature_name); - } - features -} - /// 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( @@ -2914,37 +2803,3 @@ fn validate_statement_attributes(ctx: &mut ComputationContext<'_>, syntax: &ast: ); } } - -/// Adds diagnostics if an expression using an unstable feature is not explicitly allowed to use the -/// feature. -fn validate_unstable_feature_usage( - ctx: &mut ComputationContext<'_>, - attr: Attribute, - stable_ptr: ExprPtr, -) { - let Some(feature_name) = attr.args.iter().find_map(|arg| match &arg.variant { - AttributeArgVariant::Named { value: ast::Expr::String(value), name, .. } - if name == "feature" => - { - Some(value.text(ctx.db.upcast())) - } - // TODO(orizi): Creates diagnostics for this case. - _ => None, - }) else { - return; - }; - let mut env = &ctx.environment; - loop { - if env.allowed_features.contains(feature_name.as_str()) { - // The feature is allowed. - return; - } - if let Some(parent) = env.parent.as_ref() { - // Continue checking if the feature was allowed up the tree. - env = parent; - } else { - ctx.diagnostics.report_by_ptr(stable_ptr.untyped(), UnstableFeature { feature_name }); - return; - } - } -} diff --git a/crates/cairo-lang-semantic/src/items/constant.rs b/crates/cairo-lang-semantic/src/items/constant.rs index 6b16ae321c0..c32f8c88baa 100644 --- a/crates/cairo-lang-semantic/src/items/constant.rs +++ b/crates/cairo-lang-semantic/src/items/constant.rs @@ -15,6 +15,7 @@ use itertools::Itertools; use num_bigint::BigInt; use num_traits::{Num, ToPrimitive, Zero}; +use super::feature_kind::extract_allowed_features; use super::functions::{GenericFunctionId, GenericFunctionWithBodyId}; use super::structure::SemanticStructEx; use crate::corelib::{ @@ -139,6 +140,8 @@ pub fn priv_constant_semantic_data( let lookup_item_id = LookupItemId::ModuleItem(ModuleItemId::Constant(const_id)); let inference_id = InferenceId::LookupItemDeclaration(lookup_item_id); let mut resolver = Resolver::new(db, module_file_id, inference_id); + resolver.data.allowed_features = + extract_allowed_features(db.upcast(), &const_id, &const_ast, &mut diagnostics); let const_type = resolve_type( db, @@ -147,7 +150,7 @@ pub fn priv_constant_semantic_data( &const_ast.type_clause(syntax_db).ty(syntax_db), ); - let environment = Environment::from_lookup_item_id(db, lookup_item_id, &mut diagnostics); + let environment = Environment::empty(); let mut ctx = ComputationContext::new(db, &mut diagnostics, None, resolver, None, environment); let value = compute_expr_semantic(&mut ctx, &const_ast.value(syntax_db)); diff --git a/crates/cairo-lang-semantic/src/items/extern_function.rs b/crates/cairo-lang-semantic/src/items/extern_function.rs index d80e0bc53c6..33f1ab788d3 100644 --- a/crates/cairo-lang-semantic/src/items/extern_function.rs +++ b/crates/cairo-lang-semantic/src/items/extern_function.rs @@ -8,6 +8,7 @@ use cairo_lang_syntax::attribute::structured::AttributeListStructurize; use cairo_lang_syntax::node::{TypedStablePtr, TypedSyntaxNode}; use cairo_lang_utils::extract_matches; +use super::feature_kind::extract_allowed_features; use super::function_with_body::get_inline_config; use super::functions::{FunctionDeclarationData, GenericFunctionId, InlineConfiguration}; use super::generics::{semantic_generic_params, GenericParamsData}; @@ -162,8 +163,14 @@ pub fn priv_extern_function_declaration_data( (*generic_params_data.resolver_data).clone_with_inference_id(db, inference_id), ); diagnostics.diagnostics.extend(generic_params_data.diagnostics); + resolver.data.allowed_features = extract_allowed_features( + db.upcast(), + &extern_function_id, + &extern_function_syntax, + &mut diagnostics, + ); - let mut environment = Environment::from_lookup_item_id(db, lookup_item_id, &mut diagnostics); + let mut environment = Environment::empty(); let signature_syntax = declaration.signature(syntax_db); let signature = semantic::Signature::from_ast( &mut diagnostics, diff --git a/crates/cairo-lang-semantic/src/items/feature_kind.rs b/crates/cairo-lang-semantic/src/items/feature_kind.rs new file mode 100644 index 00000000000..33387ceecfc --- /dev/null +++ b/crates/cairo-lang-semantic/src/items/feature_kind.rs @@ -0,0 +1,125 @@ +use cairo_lang_defs::db::DefsGroup; +use cairo_lang_defs::diagnostic_utils::StableLocation; +use cairo_lang_defs::ids::{LanguageElementId, ModuleId}; +use cairo_lang_diagnostics::DiagnosticsBuilder; +use cairo_lang_syntax::attribute::consts::{FEATURE_ATTR, UNSTABLE_ATTR}; +use cairo_lang_syntax::attribute::structured::{ + AttributeArg, AttributeArgVariant, AttributeStructurize, +}; +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::helpers::QueryAttrs; +use cairo_lang_syntax::node::{ast, Terminal, TypedStablePtr}; +use cairo_lang_utils::ordered_hash_set::OrderedHashSet; +use smol_str::SmolStr; + +use crate::diagnostic::{SemanticDiagnosticKind, SemanticDiagnostics}; +use crate::SemanticDiagnostic; + +/// The kind of a feature for an item. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FeatureKind { + /// The feature of the item is stable. + Stable, + /// The feature of the item is unstable, with the given name. + Unstable(String), +} +impl FeatureKind { + pub fn from_ast( + db: &dyn SyntaxGroup, + diagnostics: &mut DiagnosticsBuilder, + attributes: &ast::AttributeList, + ) -> Self { + let unstable_attrs = attributes.query_attr(db, UNSTABLE_ATTR); + if unstable_attrs.is_empty() { + return Self::Stable; + }; + let Ok([unstable_attr]): Result<[_; 1], _> = unstable_attrs.try_into() else { + diagnostics.add(SemanticDiagnostic::new( + StableLocation::from_ast(attributes), + SemanticDiagnosticKind::MultipleFeatureAttributes, + )); + return Self::Stable; + }; + let unstable_attr = unstable_attr.structurize(db); + let Ok([arg]): Result<[_; 1], _> = unstable_attr.args.try_into() else { + diagnostics.add(SemanticDiagnostic::new( + StableLocation::new(unstable_attr.args_stable_ptr.untyped()), + SemanticDiagnosticKind::UnsupportedUnstableAttrArguments, + )); + return Self::Stable; + }; + match arg.variant { + AttributeArgVariant::Named { value: ast::Expr::String(value), name, .. } + if name == "feature" => + { + Self::Unstable(value.text(db).into()) + } + _ => { + diagnostics.add(SemanticDiagnostic::new( + StableLocation::new(arg.arg_stable_ptr.untyped()), + SemanticDiagnosticKind::UnsupportedUnstableAttrArguments, + )); + Self::Stable + } + } + } +} + +/// Returns the allowed features of an object which supports attributes. +pub fn extract_item_allowed_features( + db: &dyn SyntaxGroup, + syntax: &impl QueryAttrs, + diagnostics: &mut SemanticDiagnostics, +) -> OrderedHashSet { + let mut features = OrderedHashSet::default(); + for attr_syntax in syntax.query_attr(db, FEATURE_ATTR) { + let attr = attr_syntax.structurize(db); + let feature_name = match &attr.args[..] { + [ + AttributeArg { + variant: AttributeArgVariant::Unnamed { value: ast::Expr::String(value), .. }, + .. + }, + ] => value.text(db), + _ => { + diagnostics.report_by_ptr( + attr.args_stable_ptr.untyped(), + SemanticDiagnosticKind::UnsupportedFeatureAttrArguments, + ); + continue; + } + }; + features.insert(feature_name); + } + features +} + +/// Extracts the allowed features of an element, considering its parent modules as well as its +/// attributes. +pub fn extract_allowed_features( + db: &dyn DefsGroup, + element_id: &impl LanguageElementId, + syntax: &impl QueryAttrs, + diagnostics: &mut SemanticDiagnostics, +) -> OrderedHashSet { + let syntax_db = db.upcast(); + let mut allowed_features = extract_item_allowed_features(syntax_db, syntax, diagnostics); + let ignored_diagnostics = + &mut SemanticDiagnostics::new(element_id.module_file_id(db).file_id(db).unwrap()); + let mut curr_module_id = element_id.parent_module(db); + loop { + let submodule_id = match curr_module_id { + ModuleId::CrateRoot(_) => break, + ModuleId::Submodule(id) => id, + }; + let parent = submodule_id.parent_module(db); + let module = &db.module_submodules(parent).unwrap()[&submodule_id]; + // TODO(orizi): Add parent module diagnostics. + for allowed_feature in extract_item_allowed_features(syntax_db, module, ignored_diagnostics) + { + allowed_features.insert(allowed_feature); + } + curr_module_id = parent; + } + allowed_features +} diff --git a/crates/cairo-lang-semantic/src/items/free_function.rs b/crates/cairo-lang-semantic/src/items/free_function.rs index c73c3497a02..19a40223b01 100644 --- a/crates/cairo-lang-semantic/src/items/free_function.rs +++ b/crates/cairo-lang-semantic/src/items/free_function.rs @@ -9,6 +9,7 @@ use cairo_lang_syntax::attribute::structured::AttributeListStructurize; use cairo_lang_syntax::node::{TypedStablePtr, TypedSyntaxNode}; use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; +use super::feature_kind::extract_allowed_features; use super::function_with_body::{get_inline_config, FunctionBody, FunctionBodyData}; use super::functions::{ forbid_inline_always_with_impl_generic_param, FunctionDeclarationData, InlineConfiguration, @@ -146,8 +147,14 @@ pub fn priv_free_function_declaration_data( (*generic_params_data.resolver_data).clone_with_inference_id(db, inference_id), ); diagnostics.diagnostics.extend(generic_params_data.diagnostics); + resolver.data.allowed_features = extract_allowed_features( + db.upcast(), + &free_function_id, + &free_function_syntax, + &mut diagnostics, + ); - let mut environment = Environment::from_lookup_item_id(db, lookup_item_id, &mut diagnostics); + let mut environment = Environment::empty(); let signature_syntax = declaration.signature(syntax_db); let signature = semantic::Signature::from_ast( diff --git a/crates/cairo-lang-semantic/src/items/functions.rs b/crates/cairo-lang-semantic/src/items/functions.rs index 578edf01892..b0afad0c472 100644 --- a/crates/cairo-lang-semantic/src/items/functions.rs +++ b/crates/cairo-lang-semantic/src/items/functions.rs @@ -17,7 +17,7 @@ use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; use cairo_lang_utils::{define_short_id, try_extract_matches, OptionFrom}; use itertools::{chain, Itertools}; use smol_str::SmolStr; -use syntax::attribute::consts::{MUST_USE_ATTR, UNSTABLE_ATTR}; +use syntax::attribute::consts::MUST_USE_ATTR; use syntax::node::TypedStablePtr; use super::attribute::SemanticQueryAttrs; @@ -185,15 +185,6 @@ impl GenericFunctionId { GenericFunctionId::Extern(_) => Ok(false), } } - /// Returns the attribute if a function has the `#[unstable(feature: "some-string")]` attribute. - pub fn unstable_feature(&self, db: &dyn SemanticGroup) -> Maybe> { - match self { - GenericFunctionId::Free(id) => id.find_attr(db, UNSTABLE_ATTR), - GenericFunctionId::Impl(id) => id.function.find_attr(db, UNSTABLE_ATTR), - GenericFunctionId::Extern(_) => Ok(None), - } - } - /// Returns true if the function does not depend on any generics. pub fn is_fully_concrete(&self, db: &dyn SemanticGroup) -> bool { match self { diff --git a/crates/cairo-lang-semantic/src/items/imp.rs b/crates/cairo-lang-semantic/src/items/imp.rs index 78bd575b564..d293eef5ade 100644 --- a/crates/cairo-lang-semantic/src/items/imp.rs +++ b/crates/cairo-lang-semantic/src/items/imp.rs @@ -30,6 +30,7 @@ use syntax::node::ids::SyntaxStablePtrId; use syntax::node::{Terminal, TypedStablePtr, TypedSyntaxNode}; use super::enm::SemanticEnumEx; +use super::feature_kind::extract_allowed_features; use super::function_with_body::{get_inline_config, FunctionBody, FunctionBodyData}; use super::functions::{ forbid_inline_always_with_impl_generic_param, FunctionDeclarationData, InlineConfiguration, @@ -1529,10 +1530,12 @@ pub fn priv_impl_function_declaration_data( (*generic_params_data.resolver_data).clone_with_inference_id(db, inference_id), ); diagnostics.diagnostics.extend(generic_params_data.diagnostics); + resolver.data.allowed_features = + extract_allowed_features(db.upcast(), &impl_function_id, function_syntax, &mut diagnostics); let signature_syntax = declaration.signature(syntax_db); - let mut environment = Environment::from_lookup_item_id(db, lookup_item_id, &mut diagnostics); + let mut environment = Environment::empty(); let signature = semantic::Signature::from_ast( &mut diagnostics, db, diff --git a/crates/cairo-lang-semantic/src/items/mod.rs b/crates/cairo-lang-semantic/src/items/mod.rs index 50217ae7ead..608f4a999c3 100644 --- a/crates/cairo-lang-semantic/src/items/mod.rs +++ b/crates/cairo-lang-semantic/src/items/mod.rs @@ -12,6 +12,7 @@ pub mod constant; pub mod enm; pub mod extern_function; pub mod extern_type; +pub mod feature_kind; pub mod fmt; pub mod free_function; pub mod function_with_body; diff --git a/crates/cairo-lang-semantic/src/items/module.rs b/crates/cairo-lang-semantic/src/items/module.rs index 38cbeacb08f..d03f66d74e2 100644 --- a/crates/cairo-lang-semantic/src/items/module.rs +++ b/crates/cairo-lang-semantic/src/items/module.rs @@ -4,9 +4,7 @@ use std::sync::Arc; use cairo_lang_defs::db::DefsGroup; use cairo_lang_defs::diagnostic_utils::StableLocation; use cairo_lang_defs::ids::{ - ConstantId, EnumId, ExternFunctionId, ExternTypeId, FreeFunctionId, ImplAliasId, ImplDefId, - LanguageElementId, ModuleId, ModuleItemId, ModuleTypeAliasId, NamedLanguageElementId, StructId, - SubmoduleId, TraitId, UseId, + LanguageElementId, ModuleId, ModuleItemId, NamedLanguageElementId, TraitId, }; use cairo_lang_diagnostics::{Diagnostics, DiagnosticsBuilder, Maybe}; use cairo_lang_syntax::attribute::structured::{Attribute, AttributeListStructurize}; @@ -16,6 +14,7 @@ use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use cairo_lang_utils::ordered_hash_set::OrderedHashSet; use smol_str::SmolStr; +use super::feature_kind::FeatureKind; use super::us::SemanticUseEx; use super::visibility::Visibility; use crate::db::SemanticGroup; @@ -28,6 +27,7 @@ use crate::SemanticDiagnostic; pub struct ModuleItemInfo { pub item_id: ModuleItemId, pub visibility: Visibility, + pub feature_kind: FeatureKind, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -41,55 +41,81 @@ pub fn priv_module_semantic_data( db: &dyn SemanticGroup, module_id: ModuleId, ) -> Maybe> { - let def_db = db.upcast(); + let def_db: &dyn DefsGroup = db.upcast(); + let syntax_db = db.upcast(); // We use the builder here since the items can come from different file_ids. let mut diagnostics = DiagnosticsBuilder::default(); let mut items = OrderedHashMap::default(); - let visibility_extractor = VisibilityExtractor { db: def_db, module_id }; for item_id in db.module_items(module_id)?.iter().copied() { - let (name, visibility) = match &item_id { + let (name, attributes, visibility) = match &item_id { ModuleItemId::Constant(item_id) => { - (item_id.name(def_db), visibility_extractor.constant(item_id)) + let item = &def_db.module_constants(module_id)?[item_id]; + (item_id.name(def_db), item.attributes(syntax_db), item.visibility(syntax_db)) } ModuleItemId::Submodule(item_id) => { - (item_id.name(def_db), visibility_extractor.submodule(item_id)) + let item = &def_db.module_submodules(module_id)?[item_id]; + (item_id.name(def_db), item.attributes(syntax_db), item.visibility(syntax_db)) } ModuleItemId::Use(item_id) => { - (item_id.name(def_db), visibility_extractor.use_(item_id)) + let use_ast = &def_db.module_uses(module_id)?[item_id]; + let use_path = ast::UsePath::Leaf(use_ast.clone()); + let mut node = use_path.as_syntax_node(); + let item = loop { + let Some(parent) = node.parent() else { + unreachable!("UsePath is not under an ItemUse."); + }; + match parent.kind(syntax_db) { + SyntaxKind::ItemUse => { + break ast::ItemUse::from_syntax_node(syntax_db, parent); + } + _ => node = parent, + } + }; + (item_id.name(def_db), item.attributes(syntax_db), item.visibility(syntax_db)) } ModuleItemId::FreeFunction(item_id) => { - (item_id.name(def_db), visibility_extractor.free_function(item_id)) + let item = &def_db.module_free_functions(module_id)?[item_id]; + (item_id.name(def_db), item.attributes(syntax_db), item.visibility(syntax_db)) } ModuleItemId::Struct(item_id) => { - (item_id.name(def_db), visibility_extractor.struct_(item_id)) + let item = &def_db.module_structs(module_id)?[item_id]; + (item_id.name(def_db), item.attributes(syntax_db), item.visibility(syntax_db)) } ModuleItemId::Enum(item_id) => { - (item_id.name(def_db), visibility_extractor.enum_(item_id)) + let item = &def_db.module_enums(module_id)?[item_id]; + (item_id.name(def_db), item.attributes(syntax_db), item.visibility(syntax_db)) } ModuleItemId::TypeAlias(item_id) => { - (item_id.name(def_db), visibility_extractor.type_alias(item_id)) + let item = &def_db.module_type_aliases(module_id)?[item_id]; + (item_id.name(def_db), item.attributes(syntax_db), item.visibility(syntax_db)) } ModuleItemId::ImplAlias(item_id) => { - (item_id.name(def_db), visibility_extractor.impl_alias(item_id)) + let item = &def_db.module_impl_aliases(module_id)?[item_id]; + (item_id.name(def_db), item.attributes(syntax_db), item.visibility(syntax_db)) } ModuleItemId::Trait(item_id) => { - (item_id.name(def_db), visibility_extractor.trait_(item_id)) + let item = &def_db.module_traits(module_id)?[item_id]; + (item_id.name(def_db), item.attributes(syntax_db), item.visibility(syntax_db)) } ModuleItemId::Impl(item_id) => { - (item_id.name(def_db), visibility_extractor.impl_def(item_id)) + let item = &def_db.module_impls(module_id)?[item_id]; + (item_id.name(def_db), item.attributes(syntax_db), item.visibility(syntax_db)) } ModuleItemId::ExternType(item_id) => { - (item_id.name(def_db), visibility_extractor.extern_type(item_id)) + let item = &def_db.module_extern_types(module_id)?[item_id]; + (item_id.name(def_db), item.attributes(syntax_db), item.visibility(syntax_db)) } ModuleItemId::ExternFunction(item_id) => { - (item_id.name(def_db), visibility_extractor.extern_function(item_id)) + let item = &def_db.module_extern_functions(module_id)?[item_id]; + (item_id.name(def_db), item.attributes(syntax_db), item.visibility(syntax_db)) } }; - // Defaulting to pub as if diagnostics are added privacy diagnostics are less interesting. - let visibility = visibility - .map(|v| Visibility::from_ast(db.upcast(), &mut diagnostics, &v)) - .unwrap_or(Visibility::Public); - if items.insert(name.clone(), ModuleItemInfo { item_id, visibility }).is_some() { + let visibility = Visibility::from_ast(db.upcast(), &mut diagnostics, &visibility); + let feature_kind = FeatureKind::from_ast(db.upcast(), &mut diagnostics, &attributes); + if items + .insert(name.clone(), ModuleItemInfo { item_id, visibility, feature_kind }) + .is_some() + { // `item` is extracted from `module_items` and thus `module_item_name_stable_ptr` is // guaranteed to succeed. let stable_location = @@ -101,64 +127,6 @@ pub fn priv_module_semantic_data( Ok(Arc::new(ModuleSemanticData { items, diagnostics: diagnostics.build() })) } -/// Extracts the visibility ast of an item from the database. -struct VisibilityExtractor<'a> { - db: &'a dyn DefsGroup, - module_id: ModuleId, -} -impl<'a> VisibilityExtractor<'a> { - fn submodule(&self, item_id: &SubmoduleId) -> Maybe { - Ok(self.db.module_submodules(self.module_id)?[item_id].visibility(self.db.upcast())) - } - fn constant(&self, item_id: &ConstantId) -> Maybe { - Ok(self.db.module_constants(self.module_id)?[item_id].visibility(self.db.upcast())) - } - fn free_function(&self, item_id: &FreeFunctionId) -> Maybe { - Ok(self.db.module_free_functions(self.module_id)?[item_id].visibility(self.db.upcast())) - } - fn enum_(&self, item_id: &EnumId) -> Maybe { - Ok(self.db.module_enums(self.module_id)?[item_id].visibility(self.db.upcast())) - } - fn struct_(&self, item_id: &StructId) -> Maybe { - Ok(self.db.module_structs(self.module_id)?[item_id].visibility(self.db.upcast())) - } - fn extern_function(&self, item_id: &ExternFunctionId) -> Maybe { - Ok(self.db.module_extern_functions(self.module_id)?[item_id].visibility(self.db.upcast())) - } - fn extern_type(&self, item_id: &ExternTypeId) -> Maybe { - Ok(self.db.module_extern_types(self.module_id)?[item_id].visibility(self.db.upcast())) - } - fn type_alias(&self, item_id: &ModuleTypeAliasId) -> Maybe { - Ok(self.db.module_type_aliases(self.module_id)?[item_id].visibility(self.db.upcast())) - } - fn impl_alias(&self, item_id: &ImplAliasId) -> Maybe { - Ok(self.db.module_impl_aliases(self.module_id)?[item_id].visibility(self.db.upcast())) - } - fn trait_(&self, item_id: &TraitId) -> Maybe { - Ok(self.db.module_traits(self.module_id)?[item_id].visibility(self.db.upcast())) - } - fn impl_def(&self, item_id: &ImplDefId) -> Maybe { - Ok(self.db.module_impls(self.module_id)?[item_id].visibility(self.db.upcast())) - } - fn use_(&self, item_id: &UseId) -> Maybe { - let use_ast = &self.db.module_uses(self.module_id)?[item_id]; - let use_path = ast::UsePath::Leaf(use_ast.clone()); - let mut node = use_path.as_syntax_node(); - let syntax_db = self.db.upcast(); - while let Some(parent) = node.parent() { - match parent.kind(syntax_db) { - SyntaxKind::ItemUse => { - return Ok( - ast::ItemUse::from_syntax_node(syntax_db, parent).visibility(syntax_db) - ); - } - _ => node = parent, - } - } - unreachable!("UsePath is not under an ItemUse."); - } -} - pub fn module_item_by_name( db: &dyn SemanticGroup, module_id: ModuleId, diff --git a/crates/cairo-lang-semantic/src/items/trt.rs b/crates/cairo-lang-semantic/src/items/trt.rs index 84b3b632fc1..08e0f8ca5f1 100644 --- a/crates/cairo-lang-semantic/src/items/trt.rs +++ b/crates/cairo-lang-semantic/src/items/trt.rs @@ -19,6 +19,7 @@ use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; use itertools::chain; use smol_str::SmolStr; +use super::feature_kind::extract_allowed_features; use super::function_with_body::{get_implicit_precedence, get_inline_config, FunctionBodyData}; use super::functions::{FunctionDeclarationData, ImplicitPrecedence, InlineConfiguration}; use super::generics::{semantic_generic_params, GenericParamsData}; @@ -768,9 +769,14 @@ pub fn priv_trait_function_declaration_data( (*function_generic_params_data.resolver_data).clone_with_inference_id(db, inference_id), ); diagnostics.diagnostics.extend(function_generic_params_data.diagnostics); - + resolver.data.allowed_features = extract_allowed_features( + db.upcast(), + &trait_function_id, + function_syntax, + &mut diagnostics, + ); let signature_syntax = declaration.signature(syntax_db); - let mut environment = Environment::from_lookup_item_id(db, lookup_item_id, &mut diagnostics); + let mut environment = Environment::empty(); let signature = semantic::Signature::from_ast( &mut diagnostics, db, diff --git a/crates/cairo-lang-semantic/src/resolve/mod.rs b/crates/cairo-lang-semantic/src/resolve/mod.rs index b1338671e2e..722cc544905 100644 --- a/crates/cairo-lang-semantic/src/resolve/mod.rs +++ b/crates/cairo-lang-semantic/src/resolve/mod.rs @@ -15,6 +15,7 @@ use cairo_lang_syntax::node::helpers::PathSegmentEx; use cairo_lang_syntax::node::ids::SyntaxStablePtrId; use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; +use cairo_lang_utils::ordered_hash_set::OrderedHashSet; use cairo_lang_utils::try_extract_matches; use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; pub use item::{ResolvedConcreteItem, ResolvedGenericItem}; @@ -33,6 +34,7 @@ use crate::expr::inference::infers::InferenceEmbeddings; use crate::expr::inference::{Inference, InferenceData, InferenceId}; use crate::items::constant::{resolve_const_expr_and_evaluate, ConstValue}; use crate::items::enm::SemanticEnumEx; +use crate::items::feature_kind::FeatureKind; use crate::items::functions::{GenericFunctionId, ImplGenericFunctionId}; use crate::items::imp::{ConcreteImplId, ConcreteImplLongId, ImplId, ImplLookupContext}; use crate::items::module::ModuleItemInfo; @@ -104,6 +106,7 @@ pub struct ResolverData { pub inference_data: InferenceData, /// The trait/impl context the resolver is currently in. Used to resolve "Self::" paths. pub trait_or_impl_ctx: TraitOrImplContext, + pub allowed_features: OrderedHashSet, } impl ResolverData { pub fn new(module_file_id: ModuleFileId, inference_id: InferenceId) -> Self { @@ -114,6 +117,7 @@ impl ResolverData { resolved_items: Default::default(), inference_data: InferenceData::new(inference_id), trait_or_impl_ctx: TraitOrImplContext::None, + allowed_features: Default::default(), } } pub fn clone_with_inference_id( @@ -128,6 +132,7 @@ impl ResolverData { resolved_items: self.resolved_items.clone(), inference_data: self.inference_data.clone_with_inference_id(db, inference_id), trait_or_impl_ctx: self.trait_or_impl_ctx, + allowed_features: self.allowed_features.clone(), } } } @@ -528,12 +533,7 @@ impl<'db> Resolver<'db> { .db .module_item_info_by_name(*module_id, ident)? .ok_or_else(|| diagnostics.report(identifier, PathNotFound(item_type)))?; - self.validate_item_visibility( - diagnostics, - *module_id, - identifier, - &inner_item_info, - ); + self.validate_item_usability(diagnostics, *module_id, identifier, &inner_item_info); let inner_generic_item = ResolvedGenericItem::from_module_item(self.db, inner_item_info.item_id)?; Ok(self.specialize_generic_module_item( @@ -740,12 +740,7 @@ impl<'db> Resolver<'db> { .db .module_item_info_by_name(*module_id, ident)? .ok_or_else(|| diagnostics.report(identifier, PathNotFound(item_type)))?; - self.validate_item_visibility( - diagnostics, - *module_id, - identifier, - &inner_item_info, - ); + self.validate_item_usability(diagnostics, *module_id, identifier, &inner_item_info); ResolvedGenericItem::from_module_item(self.db, inner_item_info.item_id) } ResolvedGenericItem::GenericType(GenericTypeId::Enum(enum_id)) => { @@ -1106,22 +1101,35 @@ impl<'db> Resolver<'db> { || self.edition.ignore_visibility() && owning_crate == self.db.core_crate() } - /// Validates that an item is visible from the current module or adds a diagnostic. - fn validate_item_visibility( + /// Validates that an item is usable from the current module or adds a diagnostic. + /// This includes visibility checks and feature checks. + fn validate_item_usability( &self, diagnostics: &mut SemanticDiagnostics, containing_module_id: ModuleId, identifier: &ast::TerminalIdentifier, item_info: &ModuleItemInfo, ) { - if self.ignore_visibility_checks(containing_module_id) { - return; - } let db = self.db.upcast(); - let user_module = self.module_file_id.0; - if !visibility::peek_visible_in(db, item_info.visibility, containing_module_id, user_module) - { - diagnostics.report(identifier, ItemNotVisible { item_id: item_info.item_id }); + if !self.ignore_visibility_checks(containing_module_id) { + let user_module = self.module_file_id.0; + if !visibility::peek_visible_in( + db, + item_info.visibility, + containing_module_id, + user_module, + ) { + diagnostics.report(identifier, ItemNotVisible { item_id: item_info.item_id }); + } + } + match &item_info.feature_kind { + FeatureKind::Stable => {} + FeatureKind::Unstable(feature) => { + if !self.data.allowed_features.contains(feature.as_str()) { + diagnostics + .report(identifier, UnstableFeature { feature_name: feature.into() }); + } + } } } } diff --git a/crates/cairo-lang-semantic/src/types.rs b/crates/cairo-lang-semantic/src/types.rs index 30d5fc05934..094f024c181 100644 --- a/crates/cairo-lang-semantic/src/types.rs +++ b/crates/cairo-lang-semantic/src/types.rs @@ -5,8 +5,7 @@ use cairo_lang_defs::ids::{ }; use cairo_lang_diagnostics::{DiagnosticAdded, Maybe}; use cairo_lang_proc_macros::SemanticObject; -use cairo_lang_syntax::attribute::consts::{MUST_USE_ATTR, UNSTABLE_ATTR}; -use cairo_lang_syntax::attribute::structured::Attribute; +use cairo_lang_syntax::attribute::consts::MUST_USE_ATTR; use cairo_lang_syntax::node::ids::SyntaxStablePtrId; use cairo_lang_syntax::node::{ast, TypedStablePtr, TypedSyntaxNode}; use cairo_lang_utils::{define_short_id, try_extract_matches, OptionFrom}; @@ -228,14 +227,6 @@ impl ConcreteTypeId { ConcreteTypeId::Extern(id) => id.has_attr(db, MUST_USE_ATTR), } } - /// Returns the attribute if a type has the `#[unstable(feature: "some-string")]` attribute. - pub fn unstable_attr(&self, db: &dyn SemanticGroup) -> Maybe> { - match self { - ConcreteTypeId::Struct(id) => id.find_attr(db, UNSTABLE_ATTR), - ConcreteTypeId::Enum(id) => id.find_attr(db, UNSTABLE_ATTR), - ConcreteTypeId::Extern(_) => Ok(None), - } - } /// Returns true if the type does not depend on any generics. pub fn is_fully_concrete(&self, db: &dyn SemanticGroup) -> bool { self.generic_args(db)