Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions manual/src/reference/types/option.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ if value.is_none() {
}
```

`None` is a literal, like `true` and `false`, so it can't be used as a variable or parameter name.

Some functions have variations with a `?` appended to their name that return options instead of throwing errors:

```ndc
Expand Down
4 changes: 1 addition & 3 deletions ndc_analyser/src/analyser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ impl Analyser {
) -> Result<StaticType, AnalysisError> {
match expression {
Expression::BoolLiteral(_) => Ok(StaticType::Bool),
Expression::NoneLiteral => Ok(StaticType::Option(Box::new(StaticType::Any))),
Expression::StringLiteral(_) => Ok(StaticType::String),
Expression::NumericLiteral(NumericLiteral::Int64(_)) => Ok(StaticType::Int),
Expression::NumericLiteral(NumericLiteral::Float64(_)) => Ok(StaticType::Float),
Expand All @@ -271,9 +272,6 @@ impl Analyser {
resolved,
..
} => {
if ident == "None" {
return Ok(StaticType::Option(Box::new(StaticType::Any)));
}
let Some(binding) = self.scope_tree.get_binding_any(ident) else {
self.emit(AnalysisError::identifier_not_previously_declared(
ident, *span,
Expand Down
5 changes: 3 additions & 2 deletions ndc_bin/src/highlighter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ impl AndycppHighlighter {
let colored = match &token.token {
// Strings — green
Token::String(_) => substring.rgb(152, 195, 121),
// Numeric literals and booleans — orange
Token::NumericLiteral(_) | Token::True | Token::False => {
// Numeric literals, booleans and None — orange
Token::NumericLiteral(_) | Token::True | Token::False | Token::None => {
substring.rgb(209, 154, 102)
}
// Keywords — coral red
Expand Down Expand Up @@ -238,6 +238,7 @@ fn collect_function_spans(expr: &ExpressionLocation, spans: &mut AHashSet<usize>
}
// Leaves — no sub-expressions to recurse into
Expression::BoolLiteral(_)
| Expression::NoneLiteral
| Expression::StringLiteral(_)
| Expression::NumericLiteral(_)
| Expression::Identifier { .. }
Expand Down
3 changes: 3 additions & 0 deletions ndc_lexer/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pub enum Token {
If,
In,
Let,
None,
Pure,
Return,
Struct,
Expand Down Expand Up @@ -159,6 +160,7 @@ impl fmt::Display for Token {
Self::If => "if",
Self::In => "in",
Self::Let => "let",
Self::None => "None",
Self::Pure => "pure",
Self::Return => "return",
Self::Struct => "struct",
Expand Down Expand Up @@ -335,6 +337,7 @@ impl From<String> for Token {
"if" => Self::If,
"in" => Self::In,
"let" => Self::Let,
"None" => Self::None,
"not" => Self::LogicNot,
"or" => Self::LogicOr,
"pure" => Self::Pure,
Expand Down
2 changes: 1 addition & 1 deletion ndc_lsp/src/features/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ fn merged_type_display(candidates: &[&StaticType]) -> String {
/// Language keywords offered in general (non-dot) completion.
const KEYWORDS: &[&str] = &[
"let", "fn", "struct", "if", "else", "while", "for", "in", "as", "return", "break", "continue",
"true", "false",
"true", "false", "None",
];

fn keyword_completions() -> impl Iterator<Item = CompletionItem> {
Expand Down
1 change: 1 addition & 0 deletions ndc_lsp/src/scope_resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ fn collect(expr: &ExpressionLocation, scope: Span, out: &mut Vec<Decl>) {
}
Expression::Identifier { .. }
| Expression::BoolLiteral(_)
| Expression::NoneLiteral
| Expression::StringLiteral(_)
| Expression::NumericLiteral(_)
| Expression::Break
Expand Down
2 changes: 2 additions & 0 deletions ndc_lsp/src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ fn child_expressions(expr: &ExpressionLocation) -> Vec<&ExpressionLocation> {
// fields are names + type annotations, not expressions.
Expression::Identifier { .. }
| Expression::BoolLiteral(_)
| Expression::NoneLiteral
| Expression::StringLiteral(_)
| Expression::NumericLiteral(_)
| Expression::Break
Expand Down Expand Up @@ -357,6 +358,7 @@ fn walk_expression(visitor: &mut impl AstVisitor, expr: &ExpressionLocation) {
// fields are names + type annotations, not expressions.
Expression::Identifier { .. }
| Expression::BoolLiteral(_)
| Expression::NoneLiteral
| Expression::StringLiteral(_)
| Expression::NumericLiteral(_)
| Expression::Break
Expand Down
2 changes: 2 additions & 0 deletions ndc_parser/src/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ pub struct ExpressionLocation {
pub enum Expression {
// Literals
BoolLiteral(bool),
/// `None`, the empty option.
NoneLiteral,
StringLiteral(String),
NumericLiteral(NumericLiteral),
Identifier {
Expand Down
1 change: 1 addition & 0 deletions ndc_parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,7 @@ impl Parser {
let expression = match token_location.token {
Token::False => Expression::BoolLiteral(false),
Token::True => Expression::BoolLiteral(true),
Token::None => Expression::NoneLiteral,
Token::NumericLiteral(literal) => Expression::NumericLiteral(literal),
Token::String(value) => Expression::StringLiteral(value),
Token::Identifier(identifier) => Expression::Identifier {
Expand Down
28 changes: 28 additions & 0 deletions ndc_parser/tests/literals.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#![allow(unused_crate_dependencies)]

use ndc_lexer::{Lexer, SourceId};
use ndc_parser::{Expression, ExpressionLocation, Parser};

fn parse_one(source: &str) -> ExpressionLocation {
let tokens = Lexer::new(source, SourceId::SYNTHETIC)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let mut expressions = Parser::from_tokens(tokens).parse().unwrap();
assert_eq!(expressions.len(), 1);
expressions.pop().unwrap()
}

#[test]
fn none_is_a_literal() {
assert_eq!(parse_one("None").expression, Expression::NoneLiteral);
}

#[test]
fn none_cannot_be_a_binding_name() {
for source in ["let None = 5", "fn f(None) { 1 }"] {
let tokens = Lexer::new(source, SourceId::SYNTHETIC)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert!(Parser::from_tokens(tokens).parse().is_err(), "{source}");
}
}
13 changes: 5 additions & 8 deletions ndc_vm/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ impl Compiler {
let idx = self.ir.add_constant(Value::Bool(b));
self.ir.write(OpCode::Constant(idx), span);
}
Expression::NoneLiteral => {
let idx = self.ir.add_constant(Value::None);
self.ir.write(OpCode::Constant(idx), span);
}
Expression::StringLiteral(s) => {
let idx = self.ir.add_constant(Value::string(s));
self.ir.write(OpCode::Constant(idx), span);
Expand All @@ -228,14 +232,7 @@ impl Compiler {
let idx = self.ir.add_constant(value);
self.ir.write(OpCode::Constant(idx), span);
}
Expression::Identifier { name, resolved, .. } => {
if name == "None" {
let idx = self.ir.add_constant(Value::None);
self.ir.write(OpCode::Constant(idx), span);
} else {
self.compile_binding(resolved, span)?;
}
}
Expression::Identifier { resolved, .. } => self.compile_binding(resolved, span)?,
Expression::Cast {
value,
resolved_type,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// `None` is a literal like `true`, so it can't name a variable.
// expect-error: Invalid assignment target
let None = 5;
Loading