Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for leading | and & in types #306

Merged
merged 14 commits into from
Jul 27, 2024
82 changes: 60 additions & 22 deletions full-moon/src/ast/luau.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,6 @@ pub enum TypeInfo {
ellipsis: TokenReference,
},

/// An intersection type: `string & number`, denoting both types.
#[display(fmt = "{left}{ampersand}{right}")]
Intersection {
/// The left hand side: `string`.
left: Box<TypeInfo>,
/// The ampersand (`&`) to separate the types.
ampersand: TokenReference,
/// The right hand side: `number`.
right: Box<TypeInfo>,
},

/// A type coming from a module, such as `module.Foo`
#[display(fmt = "{module}{punctuation}{type_info}")]
Module {
Expand Down Expand Up @@ -153,17 +142,6 @@ pub enum TypeInfo {
types: Punctuated<TypeInfo>,
},

/// A union type: `string | number`, denoting one or the other.
#[display(fmt = "{left}{pipe}{right}")]
Union {
/// The left hand side: `string`.
left: Box<TypeInfo>,
/// The pipe (`|`) to separate the types.
pipe: TokenReference,
/// The right hand side: `number`.
right: Box<TypeInfo>,
},

/// A variadic type: `...number`.
#[display(fmt = "{ellipsis}{type_info}")]
Variadic {
Expand All @@ -181,6 +159,66 @@ pub enum TypeInfo {
/// The name of the type that is variadic: `T`
name: TokenReference,
},

/// A union type, such as `string | number`.
#[display(fmt = "{_0}")]
Union(TypeUnion),

/// An intersection type, such as `string & number`.
#[display(fmt = "{_0}")]
Intersection(TypeIntersection),
jackdotink marked this conversation as resolved.
Show resolved Hide resolved
}

/// A union type, such as `string | number`.
#[derive(Clone, Debug, Display, PartialEq, Node)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[display(fmt = "{}{types}", "display_option(leading)")]
pub struct TypeUnion {
pub(crate) leading: Option<TokenReference>,
pub(crate) types: Punctuated<TypeInfo>,
}

impl TypeUnion {
/// Creates a new Union from the given types.
pub fn new(leading: Option<TokenReference>, types: Punctuated<TypeInfo>) -> Self {
Self { leading, types }
}

/// The leading pipe, if one is present: `|`.
pub fn leading(&self) -> Option<&TokenReference> {
self.leading.as_ref()
}

/// The types being unioned: `string | number`.
pub fn types(&self) -> &Punctuated<TypeInfo> {
&self.types
}
}

/// An intersection type, such as `string & number`.
#[derive(Clone, Debug, Display, PartialEq, Node)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[display(fmt = "{}{types}", "display_option(leading)")]
pub struct TypeIntersection {
pub(crate) leading: Option<TokenReference>,
pub(crate) types: Punctuated<TypeInfo>,
}

impl TypeIntersection {
jackdotink marked this conversation as resolved.
Show resolved Hide resolved
/// Creates a new Intersection from the given types.
pub fn new(leading: Option<TokenReference>, types: Punctuated<TypeInfo>) -> Self {
Self { leading, types }
}

/// The leading pipe, if one is present: `&`.
Kampfkarren marked this conversation as resolved.
Show resolved Hide resolved
pub fn leading(&self) -> Option<&TokenReference> {
self.leading.as_ref()
}

/// The types being intersected: `string & number`.
pub fn types(&self) -> &Punctuated<TypeInfo> {
&self.types
}
}

/// A subset of TypeInfo that consists of items which can only be used as an index, such as `Foo` and `Foo<Bar>`,
Expand Down
78 changes: 50 additions & 28 deletions full-moon/src/ast/luau_visitors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,8 @@ impl Visit for TypeInfo {
types.visit(visitor);
parentheses.tokens.1.visit(visitor);
}
TypeInfo::Union { left, pipe, right } => {
left.visit(visitor);
pipe.visit(visitor);
right.visit(visitor);
}
TypeInfo::Intersection {
left,
ampersand,
right,
} => {
left.visit(visitor);
ampersand.visit(visitor);
right.visit(visitor);
}
TypeInfo::Union(union) => union.visit(visitor),
TypeInfo::Intersection(intersection) => intersection.visit(visitor),
TypeInfo::Variadic {
ellipsis,
type_info,
Expand Down Expand Up @@ -250,21 +238,11 @@ impl VisitMut for TypeInfo {
TypeInfo::Tuple { parentheses, types }
}

TypeInfo::Union { left, pipe, right } => TypeInfo::Union {
left: left.visit_mut(visitor),
pipe: pipe.visit_mut(visitor),
right: right.visit_mut(visitor),
},
TypeInfo::Union(union) => TypeInfo::Union(union.visit_mut(visitor)),

TypeInfo::Intersection {
left,
ampersand,
right,
} => TypeInfo::Intersection {
left: left.visit_mut(visitor),
ampersand: ampersand.visit_mut(visitor),
right: right.visit_mut(visitor),
},
TypeInfo::Intersection(intersection) => {
TypeInfo::Intersection(intersection.visit_mut(visitor))
}

TypeInfo::Variadic {
ellipsis,
Expand All @@ -284,6 +262,50 @@ impl VisitMut for TypeInfo {
}
}

impl Visit for TypeUnion {
fn visit<V: Visitor>(&self, visitor: &mut V) {
visitor.visit_type_union(self);

self.leading.visit(visitor);
self.types.visit(visitor);

visitor.visit_type_union_end(self);
}
}

impl VisitMut for TypeUnion {
fn visit_mut<V: VisitorMut>(mut self, visitor: &mut V) -> Self {
self = visitor.visit_type_union(self);

self.leading = self.leading.visit_mut(visitor);
self.types = self.types.visit_mut(visitor);

visitor.visit_type_union_end(self)
}
}

impl Visit for TypeIntersection {
fn visit<V: Visitor>(&self, visitor: &mut V) {
visitor.visit_type_intersection(self);

self.leading.visit(visitor);
self.types.visit(visitor);

visitor.visit_type_intersection_end(self);
}
}

impl VisitMut for TypeIntersection {
fn visit_mut<V: VisitorMut>(mut self, visitor: &mut V) -> Self {
self = visitor.visit_type_intersection(self);

self.leading = self.leading.visit_mut(visitor);
self.types = self.types.visit_mut(visitor);

visitor.visit_type_intersection_end(self)
}
}

impl Visit for IndexedTypeInfo {
fn visit<V: Visitor>(&self, visitor: &mut V) {
visitor.visit_indexed_type_info(self);
Expand Down
127 changes: 90 additions & 37 deletions full-moon/src/ast/parsers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2225,12 +2225,24 @@ fn expect_interpolated_string(

#[cfg(feature = "luau")]
fn parse_type(state: &mut ParserState) -> ParserResult<ast::TypeInfo> {
let ParserResult::Value(simple_type) = parse_simple_type(state, SimpleTypeStyle::Default)
else {
return ParserResult::LexerMoved;
let current_token = match state.current() {
Ok(token) => token,
Err(()) => return ParserResult::NotFound,
};
jackdotink marked this conversation as resolved.
Show resolved Hide resolved

parse_type_suffix(state, simple_type)
if let TokenType::Symbol {
symbol: Symbol::Pipe | Symbol::Ampersand,
} = current_token.token_type()
{
parse_type_suffix(state, None)
} else {
let ParserResult::Value(simple_type) = parse_simple_type(state, SimpleTypeStyle::Default)
else {
return ParserResult::LexerMoved;
};

parse_type_suffix(state, Some(simple_type))
}
}

#[cfg(feature = "luau")]
Expand All @@ -2246,7 +2258,7 @@ fn parse_type_or_pack(state: &mut ParserState) -> ParserResult<ast::TypeInfo> {
ParserResult::Value(simple_type)
}
ast::TypeInfo::VariadicPack { .. } | ast::TypeInfo::GenericPack { .. } => unreachable!(),
_ => parse_type_suffix(state, simple_type),
_ => parse_type_suffix(state, Some(simple_type)),
}
}

Expand Down Expand Up @@ -2437,21 +2449,62 @@ fn parse_simple_type(
#[cfg(feature = "luau")]
fn parse_type_suffix(
state: &mut ParserState,
simple_type: ast::TypeInfo,
simple_type: Option<ast::TypeInfo>,
) -> ParserResult<ast::TypeInfo> {
let mut is_union = false;
let mut is_intersection = false;

let mut types = Punctuated::new();

let leading = if simple_type.is_some() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or_else probably better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what I would use or_else on here

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see--it is inverted

None
} else {
let current_token = match state.current() {
Ok(token) => token,
Err(()) => {
unreachable!("parse_type_suffix called with no simple_type and no current token")
}
};
jackdotink marked this conversation as resolved.
Show resolved Hide resolved

match current_token.token_type() {
TokenType::Symbol {
symbol: Symbol::Pipe,
} => is_union = true,

TokenType::Symbol {
symbol: Symbol::Ampersand,
} => is_intersection = true,

_ => unreachable!("parse_type_suffix called with no simple_type and no `|` or `&`"),
}

Some(state.consume().unwrap())
};

let mut current_type = simple_type;

loop {
let Ok(current_token) = state.current() else {
return ParserResult::NotFound;
let ty = if current_type.is_some() {
jackdotink marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.take().unwrap_or_else(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't work because of the return

current_type.take().unwrap()
} else {
let ParserResult::Value(ty) = parse_simple_type(state, SimpleTypeStyle::Default) else {
return ParserResult::LexerMoved;
};

ty
Kampfkarren marked this conversation as resolved.
Show resolved Hide resolved
};

let current_token = match state.current() {
Ok(token) => token,
Err(()) => {
types.push(Pair::End(ty));
break;
}
};

match current_token.token_type() {
TokenType::Symbol {
symbol: Symbol::Pipe,
symbol: Symbol::QuestionMark,
} => {
if is_intersection {
state.token_error(
Expand All @@ -2461,22 +2514,18 @@ fn parse_type_suffix(
return ParserResult::LexerMoved;
}

let pipe = state.consume().unwrap();
let question_mark = state.consume().unwrap();

let ParserResult::Value(right) = parse_simple_type(state, SimpleTypeStyle::Default)
else {
return ParserResult::LexerMoved;
};
current_type = Some(ast::TypeInfo::Optional {
base: Box::new(ty),
question_mark,
});

current_type = ast::TypeInfo::Union {
left: Box::new(current_type),
pipe,
right: Box::new(right),
};
is_union = true;
}

TokenType::Symbol {
symbol: Symbol::QuestionMark,
symbol: Symbol::Pipe,
} => {
if is_intersection {
state.token_error(
Expand All @@ -2486,13 +2535,11 @@ fn parse_type_suffix(
return ParserResult::LexerMoved;
}

let question_mark = state.consume().unwrap();
current_type = ast::TypeInfo::Optional {
base: Box::new(current_type),
question_mark,
};
let pipe = state.consume().unwrap();
types.push(Pair::new(ty, Some(pipe)));
is_union = true;
}

TokenType::Symbol {
symbol: Symbol::Ampersand,
} => {
Expand All @@ -2505,24 +2552,30 @@ fn parse_type_suffix(
}

let ampersand = state.consume().unwrap();
types.push(Pair::new(ty, Some(ampersand)));
is_intersection = true;
}

let ParserResult::Value(right) = parse_simple_type(state, SimpleTypeStyle::Default)
else {
return ParserResult::LexerMoved;
};
_ if types.is_empty() => return ParserResult::Value(ty),

current_type = ast::TypeInfo::Intersection {
left: Box::new(current_type),
ampersand,
right: Box::new(right),
};
is_intersection = true;
_ => {
types.push(Pair::End(ty));
break;
}
_ => break,
}
}

ParserResult::Value(current_type)
match (is_union, is_intersection) {
(true, false) => {
ParserResult::Value(ast::TypeInfo::Union(ast::TypeUnion::new(leading, types)))
}

(false, true) => ParserResult::Value(ast::TypeInfo::Intersection(
ast::TypeIntersection::new(leading, types),
)),

_ => unreachable!(),
}
}

#[cfg(feature = "luau")]
Expand Down
Loading