feat: added attributes in order to derive match and open

This commit is contained in:
felipegchi 2022-11-17 09:47:35 -03:00
parent f22d92da95
commit b7bc9e9f1f
18 changed files with 379 additions and 81 deletions

View File

@ -8,4 +8,5 @@ edition = "2021"
[dependencies]
kind-span = { path = "../kind-span" }
kind-tree = { path = "../kind-tree" }
kind-report = { path = "../kind-report" }
kind-report = { path = "../kind-report" }
fxhash = "0.2.1"

View File

@ -2,3 +2,4 @@
pub mod matching;
pub mod open;
pub mod subst;

View File

@ -1,6 +1,7 @@
//! Module to derive a dependent
//! eliminator out of a sum type declaration.
use fxhash::FxHashMap;
use kind_span::Range;
use kind_tree::concrete::expr::Expr;
@ -9,6 +10,8 @@ use kind_tree::concrete::*;
use kind_tree::concrete::{self};
use kind_tree::symbol::{Ident, QualifiedIdent};
use crate::subst::{substitute_in_expr};
/// Derives an eliminator from a sum type declaration.
pub fn derive_match(range: Range, sum: &SumTypeDecl) -> concrete::Entry {
let mk_var = |name: Ident| -> Box<Expr> {
@ -202,11 +205,11 @@ pub fn derive_match(range: Range, sum: &SumTypeDecl) -> concrete::Entry {
spine_params = sum
.parameters
.extend(&cons.args)
.map(|x| x.name.with_name(|f| format!("{}", f)))
.map(|x| x.name.with_name(|f| format!("_{}_", f)))
.to_vec();
spine = cons
.args
.map(|x| x.name.with_name(|f| format!("{}", f)))
.map(|x| x.name.with_name(|f| format!("_{}_", f)))
.to_vec();
args_indices = sp
.iter()
@ -218,7 +221,22 @@ pub fn derive_match(range: Range, sum: &SumTypeDecl) -> concrete::Entry {
Binding::Named(_, _, _) => unreachable!(),
})
.collect::<Vec<AppBinding>>();
args_indices = args_indices[sum.parameters.len()..].into();
args_indices = {
let mut indices = args_indices[sum.parameters.len()..].to_vec();
let renames = FxHashMap::from_iter(
sum.parameters
.extend(&cons.args)
.map(|x| (x.name.to_string(), format!("_{}_", x.name.to_string())))
.iter()
.cloned(),
);
for indice in &mut indices {
substitute_in_expr(&mut indice.data, &renames)
}
indices
};
}
_ => unreachable!(),
},
@ -228,12 +246,12 @@ pub fn derive_match(range: Range, sum: &SumTypeDecl) -> concrete::Entry {
.parameters
.extend(&sum.indices)
.extend(&cons.args)
.map(|x| x.name.with_name(|f| format!("{}", f)))
.map(|x| x.name.with_name(|f| format!("_{}_", f)))
.to_vec();
spine = sum
.indices
.extend(&cons.args)
.map(|x| x.name.with_name(|f| format!("{}", f)))
.map(|x| x.name.with_name(|f| format!("_{}_", f)))
.to_vec();
args_indices = sum
.indices

View File

@ -0,0 +1,21 @@
use fxhash::FxHashMap;
use kind_tree::{concrete::{visitor::Visitor, expr::Expr}, symbol::Symbol};
pub struct Subst<'a> {
pub names: &'a FxHashMap<String, String>,
}
impl<'a> Visitor for Subst<'a> {
fn visit_ident(&mut self, ident: &mut kind_tree::symbol::Ident) {
if let Some(res) = self.names.get(ident.to_str()) {
ident.data = Symbol::new(res.clone());
}
}
}
pub fn substitute_in_expr(expr: &mut Expr, names: &FxHashMap<String, String>) {
let mut session = Subst {
names,
};
session.visit_expr(expr)
}

View File

@ -123,6 +123,13 @@ impl<'a> Parser<'a> {
Ok(ident)
}
pub fn parse_any_id(&mut self) -> Result<Ident, SyntaxError> {
let range = self.range();
let id = eat_single!(self, Token::LowerId(x) | Token::UpperId(x, None) => x.clone())?;
let ident = Ident::new_static(&id, range);
Ok(ident)
}
pub fn parse_upper_id(&mut self) -> Result<QualifiedIdent, SyntaxError> {
let range = self.range();
let (start, end) =
@ -554,11 +561,11 @@ impl<'a> Parser<'a> {
pub fn parse_sttm(&mut self) -> Result<Box<Sttm>, SyntaxError> {
let start = self.range();
if self.check_actual(Token::Ask) {
if self.check_actual_id("ask") {
self.parse_ask()
} else if self.check_actual(Token::Return) {
} else if self.check_actual_id("return") {
self.parse_return()
} else if self.check_actual(Token::Let) {
} else if self.check_actual_id("let") {
self.parse_monadic_let()
} else {
let expr = self.parse_expr(false)?;
@ -643,7 +650,7 @@ impl<'a> Parser<'a> {
let mut cases = Vec::new();
while !self.get().same_variant(&Token::RBrace) {
let constructor = self.parse_id()?;
let constructor = self.parse_any_id()?;
let (_range, bindings, ignore_rest) = self.parse_pat_destruct_bindings()?;
self.eat_variant(Token::FatArrow)?;
let value = self.parse_expr(false)?;
@ -722,7 +729,7 @@ impl<'a> Parser<'a> {
self.eat_variant(Token::LBrace)?;
let if_ = self.parse_expr(false)?;
self.eat_variant(Token::RBrace)?;
self.eat_variant(Token::Else)?;
self.eat_id("else")?;
self.eat_variant(Token::LBrace)?;
let els_ = self.parse_expr(false)?;
let end = self.eat_variant(Token::RBrace)?.1;
@ -738,13 +745,13 @@ impl<'a> Parser<'a> {
/// some looakhead tokens.
pub fn parse_expr(&mut self, multiline: bool) -> Result<Box<Expr>, SyntaxError> {
self.ignore_docs();
if self.check_actual(Token::Do) {
if self.check_actual_id("do") {
self.parse_do()
} else if self.check_actual(Token::Match) {
} else if self.check_actual_id("match") {
self.parse_match()
} else if self.check_actual(Token::Let) {
} else if self.check_actual_id("let") {
self.parse_let()
} else if self.check_actual(Token::If) {
} else if self.check_actual_id("if") {
self.parse_if()
} else if self.check_actual(Token::Dollar) {
self.parse_sigma_pair()

View File

@ -52,18 +52,6 @@ impl<'a> Lexer<'a> {
pub fn to_keyword(data: &str) -> Token {
match data {
"_" => Token::Hole,
"ask" => Token::Ask,
"do" => Token::Do,
"if" => Token::If,
"else" => Token::Else,
"match" => Token::Match,
"let" => Token::Let,
"use" => Token::Use,
"as" => Token::As,
"return" => Token::Return,
"type" => Token::Type,
"record" => Token::Record,
"constructor" => Token::Constructor,
_ => Token::LowerId(data.to_string()),
}
}

View File

@ -30,18 +30,18 @@ pub enum Token {
UpperId(String, Option<String>),
// Keywords
Do,
If,
Else,
Match,
Ask,
Return,
Let,
Type,
Record,
Constructor,
Use,
As,
// Do,
// If,
// Else,
// Match,
// Ask,
// Return,
// Let,
// Type,
// Record,
// Constructor,
// Use,
// As,
// Literals
Char(char),
@ -139,18 +139,6 @@ impl fmt::Display for Token {
Token::LowerId(id) => write!(f, "{}", id),
Token::UpperId(main, Some(aux)) => write!(f, "{}/{}", main, aux),
Token::UpperId(main, None) => write!(f, "{}", main),
Token::Do => write!(f, "do"),
Token::If => write!(f, "if"),
Token::Else => write!(f, "else"),
Token::Match => write!(f, "match"),
Token::Ask => write!(f, "ask"),
Token::Return => write!(f, "return"),
Token::Let => write!(f, "let"),
Token::Type => write!(f, "type"),
Token::Record => write!(f, "record"),
Token::Constructor => write!(f, "constructor"),
Token::Use => write!(f, "use"),
Token::As => write!(f, "as"),
Token::Char(c) => write!(f, "'{}'", c),
Token::Str(s) => write!(f, "\"{}\"", s),
Token::Num60(n) => write!(f, "{}", n),

View File

@ -105,6 +105,13 @@ impl<'a> Parser<'a> {
}
}
pub fn eat_id(&mut self, expect: &str) -> Result<(Token, Range), SyntaxError> {
match self.get() {
Token::LowerId(x) if x == expect => Ok(self.advance()),
_ => self.fail(vec![Token::LowerId(expect.to_string())])
}
}
pub fn eat<T>(&mut self, expect: fn(&Token) -> Option<T>) -> Result<T, SyntaxError> {
match expect(self.get()) {
None => self.fail(vec![]),
@ -128,6 +135,10 @@ impl<'a> Parser<'a> {
self.get().same_variant(&expect)
}
pub fn check_actual_id(&self, expect: &str) -> bool {
matches!(self.get(), Token::LowerId(x) if x == expect)
}
pub fn try_single<T>(
&mut self,
fun: &dyn Fn(&mut Parser<'a>) -> Result<T, SyntaxError>,

View File

@ -6,11 +6,30 @@ use crate::lexer::tokens::Token;
use crate::state::Parser;
impl<'a> Parser<'a> {
pub fn parse_attr_args(&mut self) -> Result<Vec<AttributeStyle>, SyntaxError> {
let mut attrs = Vec::new();
let range = self.range();
if self.check_and_eat(Token::LBracket) {
while let Some(res) = self.try_single(&|fun| fun.parse_attr_style())? {
attrs.push(res);
if !self.check_and_eat(Token::Comma) {
break;
}
}
self.eat_closing_keyword(Token::RBracket, range)?;
}
Ok(attrs)
}
pub fn parse_attr_style(&mut self) -> Result<AttributeStyle, SyntaxError> {
match self.get().clone() {
Token::LowerId(_) => {
Token::LowerId(_) | Token::UpperId(_, None) => {
let range = self.range();
let ident = self.parse_id()?;
let ident = self.parse_any_id()?;
Ok(AttributeStyle::Ident(range, ident))
}
Token::Num60(num) => {
@ -46,7 +65,11 @@ impl<'a> Parser<'a> {
pub fn parse_attr(&mut self) -> Result<Attribute, SyntaxError> {
let start = self.range();
self.eat_variant(Token::Hash)?;
let name = self.parse_id()?;
let args = self.parse_attr_args()?;
let style = if self.check_and_eat(Token::Eq) {
Some(self.parse_attr_style()?)
} else {
@ -55,6 +78,7 @@ impl<'a> Parser<'a> {
Ok(Attribute {
range: start.mix(style.clone().map(|x| x.locate()).unwrap_or(name.range)),
value: style,
args,
name,
})
}

View File

@ -32,8 +32,8 @@ impl<'a> Parser<'a> {
pub fn is_top_level_start(&self) -> bool {
self.is_top_level_entry()
|| self.get().same_variant(&Token::Type)
|| self.get().same_variant(&Token::Record)
|| self.check_actual_id("type")
|| self.check_actual_id("record")
|| self.get().same_variant(&Token::Hash)
|| self.get().is_doc()
}
@ -231,13 +231,13 @@ impl<'a> Parser<'a> {
let docs = self.parse_docs()?;
let attrs = self.parse_attrs()?;
if self.check_actual(Token::Type) {
if self.check_actual_id("type") {
Ok(TopLevel::SumType(self.parse_sum_type_def(docs, attrs)?))
} else if self.check_actual(Token::Record) {
} else if self.check_actual_id("record") {
Ok(TopLevel::RecordType(self.parse_record_def(docs, attrs)?))
} else if self.is_top_level_entry_continuation() {
Ok(TopLevel::Entry(self.parse_entry(docs, attrs)?))
} else if self.check_actual(Token::Use) {
} else if self.check_actual_id("use") {
Err(SyntaxError::CannotUseUse(self.range()))
} else {
self.fail(vec![])
@ -245,9 +245,9 @@ impl<'a> Parser<'a> {
}
pub fn parse_use(&mut self) -> Result<(String, String), SyntaxError> {
self.eat_variant(Token::Use)?;
self.eat_id("use")?;
let origin = self.parse_upper_id()?;
self.eat_variant(Token::As)?;
self.eat_id("as")?;
let alias = self.parse_upper_id()?;
match (origin, alias) {
@ -275,7 +275,7 @@ impl<'a> Parser<'a> {
let mut entries: Vec<TopLevel> = Vec::new();
let mut uses: FxHashMap<String, String> = Default::default();
while self.get().same_variant(&Token::Use) {
while self.check_actual_id("use") {
match self.parse_use() {
Ok((origin, alias)) => {
uses.insert(alias, origin);

View File

@ -7,7 +7,7 @@ use crate::state::Parser;
impl<'a> Parser<'a> {
pub fn parse_constructor(&mut self) -> Result<Constructor, SyntaxError> {
let docs = self.parse_docs()?;
let name = self.parse_id()?;
let name = self.parse_any_id()?;
let args = self.parse_arguments()?;
let typ = if self.check_and_eat(Token::Colon) {
@ -29,7 +29,7 @@ impl<'a> Parser<'a> {
docs: Vec<String>,
attrs: Vec<Attribute>,
) -> Result<SumTypeDecl, SyntaxError> {
self.eat_variant(Token::Type)?;
self.eat_id("type")?;
let name = self.parse_upper_id()?;
let parameters = self.parse_arguments()?;
@ -66,7 +66,7 @@ impl<'a> Parser<'a> {
docs: Vec<String>,
attrs: Vec<Attribute>,
) -> Result<RecordDecl, SyntaxError> {
self.eat_variant(Token::Record)?;
self.eat_id("record")?;
let name = self.parse_upper_id()?;
let parameters = self.parse_arguments()?;
@ -74,7 +74,7 @@ impl<'a> Parser<'a> {
let range = self.range();
self.eat_variant(Token::LBrace)?;
self.eat_variant(Token::Constructor)?;
self.eat_id("constructor")?;
let constructor = self.parse_id()?;

View File

@ -4,7 +4,7 @@ use kind_tree::concrete::{expr, CaseBinding, Destruct, TopLevel};
use kind_tree::desugared;
use kind_tree::symbol::Ident;
use crate::errors::PassError;
use crate::errors::{PassError, Sugar};
use super::DesugarState;
@ -68,6 +68,7 @@ impl<'a> DesugarState<'a> {
match binding {
Destruct::Destruct(_, typ, case, jump_rest) => {
let count = self.old_book.count.get(&typ.to_string()).unwrap();
let open_id = typ.add_segment("open");
let rec = count
.is_record_cons_of
@ -81,6 +82,11 @@ impl<'a> DesugarState<'a> {
return desugared::Expr::err(typ.range);
};
if self.old_book.count.get(&open_id.to_string()).is_none() {
self.send_err(PassError::NeedToImplementMethods(binding.locate(), Sugar::Open(typ.to_string())));
return desugared::Expr::err(range);
}
let ordered_fields = self.order_case_arguments(
(&typ.range, typ.to_string()),
&record
@ -102,8 +108,6 @@ impl<'a> DesugarState<'a> {
}
}
let open_id = typ.add_segment("open");
let irrelev = count.arguments.map(|x| x.erased).to_vec();
let spine = vec![
@ -148,6 +152,13 @@ impl<'a> DesugarState<'a> {
) -> Box<desugared::Expr> {
let entry = self.old_book.entries.get(&match_.typ.to_string()).unwrap();
let match_id = match_.typ.add_segment("match");
if self.old_book.entries.get(&match_id.to_string()).is_none() {
self.send_err(PassError::NeedToImplementMethods(range, Sugar::Match(match_.typ.to_string())));
return desugared::Expr::err(range);
}
let sum = if let TopLevel::SumType(sum) = entry {
sum
} else {
@ -226,7 +237,6 @@ impl<'a> DesugarState<'a> {
self.send_err(PassError::NoCoverage(range, unbound))
}
let match_id = match_.typ.add_segment("match");
let motive = if let Some(res) = &match_.motive {
self.desugar_expr(res)

View File

@ -7,6 +7,8 @@ pub enum Sugar {
Sigma,
Pair,
BoolIf,
Match(String),
Open(String),
}
/// Describes all of the possible errors inside each
@ -30,6 +32,11 @@ pub enum PassError {
ShouldBeAParameter(Span, Range),
NoFieldCoverage(Range, Vec<String>),
CannotPatternMatchOnErased(Range),
AttributeDoesNotAcceptEqual(Range),
InvalidAttributeArgument(Range),
DuplicatedAttributeArgument(Range, Range),
CannotDerive(String, Range),
}
// TODO: A way to build an error message with methods
@ -150,11 +157,13 @@ impl Diagnostic for PassError {
Sugar::Sigma => "You must implement 'Sigma' in order to use the sigma notation.".to_string(),
Sugar::Pair => "You must implement 'Sigma' and 'Sigma.new' in order to use the sigma notation.".to_string(),
Sugar::BoolIf => "You must implement 'Bool.if' in order to use the if notation.".to_string(),
Sugar::Match(name) => format!("You must implement '{}.match' in order to use the match notation (or derive match with #derive[match]).", name),
Sugar::Open(name) => format!("You must implement '{}.open' in order to use the open notation (or derive open with #derive[open]).", name),
}],
positions: vec![Marker {
position: *expr_place,
color: Color::Fst,
text: "Here!".to_string(),
text: "You cannot use this expression!".to_string(),
no_code: false,
main: true,
}],
@ -422,6 +431,68 @@ impl Diagnostic for PassError {
main: true,
}],
},
PassError::AttributeDoesNotAcceptEqual(place) => DiagnosticFrame {
code: 209,
severity: Severity::Error,
title: "This attribute does not support values!".to_string(),
subtitles: vec![],
hints: vec![],
positions: vec![Marker {
position: *place,
color: Color::Fst,
text: "Try to remove everything after the equal".to_string(),
no_code: false,
main: true,
}],
},
PassError::InvalidAttributeArgument(place) => DiagnosticFrame {
code: 209,
severity: Severity::Error,
title: "Invalid attribute argument".to_string(),
subtitles: vec![],
hints: vec![],
positions: vec![Marker {
position: *place,
color: Color::Fst,
text: "Remove it or replace".to_string(),
no_code: false,
main: true,
}],
},
PassError::CannotDerive(name, place) => DiagnosticFrame {
code: 209,
severity: Severity::Error,
title: format!("Cannot derive '{}' for this definition", name),
subtitles: vec![],
hints: vec![],
positions: vec![Marker {
position: *place,
color: Color::Fst,
text: "Here!".to_string(),
no_code: false,
main: true,
}],
},
PassError::DuplicatedAttributeArgument(first, sec) => DiagnosticFrame {
code: 209,
severity: Severity::Warning,
title: "Duplicated attribute argument".to_string(),
subtitles: vec![],
hints: vec![],
positions: vec![Marker {
position: *sec,
color: Color::For,
text: "Second declaration".to_string(),
no_code: false,
main: true,
},Marker {
position: *first,
color: Color::For,
text: "First declaration!".to_string(),
no_code: false,
main: true,
}],
},
}
}
}

View File

@ -2,38 +2,182 @@
//! Currently it just derives `match` and `open` for sum type
//! and record types respectively.
use std::fmt::Display;
use std::sync::mpsc::Sender;
use fxhash::FxHashMap;
use kind_derive::matching::derive_match;
use kind_derive::open::derive_open;
use kind_report::data::Diagnostic;
use kind_tree::concrete::{Book, TopLevel};
use kind_span::Locatable;
use kind_span::Range;
use kind_tree::concrete::{Attribute, Book, TopLevel};
use crate::errors::PassError;
/// Expands sum type and record definitions to a lot of
/// helper definitions like eliminators and replace qualified identifiers
/// by their module names.
pub mod uses;
pub fn expand_book(error_channel: Sender<Box<dyn Diagnostic>>, book: &mut Book) {
#[derive(Debug, Hash, PartialEq, Eq)]
pub enum Derive {
Match,
Open
}
impl Display for Derive {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Derive::Match => write!(f, "match"),
Derive::Open => write!(f, "open"),
}
}
}
pub fn insert_or_report(
channel: Sender<Box<dyn Diagnostic>>,
hashmap: &mut FxHashMap<Derive, Range>,
key: Derive,
range: Range,
) {
match hashmap.get(&key) {
Some(last_range) => {
channel
.send(Box::new(PassError::DuplicatedAttributeArgument(last_range.clone(), range)))
.unwrap();
},
None => {
hashmap.insert(key, range);
},
}
}
fn string_to_derive(name: &str) -> Option<Derive> {
match name {
"match" => Some(Derive::Match),
"open" => Some(Derive::Open),
_ => None,
}
}
pub fn expand_derive(
error_channel: Sender<Box<dyn Diagnostic>>,
attrs: &[Attribute],
) -> Option<FxHashMap<Derive, Range>> {
let mut failed = false;
let mut def = FxHashMap::default();
for attr in attrs {
if attr.name.to_str() != "derive" {
continue;
}
if let Some(attr) = &attr.value {
error_channel
.send(Box::new(PassError::AttributeDoesNotAcceptEqual(
attr.locate(),
)))
.unwrap();
failed = true;
}
use kind_tree::concrete::AttributeStyle::*;
for arg in &attr.args {
match arg {
Ident(range, ident) => match string_to_derive(ident.to_str()) {
Some(key) => insert_or_report(error_channel.clone(), &mut def, key, range.clone()),
_ => {
error_channel
.send(Box::new(PassError::InvalidAttributeArgument(
ident.locate(),
)))
.unwrap();
failed = true;
}
},
other => {
error_channel
.send(Box::new(PassError::InvalidAttributeArgument(
other.locate(),
)))
.unwrap();
failed = true;
}
}
}
}
if failed {
None
} else {
Some(def)
}
}
pub fn expand_book(error_channel: Sender<Box<dyn Diagnostic>>, book: &mut Book) -> bool {
let mut failed = false;
let mut entries = FxHashMap::default();
for entry in book.entries.values() {
match entry {
TopLevel::SumType(sum) => {
let res = derive_match(sum.name.range, sum);
let info = res.extract_book_info();
entries.insert(res.name.to_string(), (res, info));
if let Some(derive) = expand_derive(error_channel.clone(), &sum.attrs) {
for (key, val) in derive {
match key {
Derive::Match => {
let res = derive_match(sum.name.range, sum);
let info = res.extract_book_info();
entries.insert(res.name.to_string(), (res, info));
}
other => {
error_channel
.send(Box::new(PassError::CannotDerive(
other.to_string(), val,
)))
.unwrap();
failed = true;
}
}
}
} else {
failed = true;
}
}
TopLevel::RecordType(rec) => {
let res = derive_open(rec.name.range, rec);
let info = res.extract_book_info();
entries.insert(res.name.to_string(), (res, info));
if let Some(derive) = expand_derive(error_channel.clone(), &rec.attrs) {
for (key, val) in derive {
match key {
Derive::Open => {
let res = derive_open(rec.name.range, rec);
let info = res.extract_book_info();
entries.insert(res.name.to_string(), (res, info));
}
other => {
error_channel
.send(Box::new(PassError::CannotDerive(
other.to_string(), val,
)))
.unwrap();
failed = true;
}
}
}
} else {
failed = true;
}
}
TopLevel::Entry(_) => (),
}
}
for (name, (tl, count)) in entries {
book.count.insert(name.clone(), count);
book.names.insert(name.clone().to_string(), tl.name.clone());
book.entries.insert(name.clone(), TopLevel::Entry(tl));
}
failed
}

View File

@ -6,6 +6,6 @@
pub mod desugar;
pub mod erasure;
mod errors;
pub mod expand;
pub mod unbound;
mod errors;

View File

@ -15,8 +15,7 @@ pub struct Resource<T> {
path: PathBuf,
concrete_tree: concrete::Module,
/// Accumulated diagnostics while
diagnostics: Vec<Box<dyn Diagnostic>>,
/// Useful for LSP URIs
ext: T,
}

View File

@ -67,6 +67,7 @@ pub enum AttributeStyle {
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Attribute {
pub name: Ident,
pub args: Vec<AttributeStyle>,
pub value: Option<AttributeStyle>,
pub range: Range,
}
@ -362,6 +363,14 @@ impl Display for AttributeStyle {
impl Display for Attribute {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, "#{}", self.name)?;
if !self.args.is_empty() {
write!(f, "[")?;
write!(f, "{}", self.args[0])?;
for arg in self.args[1..].iter() {
write!(f, ", {}", arg)?;
}
write!(f, "]")?;
}
if let Some(res) = &self.value {
write!(f, " = {}", res)?;
}

View File

@ -11,6 +11,12 @@ use kind_span::{Range, SyntaxCtxIndex};
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Symbol(String);
impl Symbol {
pub fn new(str: String) -> Symbol {
Symbol(str)
}
}
/// Identifier inside a syntax context.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Ident {