diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f7bbda..391d2f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ - Linting warnings can now annotate their messages with which linting rule enforces it. You can turn this off by setting 'annotate_lints' to 'false' in your lint config file - Minor fixes to the description of linting messages - Fixed an issue where diagnostics would sometime be reported within the wrong file +- Corrected incorrect parsing around tuple-declared 'local' variables in for-loops +- Added the ability to declare 'saved' or 'session' variables in for-loops +- Added functionality for goto-def/decl/ref to or from variables declared + within a function scope. ## 0.9.11 - Fixed deadlock when a configuration update happens diff --git a/src/analysis/mod.rs b/src/analysis/mod.rs index 8380e90..75caca0 100644 --- a/src/analysis/mod.rs +++ b/src/analysis/mod.rs @@ -29,7 +29,7 @@ use rayon::prelude::*; use crate::actions::SourcedDMLError; use crate::actions::analysis_storage::TimestampedStorage; use crate::analysis::symbols::{SimpleSymbol, DMLSymbolKind, Symbol, - SymbolSource}; + SymbolContainer, StructureSymbol, SymbolSource}; use crate::analysis::reference::{Reference, GlobalReference, VariableReference, ReferenceKind, NodeRef}; @@ -45,7 +45,9 @@ pub use crate::analysis::parsing::tree:: use crate::analysis::parsing::tree::{MissingToken, MissingContent, TreeElement}; use crate::analysis::structure::objects::{Template, Import, CompObjectKind, ParamValue}; +use crate::analysis::structure::statements::{ForPre, Statement, StatementKind}; use crate::analysis::structure::toplevel::{ObjectDecl, TopLevel}; +use crate::analysis::structure::types::DMLType; use crate::analysis::structure::expressions::{Expression, ExpressionKind, DMLString}; use crate::analysis::templating::objects::{make_device, DMLObject, @@ -312,6 +314,53 @@ pub struct IsolatedAnalysis { pub errors: Vec, } +// Invariant: range covers all ranges in sub_ranges +#[derive(Debug, Clone)] +pub struct RangeEntry { + range: ZeroRange, + symbols: HashMap, + // TODO: Consider replacing with a spatial-search data-structure + // e.g. segment or interval tree + sub_ranges: Vec, +} + +impl RangeEntry { + fn is_empty(&self) -> bool { + self.symbols.is_empty() + && self.sub_ranges.iter().all(|sub|sub.is_empty()) + } + fn find_symbol_for_dmlname(&self, name: &DMLString) -> Option<&SymbolRef> { + self.find_symbol_for_name( + &name.val, name.span.start_position().position) + } + + fn find_symbol_for_name(&self, + name: &str, + loc: ZeroPosition) -> Option<&SymbolRef> { + let possible_symbol = self.find_symbol_for_name_aux(name, loc)?; + // Don't find symbols in the same scope that are declared after + // the reference + if loc > possible_symbol.lock().unwrap().loc.range.start() { + Some(possible_symbol) + } else { + None + } + } + + fn find_symbol_for_name_aux(&self, + name: &str, + loc: ZeroPosition) -> Option<&SymbolRef> { + if self.range.contains_pos(loc) { + // Look into deeper scopes first + self.sub_ranges.iter() + .find_map(|re|re.find_symbol_for_name_aux(name, loc)) + .or_else(||self.symbols.get(name)) + } else { + None + } + } +} + // Mas symbol decl locations to symbols, pub type SymbolRef = Arc>; #[derive(Debug, Clone, Default)] @@ -546,10 +595,11 @@ impl DeviceAnalysis { |obj|match obj.resolve(&self.objects) { DMLResolvedObject::CompObject(robj) => Some(robj), DMLResolvedObject::ShallowObject(sobj) => { - error!("Internal Error: \ - Wanted to find context {:?} in {:?} \ - which is not \ - a composite object", sobj, context_chain); + internal_error!( + "Internal Error: \ + Wanted to find context {:?} in {:?} \ + which is not \ + a composite object", sobj, context_chain); None } }).collect(), @@ -580,8 +630,9 @@ impl DeviceAnalysis { -> Option> { // Should be guaranteed by caller responsibility if context_chain.is_empty() { - error!("Internal Error: context chain invariant broken at {:?}", - curr_obj.identity()); + internal_error!( + "context chain invariant broken at {:?}", + curr_obj.identity()); } let (first, rest) = context_chain.split_first().unwrap(); @@ -599,10 +650,10 @@ impl DeviceAnalysis { vec![found_obj.clone()] } else { - error!("Internal Error: Context chain \ - suggested {:?} should be a method, \ - but it wasn't", - found_obj.resolve(&self.objects)); + internal_error!( + "Context chain suggested {:?} should be a \ + method, but it wasn't", + found_obj.resolve(&self.objects)); vec![] } } @@ -632,15 +683,15 @@ impl DeviceAnalysis { limitations); } else { - error!("Internal Error: No template->objects map for\ - {:?}", sym); + internal_error!( + "No template->objects map for {:?}", sym); vec![] } } else { - error!("Internal Error: \ - Wanted to find context {:?} in {:?} which is not \ - a known template object", - first, curr_obj); + internal_error!( + "Wanted to find context {:?} in {:?} which is not \ + a known template object", + first, curr_obj); return None; }, ContextKey::AllWithTemplate(_, templates) => @@ -735,42 +786,21 @@ impl DeviceAnalysis { self.lookup_def_in_comp_object(o, name, type_hint), DMLResolvedObject::ShallowObject(o) => match &o.variant { - DMLShallowObjectVariant::Method(m) => - match name { - "this" => - ReferenceMatch::Found( - vec![Arc::clone( - self.symbol_info.object_symbols.get( - &o.parent).unwrap())]), - "default" => - // NOTE: This is part of the hack that maps default - // references in methods to the corret method decl. - // Here, we simply check if the method has any - // default call, and if so map the reference to the - // method symbol - if m.get_default().is_some() { - self.symbol_info.method_symbols - .get(obj.location()) - .map_or_else( - ||ReferenceMatch::NotFound(vec![]), - |sym|ReferenceMatch::Found(vec![ - Arc::clone(sym)])) - } else { - // fairly sure 'default' cannot be a - // reference otherwise - // TODO: better error message here, somehow? - ReferenceMatch::NotFound(vec![]) - }, - _ => if let Some(sym) = m.args().iter() - .find(|a|a.name().val == name) - .map(|a|self.symbol_info.variable_symbols - .get(a.loc_span()).unwrap()) - { - ReferenceMatch::Found(vec![Arc::clone(sym)]) - } else { - ReferenceMatch::NotFound(vec![]) - }, - }, + DMLShallowObjectVariant::Method(m) => { + let syms: Vec<_> = m.get_decl().symbols().into_iter() + .filter(|struct_sym| + struct_sym.get_name().as_str() == name) + .filter_map(|struct_sym| + self.symbol_info.variable_symbols.get( + struct_sym.loc_span())) + .map(Arc::clone) + .collect(); + if syms.is_empty() { + ReferenceMatch::NotFound(vec![]) + } else { + ReferenceMatch::Found(syms) + } + }, DMLShallowObjectVariant::Parameter(p) => { if let Some(param) = p.get_unambiguous_def() { // TODO: Remove this when we can resolve 'dev' param @@ -985,50 +1015,128 @@ impl DeviceAnalysis { // TODO: Do we need to fall back on globals here? Can we get an // identifier from a spot that is generic enough to refer to a // global, but also is in a context where a global makes sense? - error!("Internal Error: Failed to find objects matching {:?}", - sym); + internal_error!("Failed to find objects matching {:?}", sym); vec![] } } fn resolve_noderef_in_symbol<'t>(&'t self, symbol: &'t SymbolRef, - node: &NodeRef) + node: &NodeRef, + method_structure: + &HashMap) -> ReferenceMatch { - if let Ok(sym) = symbol.lock() { - match &sym.source { - SymbolSource::DMLObject(key) => { - self.resolve_noderef_in_obj(key, node) + let sym = symbol.lock().unwrap(); + match &sym.source { + SymbolSource::DMLObject(obj) => { + // The performance overhead is cloning here + // is _probably_ smaller than the one of holding the key + let obj_copy = obj.clone(); + drop(sym); + self.resolve_noderef_in_obj(&obj_copy, node, method_structure) + }, + // TODO: Cannot be resolved without constant folding + SymbolSource::MethodArg(_method, _name) => + ReferenceMatch::NotFound(vec![]), + SymbolSource::MethodLocal(_method, _name) => + ReferenceMatch::NotFound(vec![]), + // TODO: Fix once type system is sorted + SymbolSource::Type(_typed) => + ReferenceMatch::NotFound(vec![]), + // TODO: Handle lookups inside templates + SymbolSource::Template(_templ) => + ReferenceMatch::NotFound(vec![]), + } + } + + fn resolve_simple_noderef_in_method<'c>(&'c self, + obj: &DMLShallowObject, + meth: &Arc, + node: &DMLString, + _type_hint: Option<()>, + method_structure: &HashMap + ) + -> ReferenceMatch { + // TODO: is this 100% true? + // We cannot lookup things from within a method without + // the reference being contained within the span of the method + if !meth.span().contains_pos(&node.span.start_position()) { + return ReferenceMatch::NotFound(vec![]); + } + + match node.val.as_str() { + "this" => + ReferenceMatch::Found( + vec![Arc::clone( + self.symbol_info.object_symbols.get( + &obj.parent).unwrap())]), + "default" => + // NOTE: This is part of the hack that maps default + // references in methods to the corret method decl. + // Here, we simply check if the method has any + // default call, and if so map the reference to the + // method symbol + if meth.get_default().is_some() { + self.symbol_info.method_symbols + .get(obj.location()) + .map_or_else( + ||ReferenceMatch::NotFound(vec![]), + |sym|ReferenceMatch::Found(vec![ + Arc::clone(sym)])) + } else { + // fairly sure 'default' cannot be a + // reference otherwise + // TODO: better error message here, somehow? + ReferenceMatch::NotFound(vec![]) }, - // TODO: Cannot be resolved without constant folding - SymbolSource::MethodArg(_method, _name) => - ReferenceMatch::NotFound(vec![]), - // TODO: Fix once type system is sorted - SymbolSource::Type(_typed) => - ReferenceMatch::NotFound(vec![]), - // TODO: Handle lookups inside templates - SymbolSource::Template(_templ) => - ReferenceMatch::NotFound(vec![]), - } - } else { - error!("Internal Error?: Circular noderef resolve"); - ReferenceMatch::NotFound(vec![]) + _ => { + let local_sym = method_structure.get(meth.location()) + .and_then(|locals|locals.find_symbol_for_dmlname(node)); + let arg_sym = meth.args().iter() + .find(|a|a.name().val == node.val) + .map(|a|self.symbol_info.variable_symbols + .get(a.loc_span()).unwrap()); + match (local_sym, arg_sym) { + // NOTE: it's possible to get a local and an arg symbol, + // this is not an internal error and is reported as a + // conflict while building the symbols + (Some(sym), _) | + (_, Some(sym)) => ReferenceMatch::Found( + vec![Arc::clone(sym)]), + _ => ReferenceMatch::NotFound(vec![]), + } + }, } } fn resolve_noderef_in_obj<'c>(&'c self, obj: &DMLObject, - node: &NodeRef) + node: &NodeRef, + method_structure: &HashMap) -> ReferenceMatch { match node { NodeRef::Simple(simple) => { let resolvedobj = obj.resolve(&self.objects); - self.lookup_def_in_resolved(resolvedobj, - &simple.val, - None) + if let DMLResolvedObject::ShallowObject( + shallow @ DMLShallowObject { + variant: DMLShallowObjectVariant::Method(ref m), + .. + }) = resolvedobj { + + self.resolve_simple_noderef_in_method(shallow, m, simple, + None, + method_structure) + } else { + self.lookup_def_in_resolved(resolvedobj, + &simple.val, + None) + } }, NodeRef::Sub(subnode, simple, _) => { - let sub = self.resolve_noderef_in_obj(obj, subnode); + let sub = self.resolve_noderef_in_obj(obj, + subnode, + method_structure); match sub { ReferenceMatch::Found(syms) => { let wrapped_simple = NodeRef::Simple(simple.clone()); @@ -1036,7 +1144,7 @@ impl DeviceAnalysis { let mut suggestions = vec![]; for sub_result in syms.into_iter().map( |sym|self.resolve_noderef_in_symbol( - &sym, &wrapped_simple)) { + &sym, &wrapped_simple, method_structure)) { match sub_result { ReferenceMatch::Found(mut sub_syms) => found_syms.append(&mut sub_syms), @@ -1061,25 +1169,28 @@ impl DeviceAnalysis { fn lookup_ref_in_obj(&self, obj: &DMLObject, - reference: &VariableReference) + reference: &VariableReference, + method_structure: &HashMap) -> ReferenceMatch { match &reference.kind { ReferenceKind::Template | ReferenceKind::Type => { - error!("Internal Error: Attempted to do a contexted lookup \ - of a global reference {:?}", reference); + internal_error!("Attempted to do a contexted lookup \ + of a global reference {:?}", reference); return ReferenceMatch::NotFound(vec![]); }, _ => (), } - self.resolve_noderef_in_obj(obj, &reference.reference) + self.resolve_noderef_in_obj(obj, &reference.reference, method_structure) // TODO: Could sanity the result towards the referencekind here } pub fn lookup_symbols_by_contexted_reference( &self, context_chain: &[ContextKey], - reference: &VariableReference) -> ReferenceMatch { + reference: &VariableReference, + method_structure: &HashMap) + -> ReferenceMatch { debug!("Looking up {:?} : {:?} in device tree", context_chain, reference); // NOTE: This is actually unused, but contexts_to_objs is used @@ -1092,7 +1203,7 @@ impl DeviceAnalysis { let mut syms = vec![]; let mut suggestions = vec![]; for result in objs.into_iter().map( - |o|self.lookup_ref_in_obj(&o, reference)) { + |o|self.lookup_ref_in_obj(&o, reference, method_structure)) { match result { ReferenceMatch::Found(mut found_syms) => syms.append(&mut found_syms), @@ -1116,19 +1227,51 @@ impl DeviceAnalysis { &self, context_chain: &[ContextKey], reference: &VariableReference, + method_structure: &HashMap, reference_cache: &Mutex) -> ReferenceMatch { + let mut recursive_cache = HashSet::default(); + self.find_target_for_reference_without_loop(context_chain, + reference, + method_structure, + reference_cache, + &mut recursive_cache) + } + + fn find_target_for_reference_without_loop<'t>( + &self, + context_chain: &'t [ContextKey], + reference: &'t VariableReference, + method_structure: &HashMap, + reference_cache: &Mutex, + recursive_cache: &'t mut HashSet<(&'t [ContextKey], + &'t VariableReference)>) + -> ReferenceMatch { + // Prevent us from calling into the exact same reference lookup twice + // within one lookup. + if !recursive_cache.insert((context_chain, reference)) { + internal_error!("Recursive reference lookup detected at \ + {:?} under {:?}", reference, context_chain); + return ReferenceMatch::NotFound(vec![]); + } let index_key = (context_chain.to_vec(), reference.clone()); { if let Some(cached_result) = reference_cache.lock().unwrap() .get(index_key.clone()) { - return cached_result.clone(); + // TODO: Caching currently does not work for references + // within method bodies, as the same reference in different + // locations may have different scopes + if !context_chain.last() + .and_then(|c|c.kind()) + .map_or(false, |k|k == DMLSymbolKind::Method) { + return cached_result.clone(); + } } } let result = self.find_target_for_reference_aux( - context_chain, reference, reference_cache); + context_chain, reference, method_structure, reference_cache); reference_cache.lock().unwrap() .insert(index_key, result.clone()); result @@ -1138,6 +1281,7 @@ impl DeviceAnalysis { &self, context_chain: &[ContextKey], reference: &VariableReference, + method_structure: &HashMap, reference_cache: &Mutex) -> ReferenceMatch { if context_chain.is_empty() { @@ -1147,13 +1291,14 @@ impl DeviceAnalysis { match self.lookup_symbols_by_contexted_reference( // Ignore first element of chain, it is the device context - &context_chain[1..], reference) { + &context_chain[1..], reference, method_structure) { f @ ReferenceMatch::Found(_) => f, c @ ReferenceMatch::WrongType(_) => c, ReferenceMatch::NotFound(mut suggestions) => { let (_, new_chain) = context_chain.split_last().unwrap(); match self.find_target_for_reference(new_chain, reference, + method_structure, reference_cache) { f @ ReferenceMatch::Found(_) => f, c @ ReferenceMatch::WrongType(_) => c, @@ -1168,10 +1313,12 @@ impl DeviceAnalysis { } impl DeviceAnalysis { + #[allow(clippy::ptr_arg)] fn match_references_in_scope<'c>( &'c self, scope_chain: Vec<&'c dyn Scope>, - _report: &Mutex>, + _report: &mut Vec, + method_structure: &HashMap, reference_cache: &Mutex) { let current_scope = scope_chain.last().unwrap(); let context_chain: Vec = scope_chain @@ -1182,7 +1329,10 @@ impl DeviceAnalysis { debug!("In {:?}, Matching {:?}", context_chain, reference); let symbol_lookup = match &reference { Reference::Variable(var) => self.find_target_for_reference( - context_chain.as_slice(), var, reference_cache), + context_chain.as_slice(), + var, + method_structure, + reference_cache), Reference::Global(glob) => self.lookup_global_from_ref(glob), }; @@ -1443,7 +1593,10 @@ impl IsolatedAnalysis { } } -fn objects_to_symbols(objects: &StructureContainer) -> SymbolStorage { +fn objects_to_symbols(objects: &StructureContainer, + errors: &mut Vec, + method_structure: &mut HashMap + ) -> SymbolStorage { let mut storage = SymbolStorage::default(); for obj in objects.values() { @@ -1454,7 +1607,8 @@ fn objects_to_symbols(objects: &StructureContainer) -> SymbolStorage { // Non-shallow objects will be handled by the iteration // over objects if let DMLObject::ShallowObject(shallow) = subobj { - add_new_symbol_from_shallow(shallow, &mut storage); + add_new_symbol_from_shallow(shallow, errors, + &mut storage, method_structure); } } } @@ -1475,6 +1629,8 @@ fn template_to_symbol(template: &Arc) -> Option { bases: vec![], source: SymbolSource::Template(Arc::clone(template)), default_mappings: HashMap::default(), + // TODO: Should this be the trait type? + typed: None, }))}) } @@ -1485,7 +1641,7 @@ fn extend_with_templates(storage: &mut SymbolStorage, let loc = new_templ.lock().unwrap().loc; if let Some(prev) = storage.template_symbols .insert(loc, new_templ) { - error!("Internal Error: Unexpectedly two template symbols + internal_error!("Unexpectedly two template symbols defined in the same location"); error!("Previous was {:?}", prev); error!("New is {:?}", storage.template_symbols.get(&loc)); @@ -1508,6 +1664,7 @@ fn new_symbol_from_object(object: &DMLCompositeObject) -> SymbolRef { source: SymbolSource::DMLObject( DMLObject::CompObject(object.key)), default_mappings: HashMap::default(), + typed: None, })) } @@ -1527,6 +1684,8 @@ fn new_symbol_from_arg(methref: &Arc, source: SymbolSource::MethodArg(Arc::clone(methref), arg.name().clone()), default_mappings: HashMap::default(), + // TODO: Obtain type + typed: None, })) } @@ -1546,17 +1705,21 @@ where K: std::hash::Hash + Eq + Clone, // some meta-info if !old.lock().unwrap().equivalent( &map.get(&key).unwrap().lock().unwrap()) { - error!("Unexpected Internal Error: Overwrote previous symbol \ - {:?} with non-similar symbol {:?}", - old, map.get(&key)); + internal_error!( + "Overwrote previous symbol {:?} with non-similar symbol {:?}", + old, map.get(&key)); return true; } } false } +#[allow(clippy::ptr_arg)] fn add_new_symbol_from_shallow(shallow: &DMLShallowObject, - storage: &mut SymbolStorage) { + errors: &mut Vec, + storage: &mut SymbolStorage, + method_structure: &mut HashMap + ) { let (bases, definitions, declarations) = match &shallow.variant { DMLShallowObjectVariant::Parameter(param) => (vec![*param.get_likely_declaration().loc_span()], @@ -1596,6 +1759,8 @@ fn add_new_symbol_from_shallow(shallow: &DMLShallowObject, // noting DMLObject::ShallowObject(shallow.clone())), default_mappings: HashMap::default(), + // TODO: obtain type + typed: None, })); match &shallow.variant { DMLShallowObjectVariant::Parameter(_) => { @@ -1616,6 +1781,8 @@ fn add_new_symbol_from_shallow(shallow: &DMLShallowObject, *arg.loc_span(), new_argsymbol); } + add_method_scope_symbols(method_ref, method_structure, + storage, errors); }, DMLShallowObjectVariant::Constant(_) | DMLShallowObjectVariant::Session(_) | @@ -1628,6 +1795,239 @@ fn add_new_symbol_from_shallow(shallow: &DMLShallowObject, } } +fn add_method_scope_symbols(method: &Arc, + method_structure: &mut HashMap, + storage: &mut SymbolStorage, + errors: &mut Vec) { + let mut entry = RangeEntry { + range: method.get_decl().span().range, + symbols: HashMap::default(), + sub_ranges: vec![], + }; + if !matches!(&*method.get_decl().body, StatementKind::Compound(_)) { + error!("Internal Error: Method body was not a compound statement"); + } + add_new_method_scope_symbols(method, + &method.get_decl().body, + errors, + storage, + &mut entry); + if !entry.is_empty() { + method_structure.insert( + *method.get_decl().location(), + entry); + } +} + +fn add_new_method_scope_symbol(method: &Arc, + sym: &T, + _typ: &DMLType, + storage: &mut SymbolStorage, + scope: &mut RangeEntry) +where + T : StructureSymbol + DMLNamed + LocationSpan +{ + let symbol = Arc::new(Mutex::new(Symbol { + loc: *sym.loc_span(), + kind: sym.kind(), + definitions: vec![*sym.loc_span()], + declarations: vec![*sym.loc_span()], + implementations: vec![], + references: vec![], + bases: vec![], + source: SymbolSource::MethodLocal( + Arc::clone(method), + sym.name().clone()), + default_mappings: HashMap::default(), + // TODO: resolve type + typed: None, + })); + scope.symbols.insert(sym.name().val.clone(), Arc::clone(&symbol)); + storage.variable_symbols.insert(*sym.loc_span(), symbol); +} + +fn enter_new_method_scope(method: &Arc, + outer_stmnt_desc: &'static str, + stmnt: &Statement, + scope_span: &ZeroSpan, + errors: &mut Vec, + storage: &mut SymbolStorage, + scope: &mut RangeEntry) { + // In DMLC, there is no error or warning about this (even if a declaration + // is referred to outside the scope of the containing statement), instead + // relying on the C compilation failing + // We will instead warn in general, and actually always create the scope + // so that lookups will fail + if let StatementKind::VariableDecl(content) = stmnt.as_ref() { + errors.push(DMLError { + span: *content.span(), + description: format!("Declaration will not be available \ + outside {} scope", outer_stmnt_desc), + severity: Some(DiagnosticSeverity::WARNING), + related: vec![], + }); + } + let mut entry = RangeEntry { + range: scope_span.range, + symbols: HashMap::default(), + sub_ranges: vec![], + }; + add_new_method_scope_symbols(method, stmnt, errors, storage, &mut entry); + scope.sub_ranges.push(entry); +} + +fn add_new_method_scope_symbols(method: &Arc, + stmnt: &Statement, + errors: &mut Vec, + storage: &mut SymbolStorage, + scope: &mut RangeEntry) { + match &**stmnt { + StatementKind::Compound(content) => + for sub_stmnt in &content.statements { + add_new_method_scope_symbols(method, + sub_stmnt, + errors, + storage, + scope); + }, + StatementKind::If(content) => { + enter_new_method_scope(method, + "if", + &content.ifbody, + content.ifbody.span(), + errors, + storage, + scope); + if let Some(elsebody) = &content.elsebody { + enter_new_method_scope(method, + "else", + elsebody, + elsebody.span(), + errors, + storage, + scope); + } + }, + StatementKind::HashIf(content) => { + // TODO: this should be constant-folded if possible + enter_new_method_scope(method, + "#if", + &content.ifbody, + content.ifbody.span(), + errors, + storage, + scope); + if let Some(elsebody) = &content.elsebody { + enter_new_method_scope(method, + "#else", + elsebody, + elsebody.span(), + errors, + storage, + scope); + } + }, + StatementKind::While(content) => + enter_new_method_scope(method, + "while", + &content.body, + content.body.span(), + errors, + storage, + scope), + StatementKind::DoWhile(content) => + enter_new_method_scope(method, + "do-while", + &content.body, + content.body.span(), + errors, + storage, + scope), + StatementKind::For(content) => { + let mut entry = RangeEntry { + range: content.span().range, + symbols: HashMap::default(), + sub_ranges: vec![], + }; + + if let Some(ForPre::Declaration(variable)) = &content.pre { + for decl in &variable.vars { + add_new_method_scope_symbol(method, + decl, + &decl.typed, + storage, + &mut entry); + } + } + enter_new_method_scope(method, + "for", + &content.body, + content.body.span(), + errors, + storage, + &mut entry); + scope.sub_ranges.push(entry); + }, + StatementKind::HashSelect(content) => { + let mut entry = RangeEntry { + range: ZeroRange::combine( + content.selectbranch.span().range, + content.whereexpr.span().range), + symbols: HashMap::default(), + sub_ranges: vec![], + }; + add_new_method_scope_symbol(method, + &content.ident, + // TODO: infer type + content.ident.loc_span(), + storage, + &mut entry); + enter_new_method_scope(method, + "select", + &content.selectbranch, + content.selectbranch.span(), + errors, + storage, + &mut entry); + scope.sub_ranges.push(entry); + enter_new_method_scope(method, + "select-else", + &content.elsebranch, + content.elsebranch.span(), + errors, + storage, + scope); + }, + StatementKind::TryCatch(content) => { + enter_new_method_scope(method, + "try", + &content.tryblock, + content.tryblock.span(), + errors, + storage, + scope); + enter_new_method_scope(method, + "catch", + &content.catchblock, + content.catchblock.span(), + errors, + storage, + scope); + }, + StatementKind::VariableDecl(content) => + for decl in &content.vars { + add_new_method_scope_symbol( + method, + decl, + &decl.typed, + storage, + scope); + }, + _ => (), + } +} + impl DeviceAnalysis { pub fn new(root: IsolatedAnalysis, timed_bases: Vec>, @@ -1746,23 +2146,25 @@ impl DeviceAnalysis { } } - let mut mapped_errors = HashMap::default(); - for error in errors { - let entr: &mut Vec<_> = mapped_errors.entry(error.span.path()) - .or_default(); - entr.push(error); - } + info!("Generate symbols"); + // Used to handle scoping in methods when looking up local symbols, + // NOTE: if this meta-information becomes relevant later, move this + // structure to symbol storage + // The zerospan here is the span corresponding to the span of the + // NAME of the methoddecl + let mut method_structure: HashMap + = HashMap::default(); - trace!("Errors are {:?}", mapped_errors); + let mut symbol_info = objects_to_symbols(&container, + &mut errors, + &mut method_structure); - let mut symbol_info = objects_to_symbols(&container); - info!("Generate symbols"); // TODO: how do we store type info? extend_with_templates(&mut symbol_info, &tt_info); //extend_with_types(&mut symbols, ??) let mut device = DeviceAnalysis { name: root.toplevel.device.unwrap().name.val, - errors: mapped_errors, + errors: HashMap::default(), objects: container, device_obj: DMLObject::CompObject(device_key), templates: tt_info, @@ -1776,17 +2178,12 @@ impl DeviceAnalysis { info!("Match references"); let reference_cache: Mutex = Mutex::default(); - let new_errors: Mutex> = Mutex::default(); for scope_chain in all_scopes(&bases) { device.match_references_in_scope(scope_chain, - &new_errors, + &mut errors, + &method_structure, &reference_cache); } - for err in new_errors.into_inner().unwrap() { - device.errors.entry(err.span.path()) - .or_default() - .push(err); - } info!("Inverse map"); // Set up the inverse map of references->symbols @@ -1816,6 +2213,13 @@ impl DeviceAnalysis { } } + for error in errors { + device.errors.entry(error.span.path()) + .or_default() + .push(error); + } + trace!("Errors are {:?}", device.errors); + info!("Done with device"); Ok(device) } diff --git a/src/analysis/parsing/statement.rs b/src/analysis/parsing/statement.rs index fed3b27..a02401e 100644 --- a/src/analysis/parsing/statement.rs +++ b/src/analysis/parsing/statement.rs @@ -18,7 +18,7 @@ use crate::analysis::parsing::expression::{Expression, MemberLiteralContent, ExpressionContent, ParenExpressionContent}; -use crate::analysis::parsing::misc::{Initializer, InitializerContent, CDecl, +use crate::analysis::parsing::misc::{Initializer, InitializerContent, SingleInitializerContent, ident_filter, objident_filter}; use crate::analysis::parsing::structure::{parse_vardecl, VarDecl}; @@ -661,28 +661,28 @@ impl TreeElement for ForPostElement { #[derive(Debug, Clone, PartialEq)] pub enum ForPre { - Local(LeafToken, CDecl, Option<(LeafToken, Initializer)>), + Declaration(LeafToken, VarDecl, Option<(LeafToken, Initializer)>), Post(ForPost), } impl TreeElement for ForPre { fn range(&self) -> ZeroRange { match self { - Self::Local(loc, cdecl, init) => - Range::combine(Range::combine(loc.range(), cdecl.range()), + Self::Declaration(loc, vardecls, init) => + Range::combine(Range::combine(loc.range(), vardecls.range()), init.range()), Self::Post(forpost) => forpost.range(), } } fn subs(&self) -> TreeElements<'_> { match self { - Self::Local(loc, cdecl, init) => - create_subs!(loc, cdecl, init), + Self::Declaration(loc, vardecls, init) => + create_subs!(loc, vardecls, init), Self::Post(forpost) => create_subs!(forpost), } } fn post_parse_sanity(&self, _file: &TextFile) -> Vec { - if let ForPre::Local(_, cdecl, _) = self { + if let ForPre::Declaration(_, cdecl, _) = self { cdecl.ensure_named() } else { vec![] @@ -814,18 +814,19 @@ fn parse_forpost(context: &ParseContext, stream: &mut FileParser<'_>, file_info: fn parse_forpre(context: &ParseContext, stream: &mut FileParser<'_>, file_info: &FileInfo) -> ForPre { let mut new_context = context.enter_context(doesnt_understand_tokens); match new_context.peek_kind(stream) { - Some(TokenKind::Local) => { - let local = new_context.expect_next_kind(stream, TokenKind::Local); - let decl = CDecl::parse(&new_context, stream, file_info); + Some(TokenKind::Local | TokenKind::Saved | TokenKind::Session) => { + let decl_kind = new_context.next_leaf(stream); + let decls = parse_vardecl(&new_context, stream, file_info); let initializer = match new_context.peek_kind(stream) { Some(TokenKind::Assign) => { - let assign = new_context.next_leaf(stream); - let init = Initializer::parse(&new_context, stream, file_info); - Some((assign, init)) + let equals = new_context.next_leaf(stream); + let init = Initializer::parse( + &new_context, stream, file_info); + Some((equals, init)) }, - _ => None + _ => None, }; - ForPre::Local(local, decl, initializer) + ForPre::Declaration(decl_kind, decls, initializer) }, _ => ForPre::Post(parse_forpost(&new_context, stream, file_info)), } diff --git a/src/analysis/structure/objects.rs b/src/analysis/structure/objects.rs index 81f2f8d..a4ab302 100644 --- a/src/analysis/structure/objects.rs +++ b/src/analysis/structure/objects.rs @@ -996,10 +996,6 @@ pub struct Method { pub references: Vec, } -impl SymbolContainer for Method { - fn symbols(&self) -> Vec<&dyn StructureSymbol> { vec![] } -} - impl DeclarationSpan for Method { fn span(&self) -> &ZeroSpan { self.object.span() @@ -1023,7 +1019,9 @@ impl Scope for Method { ContextKey::Structure(SimpleSymbol::make(self, self.kind())) } fn defined_symbols(&self) -> Vec<&dyn StructureSymbol> { - self.arguments.to_symbols() + let mut symbols = self.arguments.to_symbols(); + symbols.append(&mut self.body.symbols()); + symbols } fn defined_scopes(&self) -> Vec<&dyn Scope> { vec![] @@ -1345,11 +1343,12 @@ impl DeclarationSpan for Variable { } } -fn to_variable_structure<'a>(content: &structure::VariableContent, - kind: VariableDeclKind, - report: &mut Vec, - file: FileSpec<'a>) -> Option { - let values = if let Some((_, init_ast)) = &content.initializer { +pub fn to_variable_structure<'a>(decls: &structure::VarDecl, + initializer: Option<&misc::Initializer>, + kind: VariableDeclKind, + report: &mut Vec, + file: FileSpec<'a>) -> Option { + let values = if let Some(init_ast) = initializer { init_ast.with_content( |con|to_initializer(con, report, file), vec![]) @@ -1357,7 +1356,7 @@ fn to_variable_structure<'a>(content: &structure::VariableContent, vec![] }; // I _think_ we will always have a real content here - let vars = match &content.cdecl { + let vars = match decls { structure::VarDecl::One(decl) => { vec![to_variable_decl_structure(decl, kind, report, file)?] }, @@ -1369,11 +1368,24 @@ fn to_variable_structure<'a>(content: &structure::VariableContent, } }; - if let Some((assign, init_ast)) = &content.initializer { - if vars.len() != values.len() { + if let Some(init) = initializer { + // NOTE: this is the half of the initializer arity that + // can be performed in an isolated context. Checking that + // the method would return the right number of values + // is performed in device analysis + // TODO: if we ever update to latest rust, we can merge this if-case + // with the next + let mut is_single_fun = false; + + if let [val] = values.as_slice() { + if let InitializerKind::One(expr) = &val.kind { + is_single_fun = matches!( + expr.as_ref(), ExpressionKind::FunctionCall(_)); + } + } + if !is_single_fun && vars.len() != values.len(){ report.push(LocalDMLError { - range: init_ast.with_content( - |i|i.range(), assign.range()), + range: init.range(), description: "Wrong number of \ initializers in declaration".to_string(), }); @@ -1383,7 +1395,11 @@ fn to_variable_structure<'a>(content: &structure::VariableContent, Some(Variable { vars, values, - span: ZeroSpan::from_range(content.range(), file.path), + span: ZeroSpan::from_range( + initializer.map_or_else( + ||decls.range(), + |i|ZeroRange::combine(decls.range(), i.range())), + file.path), }) } @@ -1578,7 +1594,9 @@ fn to_objectstatement<'a>(content: &structure::DMLObjectContent, Export::to_structure(con, report, file)?))), structure::DMLObjectContent::Extern(con) => DMLStatementKind::Statement(DMLStatement::Object(DMLObject::Extern( - to_variable_structure(con, VariableDeclKind::Extern, + to_variable_structure(&con.cdecl, + con.initializer.as_ref().map(|(_,i)|i), + VariableDeclKind::Extern, report, file)?))), structure::DMLObjectContent::Event(con) => DMLStatementKind::Statement(DMLStatement::Object( @@ -1650,11 +1668,15 @@ fn to_objectstatement<'a>(content: &structure::DMLObjectContent, to_register(con, report, file)?))), structure::DMLObjectContent::Saved(con) => DMLStatementKind::Statement(DMLStatement::Object(DMLObject::Saved( - to_variable_structure(con, VariableDeclKind::Saved, + to_variable_structure(&con.cdecl, + con.initializer.as_ref().map(|(_,i)|i), + VariableDeclKind::Saved, report, file)?))), structure::DMLObjectContent::Session(con) => DMLStatementKind::Statement(DMLStatement::Object(DMLObject::Session( - to_variable_structure(con, VariableDeclKind::Session, + to_variable_structure(&con.cdecl, + con.initializer.as_ref().map(|(_,i)|i), + VariableDeclKind::Session, report, file)?))), structure::DMLObjectContent::Subdevice(con) => DMLStatementKind::Statement(DMLStatement::Object( diff --git a/src/analysis/structure/statements.rs b/src/analysis/structure/statements.rs index 2ee896f..4d99edc 100644 --- a/src/analysis/structure/statements.rs +++ b/src/analysis/structure/statements.rs @@ -8,27 +8,59 @@ use crate::analysis::{DeclarationSpan, use crate::analysis::symbols::{StructureSymbol, SymbolContainer}; -use crate::analysis::structure::types::{DMLType, deconstruct_cdecl}; +use crate::analysis::structure::objects::{Variable, + VariableDeclKind, + to_variable_structure}; use crate::analysis::structure::expressions::{DMLString, ExpressionKind, Expression, Initializer}; -use crate::analysis::parsing::{structure, statement}; +use crate::analysis::parsing::{structure, statement, misc}; use crate::analysis::parsing::lexer::TokenKind; -use crate::analysis::parsing::tree::{ZeroSpan, TreeElement}; -use crate::analysis::FileSpec; +use crate::analysis::parsing::tree::{LeafToken, ZeroSpan, TreeElement}; +use crate::analysis::{DMLNamed, DMLSymbolKind, FileSpec}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ForEachIdentifier(DMLString); + +impl DeclarationSpan for ForEachIdentifier { + fn span(&self) -> &ZeroSpan { + self.0.span() + } +} + +impl DMLNamed for ForEachIdentifier { + fn name(&self) -> &DMLString { + &self.0 + } +} + +impl StructureSymbol for ForEachIdentifier { + fn kind(&self) -> DMLSymbolKind { + DMLSymbolKind::Local + } +} #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ForEach { - pub identifier: DMLString, + pub identifier: ForEachIdentifier, pub inexpr: Expression, pub body: Statement, pub span: ZeroSpan, } +impl SymbolContainer for ForEach { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + let mut subs = self.body.symbols(); + subs.push(&self.identifier as & dyn StructureSymbol); + subs + } +} + impl ForEach { fn to_statement<'a>(content: &statement::ForeachContent, report: &mut Vec, file: FileSpec<'a>) -> Option { - let identifier = DMLString::from_token(&content.ident, file)?; + let identifier = ForEachIdentifier( + DMLString::from_token(&content.ident, file)?); let inexpr = ExpressionKind::to_expression( &content.expression, report, file)?; let body = StatementKind::to_statement( @@ -50,10 +82,18 @@ impl DeclarationSpan for ForEach { pub struct If { pub condition: Expression, pub ifbody: Statement, - pub elsebody: Statement, + pub elsebody: Option, pub span: ZeroSpan, } +impl SymbolContainer for If { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + let mut subs = self.ifbody.symbols(); + subs.append(&mut self.elsebody.symbols()); + subs + } +} + impl If { fn to_statement<'a>(content: &statement::IfContent, report: &mut Vec, @@ -65,7 +105,7 @@ impl If { report, file)?; let elsebody = content.elsebranch.as_ref().and_then( - |(_, elbody)|StatementKind::to_statement(elbody, report, file))?; + |(_, elbody)|StatementKind::to_statement(elbody, report, file)); let span = ZeroSpan::from_range(content.range(), file.path); StatementKind::If(If { condition, ifbody, elsebody, span @@ -83,10 +123,18 @@ impl DeclarationSpan for If { pub struct HashIf { pub condition: Expression, pub ifbody: Statement, - pub elsebody: Statement, + pub elsebody: Option, pub span: ZeroSpan, } +impl SymbolContainer for HashIf { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + let mut subs = self.ifbody.symbols(); + subs.append(&mut self.elsebody.symbols()); + subs + } +} + impl HashIf { fn to_statement<'a>(content: &statement::HashIfContent, report: &mut Vec, @@ -97,7 +145,7 @@ impl HashIf { report, file)?; let elsebody = content.elsebranch.as_ref().and_then( - |(_, content)|StatementKind::to_statement(content, report, file))?; + |(_, content)|StatementKind::to_statement(content, report, file)); let span = ZeroSpan::from_range(content.range(), file.path); StatementKind::HashIf(HashIf { condition, ifbody, elsebody, span, @@ -119,6 +167,14 @@ pub struct HashIfCase { pub span: ZeroSpan, } +impl SymbolContainer for HashIfCase { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + let mut subs = self.truecases.symbols(); + subs.append(&mut self.falsecases.symbols()); + subs + } +} + impl HashIfCase { fn to_hif_case<'a>(content: &statement::SwitchHashIf, report: &mut Vec, @@ -152,6 +208,16 @@ pub enum SwitchCase { Default(ZeroSpan), } +impl SymbolContainer for SwitchCase { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + match self { + SwitchCase::Statement(stmnt) => stmnt.symbols(), + SwitchCase::HashIf(ifc) => ifc.symbols(), + _ => vec![], + } + } +} + impl SwitchCase { fn to_case<'a>(content: &statement::SwitchCase, report: &mut Vec, @@ -173,6 +239,8 @@ impl SwitchCase { } } + + impl DeclarationSpan for SwitchCase { fn span(&self) -> &ZeroSpan { match self { @@ -191,6 +259,12 @@ pub struct Switch { pub span: ZeroSpan, } +impl SymbolContainer for Switch { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + self.cases.symbols() + } +} + impl Switch { fn to_statement<'a>(content: &statement::SwitchContent, report: &mut Vec, @@ -213,9 +287,15 @@ impl DeclarationSpan for Switch { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct While { - cond: Expression, - body: Statement, - span: ZeroSpan, + pub cond: Expression, + pub body: Statement, + pub span: ZeroSpan, +} + +impl SymbolContainer for While { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + self.body.symbols() + } } impl While { @@ -240,9 +320,15 @@ impl DeclarationSpan for While { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DoWhile { - cond: Expression, - body: Statement, - span: ZeroSpan, + pub cond: Expression, + pub body: Statement, + pub span: ZeroSpan, +} + +impl SymbolContainer for DoWhile { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + self.body.symbols() + } } impl DoWhile { @@ -274,17 +360,34 @@ pub enum ForPostElement { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ForPre { - Local(DMLString, DMLType, Option), + Declaration(Variable), Post(Vec), } +impl SymbolContainer for ForPre { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + match self { + ForPre::Declaration(var) => var.symbols(), + _ => vec![], + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct For { - pre: Option, - cond: Option, - post: Vec, - body: Statement, - span: ZeroSpan, + pub pre: Option, + pub cond: Option, + pub post: Vec, + pub body: Statement, + pub span: ZeroSpan, +} + +impl SymbolContainer for For { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + let mut symbols = self.pre.symbols(); + symbols.append(&mut self.body.symbols()); + symbols + } } fn to_forpost<'a>(content: &statement::ForPost, @@ -330,17 +433,11 @@ impl For { ||vec![], |post|to_forpost(post, report, file)); let pre = content.pre.as_ref().and_then(|pre|match pre { - statement::ForPre::Local(_, cdecl, maybe_init) => { - let (name, typed) = cdecl.with_content( - |con|deconstruct_cdecl(con, report, file), - (None, None)); - let init = maybe_init.as_ref().and_then( - |(_,i)|Initializer::to_initializer(i, report, file)); - match (name, typed) { - (Some(n), Some(t)) => Some(ForPre::Local(n, t, init)), - _ => None, - } - }, + statement::ForPre::Declaration(kind, vardecls, maybe_init) => + to_variable(kind, vardecls, + maybe_init.as_ref().map(|(_, init)|init), + report, file) + .map(ForPre::Declaration), statement::ForPre::Post(p) => Some(ForPre::Post( to_forpost(p, report, file))), }); @@ -370,9 +467,9 @@ pub enum AfterExpression { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct After { - after: Option, - call: Expression, - span: ZeroSpan, + pub after: Option, + pub call: Expression, + pub span: ZeroSpan, } impl After { @@ -420,8 +517,8 @@ impl DeclarationSpan for After { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Return { - ret: Option, - span: ZeroSpan, + pub ret: Option, + pub span: ZeroSpan, } impl Return { @@ -445,7 +542,7 @@ impl DeclarationSpan for Return { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Continue { - span: ZeroSpan, + pub span: ZeroSpan, } impl Continue { @@ -468,7 +565,7 @@ impl DeclarationSpan for Continue { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Break { - span: ZeroSpan, + pub span: ZeroSpan, } impl DeclarationSpan for Break { @@ -491,9 +588,17 @@ impl Break { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TryCatch { - tryblock: Statement, - catchblock: Statement, - span: ZeroSpan, + pub tryblock: Statement, + pub catchblock: Statement, + pub span: ZeroSpan, +} + +impl SymbolContainer for TryCatch { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + let mut symbols = self.tryblock.symbols(); + symbols.append(&mut self.catchblock.symbols()); + symbols + } } impl TryCatch { @@ -519,7 +624,7 @@ impl DeclarationSpan for TryCatch { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Throw { - span: ZeroSpan, + pub span: ZeroSpan, } impl Throw { @@ -558,12 +663,12 @@ pub enum LogKind { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Log { - kind: LogKind, - level: Option, - flags: Option, - message: Expression, - args: Vec, - span: ZeroSpan, + pub kind: LogKind, + pub level: Option, + pub flags: Option, + pub message: Expression, + pub args: Vec, + pub span: ZeroSpan, } impl Log { @@ -630,8 +735,8 @@ impl DeclarationSpan for Log { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Assert { - expression: Expression, - span: ZeroSpan, + pub expression: Expression, + pub span: ZeroSpan, } impl Assert { @@ -656,8 +761,8 @@ impl DeclarationSpan for Assert { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Delete { - expression: Expression, - span: ZeroSpan, + pub expression: Expression, + pub span: ZeroSpan, } impl Delete { @@ -682,8 +787,8 @@ impl DeclarationSpan for Delete { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Error { - message: Option, - span: ZeroSpan, + pub message: Option, + pub span: ZeroSpan, } impl Error { @@ -707,83 +812,38 @@ impl DeclarationSpan for Error { } } -fn to_statement_variable_decl<'a>(content: &statement::VariableDeclContent, - report: &mut Vec, - file: FileSpec<'a>) -> Option { - let decls = match &content.decls { - structure::VarDecl::One(decl) => vec![decl], - structure::VarDecl::Many(_, decllist, _) => - decllist.iter().map(|(decl, _)|decl).collect() - }; - let declarations = decls.into_iter().filter_map( - |decl|decl.with_content( - |cdecl|match deconstruct_cdecl(cdecl, report, file) { - (Some(t),Some(d)) => Some((t, d)), - _ => None, - }, None)).map(|(a, b)|(b, a)).collect(); - let initializer = content.initializer.as_ref().and_then( - |(_,init)|Initializer::to_initializer(init, report, file)); - // TODO: Is this a reasonable spot to show errors about initializer length - // mismatch? - let span = ZeroSpan::from_range(content.range(), file.path); - // Guaranteed by parser - match content.kind.get_token().unwrap().kind { - TokenKind::Local => StatementKind::Local( - Local { - declarations, initializer, span, - }), - TokenKind::Session => StatementKind::Session( - Session { - declarations, initializer, span, - }), - TokenKind::Saved => StatementKind::Saved( - Saved { - declarations, initializer, span, - }), - e => { - error!("Unexpected variable statement token kind {:?}", e); - return None; +fn to_variable<'a>(leaf_for_kind: &LeafToken, + decls: &structure::VarDecl, + inits: Option<&misc::Initializer>, + report: &mut Vec, + file: FileSpec<'a>) -> Option { + let kind = if let Some(kind_str) = leaf_for_kind.read_leaf(file.file) { + match kind_str.as_str() { + "session" => VariableDeclKind::Session, + "saved" => VariableDeclKind::Saved, + "local" => VariableDeclKind::Local, + e => { + internal_error!("Unexpected declaration kind {}", e); + return None; + }, } - }.into() -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Local { - declarations: Vec<(DMLType, DMLString)>, - initializer: Option, - span: ZeroSpan, -} - -impl DeclarationSpan for Local { - fn span(&self) -> &ZeroSpan { - &self.span - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Saved { - declarations: Vec<(DMLType, DMLString)>, - initializer: Option, - span: ZeroSpan, -} - -impl DeclarationSpan for Saved { - fn span(&self) -> &ZeroSpan { - &self.span - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Session { - declarations: Vec<(DMLType, DMLString)>, - initializer: Option, - span: ZeroSpan, + } else { + // Normally this is cleanly unexpected + internal_error!("Could not read declaration kind"); + return None; + }; + to_variable_structure(decls, inits, kind, report, file) } -impl DeclarationSpan for Session { - fn span(&self) -> &ZeroSpan { - &self.span - } +fn to_statement_variable_decl<'a>(content: &statement::VariableDeclContent, + report: &mut Vec, + file: FileSpec<'a>) -> Option { + StatementKind::VariableDecl(to_variable( + &content.kind, + &content.decls, + content.initializer.as_ref().map(|(_,i)|i), + report, + file)?).into() } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -795,10 +855,10 @@ pub enum AssignOp { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct AssignOpStatement { - assignee: Expression, - operation: AssignOp, - assigner: Expression, - span: ZeroSpan, + pub assignee: Expression, + pub operation: AssignOp, + pub assigner: Expression, + pub span: ZeroSpan, } impl DeclarationSpan for AssignOpStatement { @@ -854,9 +914,9 @@ pub enum Assigner { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct AssignStatement { - assignees: Vec, - assigner: Assigner, - span: ZeroSpan, + pub assignees: Vec, + pub assigner: Assigner, + pub span: ZeroSpan, } fn target_to_vec<'a>(content: &statement::AssignTarget, @@ -920,21 +980,46 @@ impl DeclarationSpan for AssignStatement { } } +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct HashSelectIdent(DMLString); + +impl DMLNamed for HashSelectIdent { + fn name(&self) -> &DMLString { + &self.0 + } +} + +impl StructureSymbol for HashSelectIdent { + fn kind(&self) -> DMLSymbolKind { + DMLSymbolKind::Local + } +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct HashSelect { - ident: DMLString, - inexpr: Expression, - whereexpr: Expression, - selectbranch: Statement, - elsebranch: Statement, - span: ZeroSpan, + pub ident: HashSelectIdent, + pub inexpr: Expression, + pub whereexpr: Expression, + pub selectbranch: Statement, + pub elsebranch: Statement, + pub span: ZeroSpan, +} + +impl SymbolContainer for HashSelect { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + let mut symbols = vec![&self.ident as &dyn StructureSymbol]; + symbols.append(&mut self.selectbranch.symbols()); + symbols.append(&mut self.elsebranch.symbols()); + symbols + } } impl HashSelect { fn to_statement<'a>(content: &statement::HashSelectContent, report: &mut Vec, file: FileSpec<'a>) -> Option { - let ident = DMLString::from_token(&content.ident, file)?; + let ident = HashSelectIdent( + DMLString::from_token(&content.ident, file)?); let inexpr = ExpressionKind::to_expression( &content.inexpression, report, file)?; let whereexpr = ExpressionKind::to_expression( @@ -963,8 +1048,14 @@ impl DeclarationSpan for HashSelect { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct CompoundStatement { - statements: Vec, - span: ZeroSpan, + pub statements: Vec, + pub span: ZeroSpan, +} + +impl SymbolContainer for CompoundStatement { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + self.statements.symbols() + } } impl CompoundStatement { @@ -1036,9 +1127,7 @@ pub enum StatementKind { Assert(Assert), Delete(Delete), Error(Error), - Session(Session), - Saved(Saved), - Local(Local), + VariableDecl(Variable), Assign(AssignStatement), AssignOp(AssignOpStatement), Expression(ExpressionStatement), @@ -1067,9 +1156,7 @@ impl DeclarationSpan for StatementKind { StatementKind::Assert(stmnt) => stmnt.span(), StatementKind::Error(stmnt) => stmnt.span(), StatementKind::Delete(stmnt) => stmnt.span(), - StatementKind::Session(stmnt) => stmnt.span(), - StatementKind::Saved(stmnt) => stmnt.span(), - StatementKind::Local(stmnt) => stmnt.span(), + StatementKind::VariableDecl(stmnt) => stmnt.span(), StatementKind::Assign(stmnt) => stmnt.span(), StatementKind::AssignOp(stmnt) => stmnt.span(), StatementKind::Expression(stmnt) => stmnt.span(), @@ -1081,7 +1168,20 @@ impl DeclarationSpan for StatementKind { impl SymbolContainer for StatementKind { fn symbols(&self) -> Vec<&dyn StructureSymbol> { - vec![] + match self { + StatementKind::ForEach(stmnt) => stmnt.symbols(), + StatementKind::HashIf(stmnt) => stmnt.symbols(), + StatementKind::If(stmnt) => stmnt.symbols(), + StatementKind::Switch(stmnt) => stmnt.symbols(), + StatementKind::While(stmnt) => stmnt.symbols(), + StatementKind::For(stmnt) => stmnt.symbols(), + StatementKind::DoWhile(stmnt) => stmnt.symbols(), + StatementKind::HashSelect(stmnt) => stmnt.symbols(), + StatementKind::TryCatch(stmnt) => stmnt.symbols(), + StatementKind::VariableDecl(stmnt) => stmnt.symbols(), + StatementKind::Compound(stmnt) => stmnt.symbols(), + _ => vec![], + } } } diff --git a/src/analysis/symbols.rs b/src/analysis/symbols.rs index bd3e7f5..d23b41e 100644 --- a/src/analysis/symbols.rs +++ b/src/analysis/symbols.rs @@ -15,7 +15,7 @@ use crate::analysis::templating::methods::{DMLMethodRef}; use crate::analysis::templating::types::DMLResolvedType; use crate::analysis::templating::traits::DMLTemplate; -#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)] +#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash, PartialOrd, Ord)] pub enum DMLSymbolKind { CompObject(CompObjectKind), Parameter, @@ -50,13 +50,6 @@ impl MakeSymbolContainer for Vec { } } -impl MakeSymbolContainer for Option { - fn to_symbols(&self) -> Vec<&dyn StructureSymbol> { - self.iter().map(|s|s as &dyn StructureSymbol).collect() - } -} - - impl SymbolContainer for Vec { fn symbols(&self) -> Vec<&dyn StructureSymbol> { self.iter().flat_map(|s|s.symbols().into_iter()).collect() @@ -69,6 +62,12 @@ impl SymbolContainer for Option { } } +impl SymbolContainer for Box { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + self.as_ref().symbols() + } +} + #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct SimpleSymbol { pub name: String, @@ -113,6 +112,7 @@ impl SimpleSymbol { pub enum SymbolSource { DMLObject(DMLObject), MethodArg(Arc, DMLString), + MethodLocal(Arc, DMLString), // TODO: RC this if it's expensive Type(DMLResolvedType), Template(Arc), @@ -174,6 +174,8 @@ pub struct Symbol { // Used for method symbols only, maps default references // to the method_decl they should resolve to pub default_mappings: HashMap, + // TODO: RC or box this if it's expensive + pub typed: Option, } impl Symbol { diff --git a/src/analysis/templating/methods.rs b/src/analysis/templating/methods.rs index 00c5cb4..b22b69e 100644 --- a/src/analysis/templating/methods.rs +++ b/src/analysis/templating/methods.rs @@ -4,12 +4,14 @@ use std::sync::Arc; use lsp_types::DiagnosticSeverity; use crate::analysis::parsing::tree::ZeroSpan; +use crate::analysis::symbols::{DMLSymbolKind, MakeSymbolContainer, + StructureSymbol, SymbolContainer}; use crate::analysis::structure::expressions::DMLString; use crate::analysis::structure::types::DMLType; use crate::analysis::structure::objects::{MaybeAbstract, MethodArgument, MethodModifier, Method}; use crate::analysis::structure::statements::{Statement, StatementKind}; -use crate::analysis::{DMLNamed, DMLError}; +use crate::analysis::{DeclarationSpan, DMLNamed, DMLError}; use crate::analysis::templating::Declaration; use crate::analysis::templating::objects::DMLNamedMember; use crate::analysis::templating::types::{eval_type_simple, DMLResolvedType}; @@ -21,6 +23,12 @@ pub enum DMLMethodArg { Inline(DMLString), } +impl StructureSymbol for DMLMethodArg { + fn kind(&self) -> DMLSymbolKind { + DMLSymbolKind::MethodArg + } +} + impl DMLNamed for DMLMethodArg { fn name(&self) -> &DMLString { match self { @@ -116,6 +124,15 @@ pub struct MethodDecl { pub method_args: Vec, pub return_types: Vec, pub body: Statement, + pub span: ZeroSpan, +} + +impl SymbolContainer for MethodDecl { + fn symbols(&self) -> Vec<&dyn StructureSymbol> { + let mut symbols = self.method_args.to_symbols(); + symbols.append(&mut self.body.symbols()); + symbols + } } impl MaybeAbstract for MethodDecl { @@ -138,6 +155,12 @@ impl DMLNamedMember for MethodDecl { } } +impl DeclarationSpan for MethodDecl { + fn span(&self) -> &ZeroSpan { + &self.span + } +} + pub trait MethodDeclaration : DMLNamedMember + MaybeAbstract { fn is_shared(&self) -> bool; fn throws(&self) -> bool; @@ -259,6 +282,7 @@ impl MethodDecl { throws: content.throws, method_args: eval_method_args(&content.arguments, report), return_types: eval_method_returns(&content.returns, report), + span: *content.span(), } } } @@ -383,6 +407,15 @@ impl DMLNamedMember for DMLMethodRef { } } +impl DeclarationSpan for DMLMethodRef { + fn span(&self) -> &ZeroSpan { + match self { + DMLMethodRef::TraitMethod(_, decl) => decl.span(), + DMLMethodRef::ConcreteMethod(decl) => decl.span(), + } + } + } + // This is roughly equivalent with a non-codegenned method in DMLC #[derive(Debug, Clone, PartialEq, Eq)] pub struct DMLConcreteMethod { @@ -449,3 +482,9 @@ impl MaybeAbstract for DMLConcreteMethod { self.decl.is_abstract() } } + +impl DeclarationSpan for DMLConcreteMethod { + fn span(&self) -> &ZeroSpan { + self.decl.span() + } +} diff --git a/src/lib.rs b/src/lib.rs index 907e888..23172ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,17 @@ clippy::needless_lifetimes, clippy::only_used_in_recursion)] +// TODO: replace internal error panics with internal log errors where +// possible +macro_rules! internal_error { + ( $mess:expr, $( $arg:expr ),* ) => { + error!("Internal Error: {}", format!($mess, $( $arg ),*)); + }; + ( $mess:expr ) => { + error!("Internal Error: {}", $mess); + } +} + #[macro_use] pub mod actions; pub mod analysis;