Skip to content

Commit

Permalink
implement table type access modifiers (#313)
Browse files Browse the repository at this point in the history
Implements table type access modifiers:
```luau
type foo = {
    read bar: number
    write baz: string,
}
```

---------

Co-authored-by: YetAnotherClown <[email protected]>
Co-authored-by: boyned//Kampfkarren <[email protected]>
  • Loading branch information
3 people authored Oct 13, 2024
1 parent 7ed9d15 commit 511d4fb
Show file tree
Hide file tree
Showing 16 changed files with 773 additions and 30 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased
### Added
- Added `access` fields that contain a `Option<TokenReference>` to `TypeInfo::Array` and `TypeField`.
- Added support for parsing table type field access modifiers, such as `{ read foo: number }`.

## [1.0.0] - 2024-10-08

### Added
Expand Down
32 changes: 30 additions & 2 deletions full-moon/src/ast/luau.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,22 @@ use derive_more::Display;
#[non_exhaustive]
pub enum TypeInfo {
/// A shorthand type annotating the structure of an array: { number }
#[display("{}{}{}", braces.tokens().0, type_info, braces.tokens().1)]
#[display(
"{}{}{}{}",
braces.tokens().0,
display_option(access),
type_info,
braces.tokens().1
)]
Array {
/// The braces (`{}`) containing the type info.
braces: ContainedSpan,
/// The access modifer of the array, `read` in `{ read number }`.
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
access: Option<TokenReference>,
/// The type info for the values in the Array
type_info: Box<TypeInfo>,
},
Expand Down Expand Up @@ -266,8 +278,13 @@ pub enum IndexedTypeInfo {
/// The `foo: number` in `{ foo: number }`.
#[derive(Clone, Debug, Display, PartialEq, Node, Visit)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[display("{key}{colon}{value}")]
#[display("{}{key}{colon}{value}", display_option(access))]
pub struct TypeField {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub(crate) access: Option<TokenReference>,
pub(crate) key: TypeFieldKey,
pub(crate) colon: TokenReference,
pub(crate) value: TypeInfo,
Expand All @@ -277,12 +294,18 @@ impl TypeField {
/// Creates a new TypeField from the given key and value
pub fn new(key: TypeFieldKey, value: TypeInfo) -> Self {
Self {
access: None,
key,
colon: TokenReference::symbol(": ").unwrap(),
value,
}
}

/// The access modifier of the field, `read` in `read foo: number`.
pub fn access(&self) -> Option<&TokenReference> {
self.access.as_ref()
}

/// The key of the field, `foo` in `foo: number`.
pub fn key(&self) -> &TypeFieldKey {
&self.key
Expand Down Expand Up @@ -311,6 +334,11 @@ impl TypeField {
}
}

/// Returns a new TypeField with the given access modifier.
pub fn with_access(self, access: Option<TokenReference>) -> Self {
Self { access, ..self }
}

/// Returns a new TypeField with the `:` token
pub fn with_value(self, value: TypeInfo) -> Self {
Self { value, ..self }
Expand Down
15 changes: 13 additions & 2 deletions full-moon/src/ast/luau_visitors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ impl Visit for TypeInfo {
fn visit<V: Visitor>(&self, visitor: &mut V) {
visitor.visit_type_info(self);
match self {
TypeInfo::Array { braces, type_info } => {
TypeInfo::Array {
braces,
access,
type_info,
} => {
braces.tokens.0.visit(visitor);
access.visit(visitor);
type_info.visit(visitor);
braces.tokens.1.visit(visitor);
}
Expand Down Expand Up @@ -114,13 +119,19 @@ impl VisitMut for TypeInfo {
self = match self {
TypeInfo::Array {
mut braces,
mut access,
mut type_info,
} => {
braces.tokens.0 = braces.tokens.0.visit_mut(visitor);
access = access.visit_mut(visitor);
type_info = type_info.visit_mut(visitor);
braces.tokens.1 = braces.tokens.1.visit_mut(visitor);

TypeInfo::Array { braces, type_info }
TypeInfo::Array {
braces,
access,
type_info,
}
}
TypeInfo::Basic(__self_0) => TypeInfo::Basic(__self_0.visit_mut(visitor)),
TypeInfo::Boolean(__self_0) => TypeInfo::Boolean(__self_0.visit_mut(visitor)),
Expand Down
39 changes: 27 additions & 12 deletions full-moon/src/ast/parsers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2583,6 +2583,14 @@ fn expect_type_table(
let mut array_type = None;

loop {
let access = if matches!(state.current(), Ok(token) if token.token_kind() == TokenKind::Identifier && matches!(token.token().to_string().as_str(), "read" | "write"))
&& !matches!(state.peek(), Ok(token) if token.is_symbol(Symbol::Colon))
{
Some(state.consume().unwrap())
} else {
None
};

let current_token = state.current()?;

let field = if current_token.is_symbol(Symbol::RightBrace) {
Expand Down Expand Up @@ -2620,6 +2628,7 @@ fn expect_type_table(
};

ast::TypeField {
access,
key: ast::TypeFieldKey::IndexSignature {
brackets: ContainedSpan::new(left_brace, right_brace),
inner: ast::TypeInfo::String(property),
Expand Down Expand Up @@ -2674,6 +2683,7 @@ fn expect_type_table(
has_indexer = true;

ast::TypeField {
access,
key: ast::TypeFieldKey::IndexSignature {
brackets: ContainedSpan::new(left_brace, right_brace),
inner: key,
Expand All @@ -2686,17 +2696,20 @@ fn expect_type_table(
&& !(current_token.token_kind() == TokenKind::Identifier
&& matches!(state.peek(), Ok(token) if token.is_symbol(Symbol::Colon)))
{
array_type = Some(match parse_type(state) {
ParserResult::Value(value) => value,
ParserResult::NotFound => {
state.token_error(
state.current().unwrap().clone(),
"expected type for table array",
);
return Err(());
}
ParserResult::LexerMoved => return Err(()),
});
array_type = Some((
access,
match parse_type(state) {
ParserResult::Value(value) => value,
ParserResult::NotFound => {
state.token_error(
state.current().unwrap().clone(),
"expected type for table array",
);
return Err(());
}
ParserResult::LexerMoved => return Err(()),
},
));
break;
} else {
match parse_name(state) {
Expand All @@ -2719,6 +2732,7 @@ fn expect_type_table(
};

ast::TypeField {
access,
key: ast::TypeFieldKey::Name(name.name),
colon,
value,
Expand Down Expand Up @@ -2752,9 +2766,10 @@ fn expect_type_table(

let braces = ContainedSpan::new(left_brace, right_brace);

if let Some(type_info) = array_type {
if let Some((access, type_info)) = array_type {
Ok(ast::TypeInfo::Array {
braces,
access,
type_info: Box::new(type_info),
})
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: full-moon/tests/fail_cases.rs
expression: result.ast
expression: result.ast()
input_file: full-moon/tests/roblox_cases/fail/parser/type_table_no_right_brace
---
nodes:
stmts:
Expand Down Expand Up @@ -409,4 +410,3 @@ eof:
token_type:
type: Eof
trailing_trivia: []

Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
source: full-moon/tests/pass_cases.rs
assertion_line: 48
expression: ast.nodes()
input_file: full-moon/tests/roblox_cases/pass/generic_functions
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
source: full-moon/tests/pass_cases.rs
expression: ast.nodes()

input_file: full-moon/tests/roblox_cases/pass/shorthand_array_type
---
stmts:
- - TypeDeclaration:
Expand Down Expand Up @@ -494,4 +494,3 @@ stmts:
type: Whitespace
characters: " "
- ~

Loading

0 comments on commit 511d4fb

Please sign in to comment.