diff --git a/Cargo.lock b/Cargo.lock index ecdc9319..81fcb536 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,24 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "argh" version = "0.1.12" @@ -312,6 +330,7 @@ dependencies = [ "match_deref", "ordered-float", "path-absolutize", + "regress", "serde", "simple-json-parser", "source-map", @@ -449,6 +468,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + [[package]] name = "inotify" version = "0.9.6" @@ -576,6 +605,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + [[package]] name = "mio" version = "0.8.11" @@ -768,6 +803,16 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regress" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16fe0a24af5daaae947294213d2fd2646fbf5e1fbacc1d4ba3e84b2393854842" +dependencies = [ + "hashbrown", + "memchr", +] + [[package]] name = "rgb" version = "0.8.37" @@ -1059,6 +1104,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -1347,3 +1398,23 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] diff --git a/checker/Cargo.toml b/checker/Cargo.toml index 6800f69d..d454c225 100644 --- a/checker/Cargo.toml +++ b/checker/Cargo.toml @@ -39,6 +39,7 @@ path-absolutize = { version = "3.0", features = ["use_unix_paths_on_wasm"] } either = "1.6" levenshtein = "1" ordered-float = "4.2" +regress = { version = "0.10.0", features = [] } serde = { version = "1.0", features = ["derive"], optional = true } simple-json-parser = "0.0.2" diff --git a/checker/definitions/internal.ts.d.bin b/checker/definitions/internal.ts.d.bin index 70374b16..1460481e 100644 Binary files a/checker/definitions/internal.ts.d.bin and b/checker/definitions/internal.ts.d.bin differ diff --git a/checker/definitions/overrides.d.ts b/checker/definitions/overrides.d.ts index 41c584eb..45ea44d4 100644 --- a/checker/definitions/overrides.d.ts +++ b/checker/definitions/overrides.d.ts @@ -174,6 +174,37 @@ declare class String { declare class Promise { } +declare class RegExp { + @Constant("regexp:constructor") + constructor(pattern: string, flags?: string); + + @Constant("regexp:exec") + exec(input: string): RegExpExecArray | null; +} + +// es5 +interface RegExpExecArray extends Array { + /** + * The index of the search at which the result was found. + */ + index: number; + /** + * A copy of the search string. + */ + input: string; + /** + * The first match. This will always be present because `null` will be returned if there are no matches. + */ + 0: string; +} + +// es2018 +interface RegExpExecArray { + groups?: { + [key: string]: string; + }; +} + type ResponseBody = string; declare class Response { diff --git a/checker/specification/specification.md b/checker/specification/specification.md index 35251578..faaf8845 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -344,8 +344,8 @@ declare type U = { a: 2 } & { b: 3 } declare let x: U; x.b satisfies 3; -({ a: 2, b: 3 } satisfies U); -({ b: 3 } satisfies U); +({ a: 2, b: 3 } satisfies U); +({ b: 3 } satisfies U); ``` - Expected U, found { b: 3 } @@ -2433,7 +2433,7 @@ interface X { - Expected 4, found 5 - Type { a: 3 } is not assignable to type X -#### RegExp +#### `RegExp` constructor > RegExp = Regular expression > In the future, their definition could be considered and evaluated at runtime @@ -2444,6 +2444,55 @@ const regexp = /hi/ satisfies string; - Expected string, found /hi/ +#### Invalid regular expressions + +```ts +const regexp1 = /(?a2)/; +const regexp2 = new RegExp("?a2"); +``` + +- Invalid regular expression: Invalid token at named capture group identifier +- Invalid regular expression: Invalid atom character + +#### Constant `RegExp.exec` + +```ts +const regexp = /hi/; +const match = regexp.exec("hi"); +match satisfies number; +match.index satisfies string; +match.input satisfies boolean; +``` + +- Expected number, found ["hi"] +- Expected string, found 0 +- Expected boolean, found "hi" + +#### Constant `RegExp.exec` groups + +```ts +const regexp = /Hi (?.*)/; +const match = regexp.exec("Hi Ben"); +match.input satisfies number; +match.groups satisfies string; +``` + +- Expected number, found "Hi Ben" +- Expected string, found { name: "Ben" } + +#### Constant `RegExp.exec` groups greedy + +```ts +const regexp = /.*(?[a-z]+)(?[0-9]+)/; +const match = regexp.exec("ez as abc123"); +match.input satisfies number; +match.groups.x satisfies "c"; +match.groups.y satisfies boolean; +``` + +- Expected number, found "ez as abc123" +- Expected boolean, found "123" + #### Null and undefined ```ts @@ -3921,7 +3970,7 @@ function booleanNarrow(param: boolean) { function operatorNarrows(thing: string | null) { (thing ?? "something") satisfies string; (thing || "something") satisfies number; - + const result = thing === "hi" && (thing satisfies boolean); } ``` @@ -3936,7 +3985,7 @@ function logicNarrow(thing: any, other: any) { if (typeof thing === "string" && other === 4) { ({ thing, other }) satisfies string; } - + if (typeof thing === "string" || typeof thing === "number") { thing satisfies null; } @@ -4258,10 +4307,10 @@ proxy1.a satisfies string; ```ts let lastSet: string = ""; -const proxy1 = new Proxy({ a: 2 }, { +const proxy1 = new Proxy({ a: 2 }, { set(target: { a: number }, prop: string, value: number, receiver) { lastSet = prop; - } + } }); proxy1.a = 6; diff --git a/checker/src/context/environment.rs b/checker/src/context/environment.rs index ccdff6fd..9c5754d2 100644 --- a/checker/src/context/environment.rs +++ b/checker/src/context/environment.rs @@ -1367,6 +1367,9 @@ impl<'a> Environment<'a> { "Boolean" => { return Ok(TypeId::BOOLEAN_TYPE); } + "RegExp" => { + return Ok(TypeId::REGEXP_TYPE); + } "Function" => { return Ok(TypeId::FUNCTION_TYPE); } diff --git a/checker/src/context/root.rs b/checker/src/context/root.rs index 4c6bf909..81eb951d 100644 --- a/checker/src/context/root.rs +++ b/checker/src/context/root.rs @@ -64,6 +64,7 @@ impl RootContext { ("void".to_owned(), TypeId::VOID_TYPE), ("Array".to_owned(), TypeId::ARRAY_TYPE), ("Promise".to_owned(), TypeId::PROMISE_TYPE), + ("RegExp".to_owned(), TypeId::REGEXP_TYPE), ("ImportMeta".to_owned(), TypeId::IMPORT_META), ("Function".to_owned(), TypeId::FUNCTION_TYPE), ("object".to_owned(), TypeId::OBJECT_TYPE), diff --git a/checker/src/diagnostics.rs b/checker/src/diagnostics.rs index 41b886f2..89d98a0a 100644 --- a/checker/src/diagnostics.rs +++ b/checker/src/diagnostics.rs @@ -58,6 +58,11 @@ pub struct TDZ { pub position: SpanWithSource, } +pub struct InvalidRegexp { + pub error: String, + pub position: SpanWithSource, +} + pub struct NotInLoopOrCouldNotFindLabel { pub label: Label, pub position: SpanWithSource, @@ -452,6 +457,7 @@ pub(crate) enum TypeCheckError<'a> { position: SpanWithSource, }, CannotDeleteProperty(CannotDeleteFromError), + InvalidRegexp(InvalidRegexp), } #[allow(clippy::useless_format)] @@ -827,7 +833,7 @@ impl From> for Diagnostic { } => Diagnostic::Position { reason: match property { PropertyKeyRepresentation::Type(ty) => format!("Cannot write to property of type {ty}"), - PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to property '{property}'") + PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to property '{property}'") }, position, kind, @@ -850,7 +856,7 @@ impl From> for Diagnostic { } => Diagnostic::Position { reason: match property { PropertyKeyRepresentation::Type(ty) => format!("Cannot write to property of type {ty} as it is a getter"), - PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to property '{property}' as it is a getter") + PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to property '{property}' as it is a getter") }, position, kind, @@ -861,12 +867,17 @@ impl From> for Diagnostic { } => Diagnostic::Position { reason: match property { PropertyKeyRepresentation::Type(ty) => format!("Cannot write to non-existent property of type {ty}"), - PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to non-existent property '{property}'") + PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to non-existent property '{property}'") }, position, kind, } - } + }, + TypeCheckError::InvalidRegexp(InvalidRegexp { error, position }) => Diagnostic::Position { + reason: format!("Invalid regular expression: {error}"), + position, + kind, + }, } } } @@ -1176,5 +1187,10 @@ fn function_calling_error_diagnostic( kind, } } + FunctionCallingError::InvalidRegexp(InvalidRegexp { error, position }) => Diagnostic::Position { + reason: format!("Invalid regular expression: {error}"), + position, + kind, + } } } diff --git a/checker/src/features/constant_functions.rs b/checker/src/features/constant_functions.rs index f8411ab9..6f6f3ac8 100644 --- a/checker/src/features/constant_functions.rs +++ b/checker/src/features/constant_functions.rs @@ -6,7 +6,9 @@ use crate::{ events::printing::debug_effects, features::objects::{ObjectBuilder, Proxy}, types::{ - calling::{Callable, FunctionCallingError, SynthesisedArgument, ThisValue}, + calling::{ + Callable, CallingDiagnostics, FunctionCallingError, SynthesisedArgument, ThisValue, + }, logical::{Logical, LogicalOrValid}, printing::print_type, properties::{AccessMode, Descriptor, PropertyKey, Publicity}, @@ -49,7 +51,7 @@ pub(crate) fn call_constant_function( // TODO `mut` for satisfies which needs checking. Also needed for freeze etc environment: &mut Environment, call_site: SpanWithSource, - diagnostics: &mut crate::types::calling::CallingDiagnostics, + diagnostics: &mut CallingDiagnostics, ) -> Result { // crate::utilities::notify!("Calling constant function {} with {:?}", name, arguments); // TODO as parameter @@ -112,7 +114,9 @@ pub(crate) fn call_constant_function( "toUpperCase" => Constant::String(s.to_uppercase()), "toLowerCase" => Constant::String(s.to_lowercase()), "string_length" => Constant::Number( - (s.len() as f64).try_into().map_err(|_| ConstantFunctionError::BadCall)?, + (s.encode_utf16().count() as f64) + .try_into() + .map_err(|_| ConstantFunctionError::BadCall)?, ), _ => unreachable!(), }); @@ -571,6 +575,55 @@ pub(crate) fn call_constant_function( crate::utilities::notify!("TODO JSON:stringify"); Err(ConstantFunctionError::BadCall) } + "regexp:constructor" => { + let pattern = types + .get_type_by_id(arguments.first().unwrap().non_spread_type().expect("pattern")); + let flags = + arguments.get(1).map(|a| types.get_type_by_id(a.non_spread_type().expect("flags"))); + + let Type::Constant(Constant::String(pattern)) = pattern else { + return Err(ConstantFunctionError::BadCall); + }; + let flags = match flags { + Some(flags) => { + let Type::Constant(Constant::String(flags)) = flags else { + return Err(ConstantFunctionError::BadCall); + }; + + Some(flags.clone()) + } + None => None, + }; + + let regexp = types.new_regexp(&pattern.clone(), &flags, &call_site.without_source()); + + match regexp { + Ok(regex) => Ok(ConstantOutput::Value(regex)), + Err(error) => Err(ConstantFunctionError::FunctionCallingError( + FunctionCallingError::InvalidRegexp(crate::diagnostics::InvalidRegexp { + error, + position: call_site, + }), + )), + } + } + "regexp:exec" => { + let this = this_argument.get_passed().map(|t| types.get_type_by_id(t)); + + if let Some(Type::SpecialObject(SpecialObject::RegularExpression(regexp))) = this { + let pattern_type_id = + arguments.first().unwrap().non_spread_type().expect("pattern"); + + Ok(ConstantOutput::Value(regexp.clone().exec( + pattern_type_id, + types, + environment, + call_site, + ))) + } else { + Err(ConstantFunctionError::BadCall) + } + } // "satisfies" => { // let ty = arguments // .first() diff --git a/checker/src/features/mod.rs b/checker/src/features/mod.rs index f998989f..aac3199b 100644 --- a/checker/src/features/mod.rs +++ b/checker/src/features/mod.rs @@ -17,6 +17,7 @@ pub mod modules; pub mod narrowing; pub mod objects; pub mod operations; +pub mod regexp; pub mod template_literal; pub mod variables; diff --git a/checker/src/features/objects.rs b/checker/src/features/objects.rs index 422043c1..dd90301d 100644 --- a/checker/src/features/objects.rs +++ b/checker/src/features/objects.rs @@ -61,10 +61,7 @@ pub enum SpecialObject { /// Needs overrides for calling, getting etc Proxy(Proxy), /// Not a [Constant] as `typeof /hi/ === "object"` and it has state - RegularExpression { - content: TypeId, - // groups: Option, - }, + RegularExpression(super::regexp::RegExp), /// This cannot be a regular object because of is because of let mutations Import(super::modules::Exported), /// Yeah here. Also for classes diff --git a/checker/src/features/regexp.rs b/checker/src/features/regexp.rs new file mode 100644 index 00000000..7797cef9 --- /dev/null +++ b/checker/src/features/regexp.rs @@ -0,0 +1,326 @@ +use regress::{backends, Flags, Regex}; +use source_map::{SourceId, SpanWithSource}; + +use super::objects::ObjectBuilder; +use crate::{ + types::{ + properties::{PropertyKey, PropertyValue, Publicity}, + TypeStore, + }, + BinarySerializable, Constant, Environment, Type, TypeId, +}; + +#[derive(Debug, Clone)] +pub struct RegExp { + source: String, + re: Regex, + groups: u32, + named_group_indices: crate::Map, + flags_unsupported: bool, + used: bool, +} + +impl RegExp { + pub fn new(pattern: &str, flag_options: Option<&str>) -> Result { + let source = if let Some(flag_options) = flag_options { + format!("/{pattern}/{flag_options}") + } else { + format!("/{pattern}/") + }; + + let mut flags = Flags::default(); + let mut flags_unsupported = false; + + if let Some(flag_options) = flag_options { + for flag in flag_options.chars() { + #[allow(clippy::match_same_arms)] + match flag { + 'd' => flags_unsupported = true, // indices for substring matches are not supported + 'g' => flags_unsupported = true, // stateful regex is not supported + 'i' => flags.icase = true, + 'm' => flags.multiline = true, + 's' => flags.dot_all = true, + 'u' => flags.unicode = true, + 'v' => flags.unicode_sets = true, + 'y' => flags_unsupported = true, // sticky search is not supported + _ => panic!("Unknown flag: {flag:?}"), + } + } + } + + let compiled_regex = { + let mut ire = backends::try_parse(pattern.chars().map(u32::from), flags) + .map_err(|err| err.text)?; + if !flags.no_opt { + backends::optimize(&mut ire); + } + + backends::emit(&ire) + }; + + // crate::utilities::notify!("{:?}", compiled_regex); + + // let insns = compiled_regex.insns; + // let brackets = compiled_regex.brackets; + // let start_pred = compiled_regex.start_pred; + // let loops = compiled_regex.loops; + let groups = compiled_regex.groups + 1; + let named_group_indices = + compiled_regex.named_group_indices.iter().map(|(l, r)| (l.clone(), *r)).collect(); + // let flags = compiled_regex.flags; + + let re = Regex::from(compiled_regex); + + Ok(Self { source, re, groups, named_group_indices, flags_unsupported, used: false }) + } + + #[must_use] + pub fn source(&self) -> &str { + &self.source + } + + #[must_use] + pub fn used(&self) -> bool { + self.used + } + + pub(crate) fn exec( + &self, + pattern_type_id: TypeId, + types: &mut TypeStore, + environment: &mut Environment, + call_site: SpanWithSource, + ) -> TypeId { + let pattern_type = types.get_type_by_id(pattern_type_id); + + match (self.flags_unsupported, pattern_type) { + (false, Type::Constant(Constant::String(pattern))) => { + // Needed to not mutually borrow mutable types + let pattern = pattern.clone(); + self.exec_constant(&pattern, pattern_type_id, types, environment, call_site) + } + _ => self.exec_variable(types, environment, call_site), + } + } + + pub(crate) fn exec_constant( + &self, + pattern: &str, + pattern_type_id: TypeId, + types: &mut TypeStore, + environment: &mut Environment, + call_site: SpanWithSource, + ) -> TypeId { + let mut object = + ObjectBuilder::new(Some(TypeId::ARRAY_TYPE), types, call_site, &mut environment.info); + + object.append( + Publicity::Public, + PropertyKey::String("input".into()), + PropertyValue::Value(pattern_type_id), + call_site, + &mut environment.info, + ); + + match self.re.find(pattern) { + Some(match_) => { + { + let index = types.new_constant_type(Constant::Number( + (match_.start() as f64).try_into().unwrap(), + )); + object.append( + Publicity::Public, + PropertyKey::String("index".into()), + PropertyValue::Value(index), + call_site, + &mut environment.info, + ); + } + + for (idx, group) in match_.groups().enumerate() { + let key = PropertyKey::from_usize(idx); + let value = match group { + Some(range) => { + types.new_constant_type(Constant::String(pattern[range].to_string())) + } + None => todo!(), + }; + + object.append( + Publicity::Public, + key, + PropertyValue::Value(value), + call_site, + &mut environment.info, + ); + } + + { + let named_groups = { + let mut named_groups_object = ObjectBuilder::new( + Some(TypeId::NULL_TYPE), + types, + call_site, + &mut environment.info, + ); + + for (name, group) in match_.named_groups() { + let key = PropertyKey::String(name.to_string().into()); + let value = match group { + Some(range) => types.new_constant_type(Constant::String( + pattern[range].to_string(), + )), + None => todo!(), + }; + + named_groups_object.append( + Publicity::Public, + key, + PropertyValue::Value(value), + call_site, + &mut environment.info, + ); + } + + named_groups_object.build_object() + }; + + object.append( + Publicity::Public, + PropertyKey::String("groups".into()), + PropertyValue::Value(named_groups), + call_site, + &mut environment.info, + ); + } + + { + let length = types.new_constant_type(Constant::Number( + f64::from(self.groups).try_into().unwrap(), + )); + + object.append( + Publicity::Public, + PropertyKey::String("length".into()), + PropertyValue::Value(length), + call_site, + &mut environment.info, + ); + } + + object.build_object() + } + None => TypeId::NULL_TYPE, + } + } + + pub(crate) fn exec_variable( + &self, + types: &mut TypeStore, + environment: &mut Environment, + call_site: SpanWithSource, + ) -> TypeId { + let mut object = + ObjectBuilder::new(Some(TypeId::ARRAY_TYPE), types, call_site, &mut environment.info); + + object.append( + Publicity::Public, + PropertyKey::String("input".into()), + PropertyValue::Value(TypeId::STRING_TYPE), + call_site, + &mut environment.info, + ); + + { + object.append( + Publicity::Public, + PropertyKey::String("index".into()), + PropertyValue::Value(TypeId::NUMBER_TYPE), + call_site, + &mut environment.info, + ); + } + + for idx in 0..self.groups { + let key = PropertyKey::from_usize(idx as usize); + + object.append( + Publicity::Public, + key, + PropertyValue::Value(TypeId::STRING_TYPE), + call_site, + &mut environment.info, + ); + } + + { + let named_groups = { + let mut named_groups_object = ObjectBuilder::new( + Some(TypeId::NULL_TYPE), + types, + call_site, + &mut environment.info, + ); + + for name in self.named_group_indices.keys() { + let key = PropertyKey::String(name.to_string().into()); + + named_groups_object.append( + Publicity::Public, + key, + PropertyValue::Value(TypeId::STRING_TYPE), + call_site, + &mut environment.info, + ); + } + + named_groups_object.build_object() + }; + + object.append( + Publicity::Public, + PropertyKey::String("groups".into()), + PropertyValue::Value(named_groups), + call_site, + &mut environment.info, + ); + } + + { + let length = types + .new_constant_type(Constant::Number(f64::from(self.groups).try_into().unwrap())); + + object.append( + Publicity::Public, + PropertyKey::String("length".into()), + PropertyValue::Value(length), + call_site, + &mut environment.info, + ); + } + + types.new_or_type(object.build_object(), TypeId::NULL_TYPE) + } +} + +impl std::fmt::Display for RegExp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.source) + } +} + +// TODO: Optimize +impl BinarySerializable for RegExp { + fn serialize(self, buf: &mut Vec) { + self.source.serialize(buf); + } + + fn deserialize>(iter: &mut I, source_id: SourceId) -> Self { + let source = String::deserialize(iter, source_id); + + let (pattern, flags) = source[1..].rsplit_once('/').unwrap(); + let flags = if flags.is_empty() { None } else { Some(flags) }; + + Self::new(pattern, flags).unwrap() + } +} diff --git a/checker/src/synthesis/expressions.rs b/checker/src/synthesis/expressions.rs index 8268c85b..619017f2 100644 --- a/checker/src/synthesis/expressions.rs +++ b/checker/src/synthesis/expressions.rs @@ -93,6 +93,25 @@ pub(super) fn synthesise_expression( Expression::StringLiteral(value, ..) => { return checking_data.types.new_constant_type(Constant::String(value.clone())) } + Expression::RegexLiteral { pattern, flags, position } => { + let regexp = checking_data.types.new_regexp(pattern, flags, position); + + match regexp { + Ok(regexp) => Instance::RValue(regexp), + Err(error) => { + checking_data.diagnostics_container.add_error( + crate::diagnostics::TypeCheckError::InvalidRegexp( + crate::diagnostics::InvalidRegexp { + error, + position: position.with_source(environment.get_source()), + }, + ), + ); + + return TypeId::ERROR_TYPE; + } + } + } Expression::NumberLiteral(value, ..) => { let not_nan = if let Ok(v) = f64::try_from(value.clone()) { v.try_into().unwrap() @@ -836,12 +855,6 @@ pub(super) fn synthesise_expression( Expression::JSXRoot(jsx_root) => { Instance::RValue(synthesise_jsx_root(jsx_root, environment, checking_data)) } - Expression::RegexLiteral { pattern, flags: _, position: _ } => { - let content = checking_data.types.new_constant_type(Constant::String(pattern.clone())); - Instance::RValue(checking_data.types.register_type(crate::Type::SpecialObject( - crate::types::SpecialObject::RegularExpression { content }, - ))) - } Expression::Comment { on, .. } => { return synthesise_expression(on, environment, checking_data, expecting); } diff --git a/checker/src/types/calling.rs b/checker/src/types/calling.rs index e3b93222..c7ae9e68 100644 --- a/checker/src/types/calling.rs +++ b/checker/src/types/calling.rs @@ -1012,6 +1012,7 @@ pub enum FunctionCallingError { /// Should be set by parent call_site: SpanWithSource, }, + InvalidRegexp(crate::diagnostics::InvalidRegexp), /// For #18 SetPropertyConstraint { property_type: TypeStringRepresentation, diff --git a/checker/src/types/printing.rs b/checker/src/types/printing.rs index 91307e3d..0490fae6 100644 --- a/checker/src/types/printing.rs +++ b/checker/src/types/printing.rs @@ -632,7 +632,9 @@ pub fn print_type_into_buf( } } } else { - if let Some(prototype) = prototype { + if let Some(prototype) = + prototype.filter(|prototype| !matches!(*prototype, TypeId::NULL_TYPE)) + { // crate::utilities::notify!("P during print {:?}", prototype); buf.push('['); print_type_into_buf(prototype, buf, cycles, args, types, info, debug); @@ -799,15 +801,8 @@ pub fn print_type_into_buf( } buf.push_str(" }"); } - SpecialObject::RegularExpression { content, .. } => { - // TODO flags - if let Type::Constant(crate::Constant::String(name)) = - types.get_type_by_id(*content) - { - write!(buf, "/{name}/").unwrap(); - } else { - buf.push_str("RegExp"); - } + SpecialObject::RegularExpression(exp) => { + buf.push_str(exp.source()); } SpecialObject::Function(..) => unreachable!(), }, diff --git a/checker/src/types/store.rs b/checker/src/types/store.rs index 07662858..a72698fc 100644 --- a/checker/src/types/store.rs +++ b/checker/src/types/store.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; -use crate::{types::intrinsics::Intrinsic, Constant, Map as SmallMap}; -use source_map::{Nullable, SpanWithSource}; +use crate::{features::regexp::RegExp, types::intrinsics::Intrinsic, Constant, Map as SmallMap}; +use source_map::{Nullable, Span, SpanWithSource}; use crate::{ features::{functions::ClosureId, objects::SpecialObject}, @@ -491,6 +491,18 @@ impl TypeStore { } } + pub fn new_regexp( + &mut self, + pattern: &str, + flags: &Option, + _position: &Span, + ) -> Result { + let regexp = RegExp::new(pattern, flags.as_ref().map(String::as_str))?; + let ty = Type::SpecialObject(SpecialObject::RegularExpression(regexp)); + + Ok(self.register_type(ty)) + } + pub fn new_function_parameter(&mut self, parameter_constraint: TypeId) -> TypeId { // TODO this has problems if there are two generic types. Aka `(a: T, b: T) -> T`. Although I have // no idea why this is possible so should be fine? diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs index 3fa83b11..f845791e 100644 --- a/checker/src/types/subtyping.rs +++ b/checker/src/types/subtyping.rs @@ -1476,7 +1476,11 @@ pub(crate) fn type_is_subtype_with_generics( types, ) } - Type::AliasTo { .. } | Type::Interface { .. } => { + Type::FunctionReference(_) + | Type::SpecialObject(_) + | Type::Class { .. } + | Type::AliasTo { .. } + | Type::Interface { .. } => { crate::utilities::notify!("lhs={:?} rhs={:?}", left_ty, right_ty); // TODO SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) @@ -1519,9 +1523,6 @@ pub(crate) fn type_is_subtype_with_generics( // ) // } } - Type::FunctionReference(_) => todo!(), - Type::SpecialObject(_) => todo!(), - Type::Class { .. } => todo!(), } } Type::SpecialObject(SpecialObject::Null) => { diff --git a/checker/src/utilities/serialization.rs b/checker/src/utilities/serialization.rs index 654727b2..60d6e8d7 100644 --- a/checker/src/utilities/serialization.rs +++ b/checker/src/utilities/serialization.rs @@ -198,6 +198,16 @@ impl BinarySerializable for SourceId { } } +impl BinarySerializable for u16 { + fn serialize(self, buf: &mut Vec) { + buf.extend_from_slice(&self.to_le_bytes()); + } + + fn deserialize>(iter: &mut I, _source: SourceId) -> Self { + u16::from_le_bytes([iter.next().unwrap(), iter.next().unwrap()]) + } +} + impl BinarySerializable for u32 { fn serialize(self, buf: &mut Vec) { buf.extend_from_slice(&self.to_le_bytes());