mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Improve unification of arg types in parser
This commit is contained in:
parent
7c25c3319d
commit
77623264ae
5 changed files with 144 additions and 93 deletions
|
@ -11,5 +11,3 @@ pub use item::DefinitionsItem;
|
||||||
pub use kind::DefinitionsItemKind;
|
pub use kind::DefinitionsItemKind;
|
||||||
pub use tag::DefinitionsItemTag;
|
pub use tag::DefinitionsItemTag;
|
||||||
pub use tree::DefinitionsTree;
|
pub use tree::DefinitionsTree;
|
||||||
|
|
||||||
pub const PIPE_SEPARATOR: &str = " | ";
|
|
||||||
|
|
|
@ -33,6 +33,12 @@ impl DefinitionsParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Parses the given Luau type definitions into parser items.
|
||||||
|
|
||||||
|
The parser items will be stored internally and can be converted
|
||||||
|
into usable definition items using [`DefinitionsParser::drain`].
|
||||||
|
*/
|
||||||
pub fn parse<S>(&mut self, contents: S) -> Result<()>
|
pub fn parse<S>(&mut self, contents: S) -> Result<()>
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
|
@ -82,7 +88,7 @@ impl DefinitionsParser {
|
||||||
|
|
||||||
fn convert_parser_item_into_doc_item(&self, item: DefinitionsParserItem) -> DefinitionsItem {
|
fn convert_parser_item_into_doc_item(&self, item: DefinitionsParserItem) -> DefinitionsItem {
|
||||||
let mut builder = DefinitionsItemBuilder::new()
|
let mut builder = DefinitionsItemBuilder::new()
|
||||||
.with_kind(item.type_info.to_definitions_kind())
|
.with_kind(item.type_info.parse_definitions_kind())
|
||||||
.with_name(&item.name);
|
.with_name(&item.name);
|
||||||
if self.found_top_level_declares.contains(&item.name) {
|
if self.found_top_level_declares.contains(&item.name) {
|
||||||
builder = builder.as_exported();
|
builder = builder.as_exported();
|
||||||
|
@ -109,13 +115,19 @@ impl DefinitionsParser {
|
||||||
builder.build().unwrap()
|
builder.build().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Converts currently stored parser items into definition items.
|
||||||
|
|
||||||
|
This will consume (drain) all stored parser items, leaving the parser empty.
|
||||||
|
*/
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
pub fn into_definition_items(mut self) -> Result<Vec<DefinitionsItem>> {
|
pub fn drain(&mut self) -> Result<Vec<DefinitionsItem>> {
|
||||||
let mut top_level_items = self.found_top_level_items.drain(..).collect::<Vec<_>>();
|
let mut top_level_items = self.found_top_level_items.drain(..).collect::<Vec<_>>();
|
||||||
let results = top_level_items
|
let results = top_level_items
|
||||||
.drain(..)
|
.drain(..)
|
||||||
.map(|visitor_item| self.convert_parser_item_into_doc_item(visitor_item))
|
.map(|visitor_item| self.convert_parser_item_into_doc_item(visitor_item))
|
||||||
.collect();
|
.collect();
|
||||||
|
self.found_top_level_declares = Vec::new();
|
||||||
Ok(results)
|
Ok(results)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ impl DefinitionsTree {
|
||||||
.parse(type_definitions_contents)
|
.parse(type_definitions_contents)
|
||||||
.context("Failed to parse type definitions AST")?;
|
.context("Failed to parse type definitions AST")?;
|
||||||
let top_level_definition_items = parser
|
let top_level_definition_items = parser
|
||||||
.into_definition_items()
|
.drain()
|
||||||
.context("Failed to convert parser items into definition items")?;
|
.context("Failed to convert parser items into definition items")?;
|
||||||
let root = DefinitionsItemBuilder::new()
|
let root = DefinitionsItemBuilder::new()
|
||||||
.with_kind(DefinitionsItemKind::Root)
|
.with_kind(DefinitionsItemKind::Root)
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
use full_moon::ast::types::{TypeArgument, TypeInfo};
|
use full_moon::{
|
||||||
|
ast::types::{TypeArgument, TypeInfo},
|
||||||
|
tokenizer::{Symbol, Token, TokenReference, TokenType},
|
||||||
|
};
|
||||||
|
|
||||||
use super::kind::DefinitionsItemKind;
|
use super::kind::DefinitionsItemKind;
|
||||||
|
|
||||||
pub const PIPE_SEPARATOR: &str = " | ";
|
pub(crate) trait TypeInfoExt {
|
||||||
|
|
||||||
pub(super) trait TypeInfoExt {
|
|
||||||
fn is_fn(&self) -> bool;
|
fn is_fn(&self) -> bool;
|
||||||
fn to_definitions_kind(&self) -> DefinitionsItemKind;
|
fn parse_definitions_kind(&self) -> DefinitionsItemKind;
|
||||||
fn stringify_simple(&self, parent_typ: Option<&TypeInfo>) -> String;
|
fn stringify_simple(&self, parent_typ: Option<&TypeInfo>) -> String;
|
||||||
fn extract_args<'a>(&'a self, base: Vec<Vec<&'a TypeArgument>>) -> Vec<Vec<&'a TypeArgument>>;
|
fn extract_args(&self, base: Vec<TypeArgument>) -> Vec<TypeArgument>;
|
||||||
fn extract_args_normalized(&self) -> Option<Vec<String>>;
|
fn extract_args_normalized(&self) -> Option<Vec<String>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeInfoExt for TypeInfo {
|
impl TypeInfoExt for TypeInfo {
|
||||||
|
/**
|
||||||
|
Checks if this type represents a function or not.
|
||||||
|
|
||||||
|
If the type is a tuple, union, or intersection, it will be checked recursively.
|
||||||
|
*/
|
||||||
fn is_fn(&self) -> bool {
|
fn is_fn(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
TypeInfo::Callback { .. } => true,
|
TypeInfo::Callback { .. } => true,
|
||||||
|
@ -24,15 +30,21 @@ impl TypeInfoExt for TypeInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_definitions_kind(&self) -> DefinitionsItemKind {
|
/**
|
||||||
|
Parses the definitions item kind from the type.
|
||||||
|
|
||||||
|
If the type is a tupe, union, or intersection, all the inner types
|
||||||
|
are required to be equivalent in terms of definitions item kinds.
|
||||||
|
*/
|
||||||
|
fn parse_definitions_kind(&self) -> DefinitionsItemKind {
|
||||||
match self {
|
match self {
|
||||||
TypeInfo::Array { .. } | TypeInfo::Table { .. } => DefinitionsItemKind::Table,
|
TypeInfo::Array { .. } | TypeInfo::Table { .. } => DefinitionsItemKind::Table,
|
||||||
TypeInfo::Basic(_) | TypeInfo::String(_) => DefinitionsItemKind::Property,
|
TypeInfo::Basic(_) | TypeInfo::String(_) => DefinitionsItemKind::Property,
|
||||||
TypeInfo::Optional { base, .. } => Self::to_definitions_kind(base.as_ref()),
|
TypeInfo::Optional { base, .. } => Self::parse_definitions_kind(base.as_ref()),
|
||||||
TypeInfo::Tuple { types, .. } => {
|
TypeInfo::Tuple { types, .. } => {
|
||||||
let mut kinds = types
|
let mut kinds = types
|
||||||
.iter()
|
.iter()
|
||||||
.map(Self::to_definitions_kind)
|
.map(Self::parse_definitions_kind)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let kinds_all_the_same = kinds.windows(2).all(|w| w[0] == w[1]);
|
let kinds_all_the_same = kinds.windows(2).all(|w| w[0] == w[1]);
|
||||||
if kinds_all_the_same && !kinds.is_empty() {
|
if kinds_all_the_same && !kinds.is_empty() {
|
||||||
|
@ -44,8 +56,8 @@ impl TypeInfoExt for TypeInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TypeInfo::Union { left, right, .. } | TypeInfo::Intersection { left, right, .. } => {
|
TypeInfo::Union { left, right, .. } | TypeInfo::Intersection { left, right, .. } => {
|
||||||
let kind_left = Self::to_definitions_kind(left.as_ref());
|
let kind_left = Self::parse_definitions_kind(left.as_ref());
|
||||||
let kind_right = Self::to_definitions_kind(right.as_ref());
|
let kind_right = Self::parse_definitions_kind(right.as_ref());
|
||||||
if kind_left == kind_right {
|
if kind_left == kind_right {
|
||||||
kind_left
|
kind_left
|
||||||
} else {
|
} else {
|
||||||
|
@ -62,6 +74,22 @@ impl TypeInfoExt for TypeInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Stringifies the type into a simplified type string.
|
||||||
|
|
||||||
|
The simplified type string match one of the following formats:
|
||||||
|
|
||||||
|
* `any`
|
||||||
|
* `boolean`
|
||||||
|
* `string`
|
||||||
|
* `function`
|
||||||
|
* `table`
|
||||||
|
* `CustomTypeName`
|
||||||
|
* `TypeName?`
|
||||||
|
* `TypeName | OtherTypeName`
|
||||||
|
* `{ TypeName }`
|
||||||
|
* `"string-literal"`
|
||||||
|
*/
|
||||||
fn stringify_simple(&self, parent_typ: Option<&TypeInfo>) -> String {
|
fn stringify_simple(&self, parent_typ: Option<&TypeInfo>) -> String {
|
||||||
match self {
|
match self {
|
||||||
TypeInfo::Array { type_info, .. } => {
|
TypeInfo::Array { type_info, .. } => {
|
||||||
|
@ -96,8 +124,9 @@ impl TypeInfoExt for TypeInfo {
|
||||||
TypeInfo::Table { .. } => "table".to_string(),
|
TypeInfo::Table { .. } => "table".to_string(),
|
||||||
TypeInfo::Union { left, right, .. } => {
|
TypeInfo::Union { left, right, .. } => {
|
||||||
format!(
|
format!(
|
||||||
"{}{PIPE_SEPARATOR}{}",
|
"{} {} {}",
|
||||||
left.as_ref().stringify_simple(Some(self)),
|
left.as_ref().stringify_simple(Some(self)),
|
||||||
|
Symbol::Pipe,
|
||||||
right.as_ref().stringify_simple(Some(self))
|
right.as_ref().stringify_simple(Some(self))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -107,20 +136,18 @@ impl TypeInfoExt for TypeInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_args<'a>(&'a self, base: Vec<Vec<&'a TypeArgument>>) -> Vec<Vec<&'a TypeArgument>> {
|
fn extract_args(&self, base: Vec<TypeArgument>) -> Vec<TypeArgument> {
|
||||||
match self {
|
match self {
|
||||||
TypeInfo::Callback { arguments, .. } => {
|
TypeInfo::Callback { arguments, .. } => {
|
||||||
let mut result = base.clone();
|
merge_type_argument_vecs(base, arguments.iter().cloned().collect::<Vec<_>>())
|
||||||
result.push(arguments.iter().collect::<Vec<_>>());
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
TypeInfo::Tuple { types, .. } => types
|
TypeInfo::Tuple { types, .. } => types
|
||||||
.iter()
|
.iter()
|
||||||
.next()
|
.next()
|
||||||
.expect("Function tuple type was empty")
|
.expect("Function tuple type was empty")
|
||||||
.extract_args(base.clone()),
|
.extract_args(base),
|
||||||
TypeInfo::Union { left, right, .. } | TypeInfo::Intersection { left, right, .. } => {
|
TypeInfo::Union { left, right, .. } | TypeInfo::Intersection { left, right, .. } => {
|
||||||
let mut result = base.clone();
|
let mut result = base;
|
||||||
result = left.extract_args(result.clone());
|
result = left.extract_args(result.clone());
|
||||||
result = right.extract_args(result.clone());
|
result = right.extract_args(result.clone());
|
||||||
result
|
result
|
||||||
|
@ -131,76 +158,80 @@ impl TypeInfoExt for TypeInfo {
|
||||||
|
|
||||||
fn extract_args_normalized(&self) -> Option<Vec<String>> {
|
fn extract_args_normalized(&self) -> Option<Vec<String>> {
|
||||||
if self.is_fn() {
|
if self.is_fn() {
|
||||||
let mut type_args_multi = self.extract_args(Vec::new());
|
let separator = format!(" {} ", Symbol::Pipe);
|
||||||
match type_args_multi.len() {
|
let args_stringified_not_normalized = self
|
||||||
0 => None,
|
.extract_args(Vec::new())
|
||||||
1 => Some(
|
.iter()
|
||||||
// We got a normal function with some known list of args, and we will
|
.map(|type_arg| type_arg.type_info().stringify_simple(Some(self)))
|
||||||
// stringify the arg types into simple ones such as "function", "table", ..
|
.collect::<Vec<_>>();
|
||||||
type_args_multi
|
let mut args_stringified = Vec::new();
|
||||||
.pop()
|
for arg_string in args_stringified_not_normalized {
|
||||||
.unwrap()
|
let arg_parts = arg_string.split(&separator).collect::<Vec<_>>();
|
||||||
.iter()
|
// Check if we got any optional arg, if so then the entire possible
|
||||||
.map(|type_arg| type_arg.type_info().stringify_simple(Some(self)))
|
// union of args will be optional when merged together / normalized
|
||||||
.collect(),
|
let is_optional = arg_parts
|
||||||
),
|
.iter()
|
||||||
_ => {
|
.any(|part| part == &"nil" || part.ends_with('?'));
|
||||||
// We got a union or intersection function, meaning it has
|
// Get rid of any nils or optional markers since we keep track of it above
|
||||||
// several different overloads that accept different args
|
let mut arg_parts_no_nils = arg_parts
|
||||||
let mut unified_args = Vec::new();
|
.iter()
|
||||||
for index in 0..type_args_multi
|
.filter_map(|arg_part| {
|
||||||
.iter()
|
if arg_part == &"nil" {
|
||||||
.fold(0, |acc, type_args| acc.max(type_args.len()))
|
None
|
||||||
{
|
|
||||||
// Gather function arg type strings for all
|
|
||||||
// of the different variants of this function
|
|
||||||
let mut type_arg_strings = type_args_multi
|
|
||||||
.iter()
|
|
||||||
.filter_map(|type_args| type_args.get(index))
|
|
||||||
.map(|type_arg| type_arg.type_info().stringify_simple(Some(self)))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if type_arg_strings.len() < type_args_multi.len() {
|
|
||||||
for _ in type_arg_strings.len()..type_args_multi.len() {
|
|
||||||
type_arg_strings.push("nil".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Type arg strings may themselves be stringified to something like number | string so we
|
|
||||||
// will split that out to be able to handle it better with the following unification process
|
|
||||||
let mut type_arg_strings_sep = Vec::new();
|
|
||||||
for type_arg_string in type_arg_strings.drain(..) {
|
|
||||||
for typ_arg_string_inner in type_arg_string.split(PIPE_SEPARATOR) {
|
|
||||||
type_arg_strings_sep.push(typ_arg_string_inner.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Find out if we have any nillable type, to know if we
|
|
||||||
// should make the entire arg type union nillable or not
|
|
||||||
let has_any_optional = type_arg_strings_sep
|
|
||||||
.iter()
|
|
||||||
.any(|s| s == "nil" || s.ends_with('?'));
|
|
||||||
// Filter out any nils or optional markers (?),
|
|
||||||
// we will add this back at the end if necessary
|
|
||||||
let mut type_arg_strings_non_nil = type_arg_strings_sep
|
|
||||||
.iter()
|
|
||||||
.filter(|s| *s != "nil")
|
|
||||||
.map(|s| s.trim_end_matches('?').to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
type_arg_strings_non_nil.sort(); // Need to sort for dedup
|
|
||||||
type_arg_strings_non_nil.dedup(); // Dedup to get rid of redundant types such as string | string
|
|
||||||
unified_args.push(if has_any_optional {
|
|
||||||
if type_arg_strings_non_nil.len() == 1 {
|
|
||||||
format!("{}?", type_arg_strings_non_nil.pop().unwrap())
|
|
||||||
} else {
|
|
||||||
format!("({})?", type_arg_strings_non_nil.join(PIPE_SEPARATOR))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
type_arg_strings_non_nil.join(PIPE_SEPARATOR)
|
Some(arg_part.trim_end_matches('?'))
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
arg_parts_no_nils.sort_unstable(); // Sort the args to be able to dedup
|
||||||
|
arg_parts_no_nils.dedup(); // Deduplicate types that are the exact same shape
|
||||||
|
if is_optional {
|
||||||
|
if arg_parts_no_nils.len() > 1 {
|
||||||
|
// A union of args that is nillable should be enclosed in parens to make
|
||||||
|
// it more clear that the entire arg is nillable and not just the last type
|
||||||
|
args_stringified.push(format!("({})?", arg_parts_no_nils.join(&separator)));
|
||||||
|
} else {
|
||||||
|
// Just one nillable arg, does not need any parens
|
||||||
|
args_stringified.push(format!("{}?", arg_parts_no_nils.first().unwrap()));
|
||||||
}
|
}
|
||||||
Some(unified_args)
|
} else if arg_parts_no_nils.len() > 1 {
|
||||||
|
args_stringified.push(arg_parts_no_nils.join(&separator).to_string());
|
||||||
|
} else {
|
||||||
|
args_stringified.push((*arg_parts_no_nils.first().unwrap()).to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(args_stringified)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn merge_type_arguments(left: TypeArgument, right: TypeArgument) -> TypeArgument {
|
||||||
|
TypeArgument::new(TypeInfo::Union {
|
||||||
|
left: Box::new(left.type_info().clone()),
|
||||||
|
pipe: TokenReference::new(
|
||||||
|
vec![],
|
||||||
|
Token::new(TokenType::Symbol {
|
||||||
|
symbol: Symbol::Pipe,
|
||||||
|
}),
|
||||||
|
vec![],
|
||||||
|
),
|
||||||
|
right: Box::new(right.type_info().clone()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_type_argument_vecs(
|
||||||
|
existing: Vec<TypeArgument>,
|
||||||
|
new: Vec<TypeArgument>,
|
||||||
|
) -> Vec<TypeArgument> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for (index, argument) in new.iter().enumerate() {
|
||||||
|
if let Some(existing) = existing.get(index) {
|
||||||
|
result.push(merge_type_arguments(existing.clone(), argument.clone()));
|
||||||
|
} else {
|
||||||
|
result.push(argument.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use full_moon::tokenizer::Symbol;
|
||||||
use serde_yaml::{Mapping as YamlMapping, Sequence as YamlSequence, Value as YamlValue};
|
use serde_yaml::{Mapping as YamlMapping, Sequence as YamlSequence, Value as YamlValue};
|
||||||
|
|
||||||
use crate::gen::definitions::DefinitionsItemTag;
|
use crate::gen::definitions::DefinitionsItemTag;
|
||||||
|
|
||||||
use super::definitions::{DefinitionsItem, DefinitionsItemKind, DefinitionsTree, PIPE_SEPARATOR};
|
use super::definitions::{DefinitionsItem, DefinitionsItemKind, DefinitionsTree};
|
||||||
|
|
||||||
pub fn generate_from_type_definitions(contents: &str) -> Result<String> {
|
pub fn generate_from_type_definitions(contents: &str) -> Result<String> {
|
||||||
let tree = DefinitionsTree::from_type_definitions(contents)?;
|
let tree = DefinitionsTree::from_type_definitions(contents)?;
|
||||||
|
@ -141,17 +142,26 @@ fn doc_item_to_selene_yaml_mapping(item: &DefinitionsItem) -> Result<YamlMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
fn simplify_type_str_into_primitives(type_str: &str) -> String {
|
fn simplify_type_str_into_primitives(type_str: &str) -> String {
|
||||||
|
let separator = format!(" {} ", Symbol::Pipe);
|
||||||
|
// Simplify type strings even further into ones that selene can understand,
|
||||||
|
// turning types such as `{ bool }` or `"string-literal"` into `bool[]` and `string`
|
||||||
let mut primitives = Vec::new();
|
let mut primitives = Vec::new();
|
||||||
for type_inner in type_str.split(PIPE_SEPARATOR) {
|
for type_inner in type_str.split(&separator) {
|
||||||
if type_inner.starts_with('{') && type_inner.ends_with('}') {
|
if type_inner.starts_with('{') && type_inner.ends_with('}') {
|
||||||
primitives.push("table");
|
primitives.push(format!(
|
||||||
|
"{}[]",
|
||||||
|
type_inner
|
||||||
|
.trim_start_matches('{')
|
||||||
|
.trim_end_matches('}')
|
||||||
|
.trim()
|
||||||
|
));
|
||||||
} else if type_inner.starts_with('"') && type_inner.ends_with('"') {
|
} else if type_inner.starts_with('"') && type_inner.ends_with('"') {
|
||||||
primitives.push("string");
|
primitives.push("string".to_string());
|
||||||
} else {
|
} else {
|
||||||
primitives.push(type_inner);
|
primitives.push(type_inner.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
primitives.sort_unstable();
|
primitives.sort_unstable();
|
||||||
primitives.dedup();
|
primitives.dedup();
|
||||||
primitives.join(PIPE_SEPARATOR)
|
primitives.join(&separator)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue