From bf379456fed6580d7a834aa0950234a022e753d2 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sat, 27 May 2017 23:10:08 +0200 Subject: [PATCH 1/4] feat(check): Allow retrieval of documentation through the completion interface Currently only works when completing on identifiers though in the future it should be possible to annotate types as well which would let ``` (f x).field // ^ Should return documentation if `f x` returns a type that has documented `field` ``` --- check/src/completion.rs | 52 ++++++++++++++++++++++++ check/src/metadata.rs | 23 ++++++----- check/tests/completion.rs | 82 +++++++++++++++++++++++++++++++++++++- check/tests/metadata.rs | 24 ++++++----- check/tests/support/mod.rs | 8 ++++ src/compiler_pipeline.rs | 2 +- src/lib.rs | 2 +- 7 files changed, 169 insertions(+), 24 deletions(-) diff --git a/check/src/completion.rs b/check/src/completion.rs index d4718cac60..40e94beae2 100644 --- a/check/src/completion.rs +++ b/check/src/completion.rs @@ -5,6 +5,8 @@ use std::cmp::Ordering; use base::ast::{Expr, SpannedExpr, SpannedPattern, Pattern, TypedIdent, Typed, Visitor, walk_expr, walk_pattern}; +use base::fnv::FnvMap; +use base::metadata::Metadata; use base::resolve; use base::pos::{BytePos, Span, NO_EXPANSION}; use base::scoped_map::ScopedMap; @@ -152,6 +154,41 @@ impl OnFound for Suggest { } } +struct GetMetadata<'a> { + env: &'a FnvMap, + result: Option<&'a Metadata>, +} + +impl<'a> OnFound for GetMetadata<'a> { + fn expr(&mut self, expr: &SpannedExpr) { + if let Expr::Ident(ref id) = expr.value { + self.result = self.env.get(&id.name); + } + } + + fn ident(&mut self, context: &SpannedExpr, id: &Symbol, _typ: &ArcType) { + match context.value { + Expr::Projection(ref expr, _, _) => { + if let Expr::Ident(ref expr_id) = expr.value { + self.result = self.env + .get(&expr_id.name) + .and_then(|metadata| metadata.module.get(id.as_ref())); + } + } + Expr::Infix(..) => { + self.result = self.env.get(id); + } + _ => (), + } + } + + fn nothing(&mut self) {} + + fn found(&self) -> bool { + self.result.is_some() + } +} + struct FindVisitor { pos: BytePos, on_found: F, @@ -332,3 +369,18 @@ pub fn suggest(env: &T, expr: &SpannedExpr, pos: BytePos) -> Vec(env: &'a FnvMap, + expr: &SpannedExpr, + pos: BytePos) + -> Option<&'a Metadata> { + let mut visitor = FindVisitor { + pos: pos, + on_found: GetMetadata { + env: env, + result: None, + }, + }; + visitor.visit_expr(expr); + visitor.on_found.result +} diff --git a/check/src/metadata.rs b/check/src/metadata.rs index 6a78b9c352..9067154b0b 100644 --- a/check/src/metadata.rs +++ b/check/src/metadata.rs @@ -2,17 +2,19 @@ use std::collections::BTreeMap; use base::ast::{self, Expr, Pattern, SpannedExpr, SpannedPattern, ValueBinding}; use base::ast::MutVisitor; +use base::fnv::FnvMap; use base::metadata::{Metadata, MetadataEnv}; -use base::scoped_map::ScopedMap; use base::symbol::{Name, Symbol}; struct Environment<'b> { env: &'b MetadataEnv, - stack: ScopedMap, + stack: FnvMap, } /// Queries `expr` for the metadata which it contains. -pub fn metadata(env: &MetadataEnv, expr: &mut SpannedExpr) -> Metadata { +pub fn metadata(env: &MetadataEnv, + expr: &mut SpannedExpr) + -> (Metadata, FnvMap) { struct MetadataVisitor<'b> { env: Environment<'b>, } @@ -74,6 +76,10 @@ pub fn metadata(env: &MetadataEnv, expr: &mut SpannedExpr) -> Metadata { fn stack_var(&mut self, id: Symbol, metadata: Metadata) { if metadata.has_data() { debug!("Insert {}", id); + // All symbols should have been renamed to unique ones + debug_assert!(!self.env.stack.contains_key(&id), + "Symbol {:?}, appears twice in the source", + id); self.env.stack.insert(id, metadata); } } @@ -138,16 +144,13 @@ pub fn metadata(env: &MetadataEnv, expr: &mut SpannedExpr) -> Metadata { } } Expr::LetBindings(ref mut bindings, ref mut expr) => { - self.env.stack.enter_scope(); let is_recursive = bindings.iter().all(|bind| !bind.args.is_empty()); if is_recursive { for bind in bindings.iter_mut() { self.new_binding(Metadata::default(), bind); } for bind in bindings { - self.env.stack.enter_scope(); self.metadata_expr(&mut bind.expr); - self.env.stack.exit_scope(); } } else { for bind in bindings { @@ -156,11 +159,9 @@ pub fn metadata(env: &MetadataEnv, expr: &mut SpannedExpr) -> Metadata { } } let result = self.metadata_expr(expr); - self.env.stack.exit_scope(); result } Expr::TypeBindings(ref mut bindings, ref mut expr) => { - self.env.stack.enter_scope(); for bind in bindings.iter_mut() { let maybe_metadata = bind.comment .as_ref() @@ -175,7 +176,6 @@ pub fn metadata(env: &MetadataEnv, expr: &mut SpannedExpr) -> Metadata { } } let result = self.metadata_expr(expr); - self.env.stack.exit_scope(); result } _ => { @@ -197,8 +197,9 @@ pub fn metadata(env: &MetadataEnv, expr: &mut SpannedExpr) -> Metadata { let mut visitor = MetadataVisitor { env: Environment { env: env, - stack: ScopedMap::new(), + stack: FnvMap::default(), }, }; - visitor.metadata_expr(expr) + let metadata = visitor.metadata_expr(expr); + (metadata, visitor.env.stack) } diff --git a/check/tests/completion.rs b/check/tests/completion.rs index ec1d2b2e78..9526d3258c 100644 --- a/check/tests/completion.rs +++ b/check/tests/completion.rs @@ -6,6 +6,7 @@ extern crate gluon_base as base; extern crate gluon_parser as parser; extern crate gluon_check as check; +use base::metadata::Metadata; use base::pos::BytePos; use base::types::{Field, Type, ArcType}; use check::completion::{self, Suggestion}; @@ -32,7 +33,21 @@ fn suggest_types(s: &str, pos: BytePos) -> Result, ()> { } fn suggest(s: &str, pos: BytePos) -> Result, ()> { - suggest_types(s, pos).map(|vec| vec.into_iter().map(|suggestion| suggestion.name).collect()) + suggest_types(s, pos).map(|vec| { + vec.into_iter() + .map(|suggestion| suggestion.name) + .collect() + }) +} + +fn get_metadata(s: &str, pos: BytePos) -> Option { + let env = MockEnv::new(); + + let (mut expr, result) = support::typecheck_expr(s); + assert!(result.is_ok(), "{}", result.unwrap_err()); + + let (_, metadata_map) = check::metadata::metadata(&env, &mut expr); + completion::get_metadata(&metadata_map, &mut expr, pos).cloned() } #[test] @@ -345,3 +360,68 @@ test test1 assert_eq!(result, expected); } + +#[test] +fn metadata_at_variable() { + let _ = env_logger::init(); + + let text = r#" +/// test +let abc = 1 +let abb = 2 +abb +abc +"#; + let result = get_metadata(text, BytePos::from(37)); + + let expected = None; + assert_eq!(result, expected); + + let result = get_metadata(text, BytePos::from(41)); + + let expected = Some(Metadata { + comment: Some("test".to_string()), + ..Metadata::default() + }); + assert_eq!(result, expected); +} + +#[test] +fn metadata_at_binop() { + let _ = env_logger::init(); + + let text = r#" +/// test +let (+++) x y = 1 +1 +++ 3 +"#; + let result = get_metadata(text, BytePos::from(32)); + + let expected = Some(Metadata { + comment: Some("test".to_string()), + ..Metadata::default() + }); + assert_eq!(result, expected); +} + + +#[test] +fn metadata_at_field_access() { + let _ = env_logger::init(); + + let text = r#" +let module = { + /// test + abc = 1, + abb = 2 + } +module.abc +"#; + let result = get_metadata(text, BytePos::from(81)); + + let expected = Some(Metadata { + comment: Some("test".to_string()), + ..Metadata::default() + }); + assert_eq!(result, expected); +} diff --git a/check/tests/metadata.rs b/check/tests/metadata.rs index 97ff2878eb..15a8035922 100644 --- a/check/tests/metadata.rs +++ b/check/tests/metadata.rs @@ -6,9 +6,13 @@ extern crate gluon_base as base; extern crate gluon_parser as parser; extern crate gluon_check as check; +use base::ast::SpannedExpr; use base::metadata::{Metadata, MetadataEnv}; use base::symbol::Symbol; -use check::metadata::metadata; + +fn metadata(env: &MetadataEnv, expr: &mut SpannedExpr) -> Metadata { + check::metadata::metadata(env, expr).0 +} mod support; @@ -57,9 +61,9 @@ let id x = x let metadata = metadata(&MockEnv, &mut expr); assert_eq!(metadata.module.get("id"), Some(&Metadata { - comment: Some("The identity function".into()), - module: Default::default(), - })); + comment: Some("The identity function".into()), + module: Default::default(), + })); } #[test] @@ -78,9 +82,9 @@ type Test = Int let metadata = metadata(&MockEnv, &mut expr); assert_eq!(metadata.module.get("Test"), Some(&Metadata { - comment: Some("A test type".into()), - module: Default::default(), - })); + comment: Some("A test type".into()), + module: Default::default(), + })); } #[test] @@ -100,7 +104,7 @@ fn propagate_metadata_record_field_comment() { let metadata = metadata(&MockEnv, &mut expr); assert_eq!(metadata.module.get("id"), Some(&Metadata { - comment: Some("The identity function".into()), - module: Default::default(), - })); + comment: Some("The identity function".into()), + module: Default::default(), + })); } diff --git a/check/tests/support/mod.rs b/check/tests/support/mod.rs index 15eb240e0e..e482586fe2 100644 --- a/check/tests/support/mod.rs +++ b/check/tests/support/mod.rs @@ -1,6 +1,7 @@ use base::ast::{DisplayEnv, IdentEnv, SpannedExpr}; use base::error::InFile; use base::kind::{ArcKind, Kind, KindEnv}; +use base::metadata::{Metadata, MetadataEnv}; use base::symbol::{Symbols, SymbolModule, Symbol, SymbolRef}; use base::types::{self, Alias, ArcType, Generic, PrimitiveEnv, RecordSelector, Type, TypeEnv}; use check::typecheck::{self, Typecheck}; @@ -105,6 +106,13 @@ impl PrimitiveEnv for MockEnv { } } +impl MetadataEnv for MockEnv { + fn get_metadata(&self, _id: &Symbol) -> Option<&Metadata> { + None + } +} + + #[allow(dead_code)] pub struct MockIdentEnv(PhantomData); diff --git a/src/compiler_pipeline.rs b/src/compiler_pipeline.rs index f5b7937f4f..b973ec77b0 100644 --- a/src/compiler_pipeline.rs +++ b/src/compiler_pipeline.rs @@ -335,7 +335,7 @@ impl<'vm, E> Executable<'vm, ()> for CompileValue typ, function, } = self; - let metadata = metadata::metadata(&*vm.get_env(), expr.borrow_mut()); + let (metadata, _) = metadata::metadata(&*vm.get_env(), expr.borrow_mut()); let closure = try_future!(vm.global_env().new_global_thunk(function)); let filename = filename.to_string(); vm.call_thunk(closure) diff --git a/src/lib.rs b/src/lib.rs index 44d1e56e72..baf4cedc90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -233,7 +233,7 @@ impl Compiler { use check::metadata; let (mut expr, typ) = self.typecheck_str(vm, file, expr_str, None)?; - let metadata = metadata::metadata(&*vm.get_env(), &mut expr); + let (metadata, _) = metadata::metadata(&*vm.get_env(), &mut expr); Ok((expr, typ, metadata)) } From 989a575a7ff71ce7f2b5dc8e03a937c33b0228d6 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sun, 28 May 2017 00:00:34 +0200 Subject: [PATCH 2/4] refactor(check): &mut Expr -> &Expr for metadata computation --- check/src/metadata.rs | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/check/src/metadata.rs b/check/src/metadata.rs index 9067154b0b..8063dcdc5e 100644 --- a/check/src/metadata.rs +++ b/check/src/metadata.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use base::ast::{self, Expr, Pattern, SpannedExpr, SpannedPattern, ValueBinding}; -use base::ast::MutVisitor; +use base::ast::Visitor; use base::fnv::FnvMap; use base::metadata::{Metadata, MetadataEnv}; use base::symbol::{Name, Symbol}; @@ -13,16 +13,16 @@ struct Environment<'b> { /// Queries `expr` for the metadata which it contains. pub fn metadata(env: &MetadataEnv, - expr: &mut SpannedExpr) + expr: &SpannedExpr) -> (Metadata, FnvMap) { struct MetadataVisitor<'b> { env: Environment<'b>, } impl<'b> MetadataVisitor<'b> { - fn new_binding(&mut self, metadata: Metadata, bind: &mut ValueBinding) { + fn new_binding(&mut self, metadata: Metadata, bind: &ValueBinding) { match bind.name.value { - Pattern::Ident(ref mut id) => { + Pattern::Ident(ref id) => { let metadata = bind.comment .as_ref() .map_or(metadata, |comment| { @@ -33,21 +33,21 @@ pub fn metadata(env: &MetadataEnv, }); self.stack_var(id.name.clone(), metadata); } - _ => self.new_pattern(metadata, &mut bind.name), + _ => self.new_pattern(metadata, &bind.name), } } - fn new_pattern(&mut self, mut metadata: Metadata, pattern: &mut SpannedPattern) { + fn new_pattern(&mut self, mut metadata: Metadata, pattern: &SpannedPattern) { match pattern.value { Pattern::Record { - ref mut fields, - ref mut types, + ref fields, + ref types, .. } => { for field in fields { if let Some(m) = metadata.module.remove(field.0.as_ref()) { let id = match field.1 { - Some(ref mut pat) => { + Some(ref pat) => { match pat.value { Pattern::Ident(ref id) => &id.name, _ => return self.new_pattern(m, pat), @@ -65,7 +65,7 @@ pub fn metadata(env: &MetadataEnv, } } } - Pattern::Ident(ref mut id) => { + Pattern::Ident(ref id) => { self.stack_var(id.name.clone(), metadata); } Pattern::Tuple { .. } | @@ -92,22 +92,22 @@ pub fn metadata(env: &MetadataEnv, .or_else(|| self.env.env.get_metadata(id)) } - fn metadata_expr(&mut self, expr: &mut SpannedExpr) -> Metadata { + fn metadata_expr(&mut self, expr: &SpannedExpr) -> Metadata { match expr.value { - Expr::Ident(ref mut id) => { + Expr::Ident(ref id) => { self.metadata(&id.name) .cloned() .unwrap_or_else(Metadata::default) } Expr::Record { - ref mut exprs, - ref mut types, + ref exprs, + ref types, .. } => { let mut module = BTreeMap::new(); for field in exprs { let maybe_metadata = match field.value { - Some(ref mut expr) => { + Some(ref expr) => { let m = self.metadata_expr(expr); if m.has_data() { Some(m) } else { None } } @@ -143,26 +143,26 @@ pub fn metadata(env: &MetadataEnv, module: module, } } - Expr::LetBindings(ref mut bindings, ref mut expr) => { + Expr::LetBindings(ref bindings, ref expr) => { let is_recursive = bindings.iter().all(|bind| !bind.args.is_empty()); if is_recursive { - for bind in bindings.iter_mut() { + for bind in bindings { self.new_binding(Metadata::default(), bind); } for bind in bindings { - self.metadata_expr(&mut bind.expr); + self.metadata_expr(&bind.expr); } } else { for bind in bindings { - let metadata = self.metadata_expr(&mut bind.expr); + let metadata = self.metadata_expr(&bind.expr); self.new_binding(metadata, bind); } } let result = self.metadata_expr(expr); result } - Expr::TypeBindings(ref mut bindings, ref mut expr) => { - for bind in bindings.iter_mut() { + Expr::TypeBindings(ref bindings, ref expr) => { + for bind in bindings { let maybe_metadata = bind.comment .as_ref() .map(|comment| { @@ -179,17 +179,17 @@ pub fn metadata(env: &MetadataEnv, result } _ => { - ast::walk_mut_expr(self, expr); + ast::walk_expr(self, expr); Metadata::default() } } } } - impl<'b> MutVisitor for MetadataVisitor<'b> { + impl<'b> Visitor for MetadataVisitor<'b> { type Ident = Symbol; - fn visit_expr(&mut self, expr: &mut SpannedExpr) { + fn visit_expr(&mut self, expr: &SpannedExpr) { self.metadata_expr(expr); } } From 2cfc5a3d707c355ef596f8b208964fef57e4b216 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sun, 28 May 2017 14:59:02 +0200 Subject: [PATCH 3/4] feat(check): Produce metadata for auto completed variables --- check/src/completion.rs | 149 ++++++++++++++++++++++++++++++++------ check/tests/completion.rs | 50 +++++++++++++ 2 files changed, 175 insertions(+), 24 deletions(-) diff --git a/check/src/completion.rs b/check/src/completion.rs index 40e94beae2..bfee3ef8cd 100644 --- a/check/src/completion.rs +++ b/check/src/completion.rs @@ -65,6 +65,37 @@ struct Suggest { result: Vec, } +fn expr_iter<'e>(stack: &'e ScopedMap, + expr: &'e SpannedExpr) + -> Box + 'e> { + if let Expr::Ident(ref ident) = expr.value { + Box::new(stack + .iter() + .filter(move |&(k, _)| { + k.declared_name().starts_with(ident.name.declared_name()) + })) + } else { + Box::new(None.into_iter()) + } +} + +impl Suggest + where E: TypeEnv +{ + fn ident_iter(&self, context: &SpannedExpr, ident: &Symbol) -> Vec<(Symbol, ArcType)> { + if let Expr::Projection(ref expr, _, _) = context.value { + let typ = resolve::remove_aliases(&self.env, expr.env_type_of(&self.env)); + let id = ident.as_ref(); + typ.row_iter() + .filter(move |field| field.name.as_ref().starts_with(id)) + .map(|field| (field.name.clone(), field.typ.clone())) + .collect() + } else { + vec![] + } + } +} + impl OnFound for Suggest { fn on_ident(&mut self, ident: &TypedIdent) { self.stack.insert(ident.name.clone(), ident.typ.clone()); @@ -108,33 +139,26 @@ impl OnFound for Suggest { } fn expr(&mut self, expr: &SpannedExpr) { - if let Expr::Ident(ref ident) = expr.value { - for (k, typ) in self.stack.iter() { - if k.declared_name().starts_with(ident.name.declared_name()) { - self.result - .push(Suggestion { - name: k.declared_name().into(), - typ: typ.clone(), - }); - } - } - } + self.result + .extend(expr_iter(&self.stack, expr).map(|(k, typ)| { + + Suggestion { + name: k.declared_name().into(), + typ: typ.clone(), + } + })); } fn ident(&mut self, context: &SpannedExpr, ident: &Symbol, _: &ArcType) { - if let Expr::Projection(ref expr, _, _) = context.value { - let typ = resolve::remove_aliases(&self.env, expr.env_type_of(&self.env)); - let id = ident.as_ref(); - for field in typ.row_iter() { - if field.name.as_ref().starts_with(id) { - self.result - .push(Suggestion { - name: field.name.declared_name().into(), - typ: field.typ.clone(), - }); - } - } - } + let iter = self.ident_iter(context, ident); + self.result + .extend(iter.into_iter() + .map(|(name, typ)| { + Suggestion { + name: name.declared_name().into(), + typ: typ, + } + })); } fn nothing(&mut self) { @@ -189,6 +213,58 @@ impl<'a> OnFound for GetMetadata<'a> { } } +struct MetadataSuggest<'a, E> { + env: &'a FnvMap, + suggest: Suggest, + ident: &'a str, + result: Option<&'a Metadata>, +} + +impl<'a, E: TypeEnv> OnFound for MetadataSuggest<'a, E> { + fn on_ident(&mut self, ident: &TypedIdent) { + self.suggest.on_ident(ident) + } + + fn on_pattern(&mut self, pattern: &SpannedPattern) { + self.suggest.on_pattern(pattern) + } + + fn expr(&mut self, expr: &SpannedExpr) { + let suggestion = expr_iter(&self.suggest.stack, expr).find(|&(name, _)| { + name.declared_name() == + self.ident + }); + if let Some((name, _)) = suggestion { + self.result = self.env.get(name); + } + } + + fn ident(&mut self, context: &SpannedExpr, _: &Symbol, _typ: &ArcType) { + match context.value { + Expr::Projection(ref expr, _, _) => { + if let Expr::Ident(ref expr_ident) = expr.value { + self.result = self.env + .get(&expr_ident.name) + .and_then(|metadata| metadata.module.get(self.ident)); + } + } + _ => (), + } + } + + fn nothing(&mut self) { + self.result = self.suggest + .stack + .iter() + .find(|&(ref name, _)| name.declared_name() == self.ident) + .and_then(|t| self.env.get(t.0)); + } + + fn found(&self) -> bool { + self.result.is_some() + } +} + struct FindVisitor { pos: BytePos, on_found: F, @@ -384,3 +460,28 @@ pub fn get_metadata<'a>(env: &'a FnvMap, visitor.visit_expr(expr); visitor.on_found.result } + +pub fn suggest_metadata<'a, T>(env: &'a FnvMap, + type_env: &T, + expr: &SpannedExpr, + pos: BytePos, + name: &'a str) + -> Option<&'a Metadata> + where T: TypeEnv +{ + let mut visitor = FindVisitor { + pos: pos, + on_found: MetadataSuggest { + env: env, + suggest: Suggest { + env: type_env, + stack: ScopedMap::new(), + result: Vec::new(), + }, + ident: name, + result: None, + }, + }; + visitor.visit_expr(expr); + visitor.on_found.result +} diff --git a/check/tests/completion.rs b/check/tests/completion.rs index 9526d3258c..1020cfa557 100644 --- a/check/tests/completion.rs +++ b/check/tests/completion.rs @@ -50,6 +50,16 @@ fn get_metadata(s: &str, pos: BytePos) -> Option { completion::get_metadata(&metadata_map, &mut expr, pos).cloned() } +fn suggest_metadata(s: &str, pos: BytePos, name: &str) -> Option { + let env = MockEnv::new(); + + let (mut expr, _result) = support::typecheck_expr(s); + + let (_, metadata_map) = check::metadata::metadata(&env, &mut expr); + completion::suggest_metadata(&metadata_map, &env, &mut expr, pos, name).cloned() +} + + #[test] fn identifier() { let env = MockEnv::new(); @@ -425,3 +435,43 @@ module.abc }); assert_eq!(result, expected); } + +#[test] +fn suggest_metadata_at_variable() { + let _ = env_logger::init(); + + let text = r#" +/// test +let abc = 1 +let abb = 2 +ab +"#; + let result = suggest_metadata(text, BytePos::from(36), "abc"); + + let expected = Some(Metadata { + comment: Some("test".to_string()), + ..Metadata::default() + }); + assert_eq!(result, expected); +} + +#[test] +fn suggest_metadata_at_field_access() { + let _ = env_logger::init(); + + let text = r#" +let module = { + /// test + abc = 1, + abb = 2 + } +module.ab +"#; + let result = suggest_metadata(text, BytePos::from(81), "abc"); + + let expected = Some(Metadata { + comment: Some("test".to_string()), + ..Metadata::default() + }); + assert_eq!(result, expected); +} From d6c02a0ec3069a606492194cbe5aa3963316e502 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Mon, 29 May 2017 23:35:18 +0200 Subject: [PATCH 4/4] fix(check): Propagate metadata through field accesses --- check/src/metadata.rs | 12 ++++++++---- check/tests/metadata.rs | 24 ++++++++++++++++++++++++ tests/vm.rs | 2 +- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/check/src/metadata.rs b/check/src/metadata.rs index 8063dcdc5e..501bfaee52 100644 --- a/check/src/metadata.rs +++ b/check/src/metadata.rs @@ -76,10 +76,6 @@ pub fn metadata(env: &MetadataEnv, fn stack_var(&mut self, id: Symbol, metadata: Metadata) { if metadata.has_data() { debug!("Insert {}", id); - // All symbols should have been renamed to unique ones - debug_assert!(!self.env.stack.contains_key(&id), - "Symbol {:?}, appears twice in the source", - id); self.env.stack.insert(id, metadata); } } @@ -178,6 +174,14 @@ pub fn metadata(env: &MetadataEnv, let result = self.metadata_expr(expr); result } + Expr::Projection(ref expr, ref field, _) => { + let metadata = self.metadata_expr(expr); + metadata + .module + .get(field.as_ref()) + .cloned() + .unwrap_or_default() + } _ => { ast::walk_expr(self, expr); Metadata::default() diff --git a/check/tests/metadata.rs b/check/tests/metadata.rs index 15a8035922..190cdacc0f 100644 --- a/check/tests/metadata.rs +++ b/check/tests/metadata.rs @@ -108,3 +108,27 @@ fn propagate_metadata_record_field_comment() { module: Default::default(), })); } + +#[test] +fn projection_has_metadata() { + let _ = env_logger::init(); + + let text = r#" +let x = { + /// The identity function + id = \x -> x +} +x.id +"#; + let (mut expr, result) = support::typecheck_expr(text); + + assert!(result.is_ok(), "{}", result.unwrap_err()); + + let metadata = metadata(&MockEnv, &mut expr); + assert_eq!(metadata, + Metadata { + comment: Some("The identity function".into()), + module: Default::default(), + }); + +} diff --git a/tests/vm.rs b/tests/vm.rs index c9fe9e3af1..d54ca5d85f 100644 --- a/tests/vm.rs +++ b/tests/vm.rs @@ -785,7 +785,7 @@ sender fn invalid_string_slice_dont_panic() { let _ = ::env_logger::init(); let text = r#" -let string = import! "std/string.glu" +let string = import! "std/string.glu" let s = "åäö" string.slice s 1 (string.length s) "#;