ezno_checker/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(deprecated, clippy::new_without_default, clippy::too_many_lines, clippy::result_unit_err)]
3#![warn(clippy::must_use_candidate)]
4// #![allow(unused)]
5
6pub mod context;
7pub mod diagnostics;
8pub mod events;
9pub mod features;
10mod options;
11mod type_mappings;
12pub mod types;
13pub mod utilities;
14
15#[cfg(feature = "ezno-parser")]
16pub mod synthesis;
17
18use std::{
19	collections::{HashMap, HashSet},
20	path::{Path, PathBuf},
21	time::Duration,
22};
23
24use context::{
25	information::{LocalInformation, ModuleInformation},
26	Names,
27};
28
29use diagnostics::{TypeCheckError, TypeCheckWarning};
30pub(crate) use utilities::{map::Map, range_map::RangeMap, serialization::BinarySerializable};
31
32use features::{
33	functions::SynthesisableFunction, modules::CouldNotOpenFile, modules::SynthesisedModule,
34};
35
36pub use context::{Environment, GeneralContext, RootContext, Scope, VariableRegisterArguments};
37pub use diagnostics::{Diagnostic, DiagnosticKind, DiagnosticsContainer};
38pub use options::TypeCheckOptions;
39pub use types::{
40	calling::call_type_handle_errors, generics::GenericTypeParameters, properties::PropertyValue,
41	subtyping, Constant, Type, TypeId, TypeStore,
42};
43
44pub use source_map::{self, SourceId, Span};
45use source_map::{FileSystem, MapFileStore, Nullable, SpanWithSource, WithPathMap};
46pub use type_mappings::*;
47
48pub const INTERNAL_DEFINITION_FILE_PATH: &str = "internal.ts.d.bin";
49pub const INTERNAL_DEFINITION_FILE: &[u8] = include_bytes!("../definitions/internal.ts.d.bin");
50
51pub trait ReadFromFS {
52	/// Returns `Vec<u8>` as this callback can return binary file
53	/// TODO this shouldn't take `&self`. Should be just `T::read_file`, doesn't need any data
54	fn read_file(&self, path: &std::path::Path) -> Option<Vec<u8>>;
55}
56
57impl<T, U> ReadFromFS for T
58where
59	T: Fn(&std::path::Path) -> Option<U>,
60	U: Into<Vec<u8>>,
61{
62	fn read_file(&self, path: &std::path::Path) -> Option<Vec<u8>> {
63		(self)(path).map(Into::into)
64	}
65}
66
67use levenshtein::levenshtein;
68
69pub trait ASTImplementation: Sized {
70	type ParseOptions;
71	/// Custom allocator etc
72	type ParserRequirements;
73
74	type ParseError: Into<Diagnostic>;
75
76	type Module<'a>;
77	/// TODO temp item. Some modules can have references
78	type OwnedModule;
79
80	type DefinitionFile<'a>;
81
82	type TypeAnnotation<'a>;
83	type TypeParameter<'a>;
84	type Expression<'a>;
85	/// List of statements and declarations
86	type Block<'a>;
87	type MultipleExpression<'a>;
88	type ForStatementInitiliser<'a>;
89
90	/// Used in `for of`, `for in` and function parameters
91	type VariableField<'a>;
92
93	type ClassMethod<'a>: SynthesisableFunction<Self>;
94
95	fn module_from_string(
96		source_id: SourceId,
97		string: String,
98		options: Self::ParseOptions,
99		parser_requirements: &mut Self::ParserRequirements,
100	) -> Result<Self::Module<'static>, Self::ParseError>;
101
102	fn definition_module_from_string(
103		source_id: SourceId,
104		string: String,
105		parser_requirements: &mut Self::ParserRequirements,
106	) -> Result<Self::DefinitionFile<'static>, Self::ParseError>;
107
108	#[allow(clippy::needless_lifetimes)]
109	fn synthesise_module<'a, T: crate::ReadFromFS>(
110		module: &Self::Module<'a>,
111		source_id: SourceId,
112		module_context: &mut Environment,
113		checking_data: &mut crate::CheckingData<T, Self>,
114	);
115
116	#[allow(clippy::needless_lifetimes)]
117	fn synthesise_definition_module<'a, T: crate::ReadFromFS>(
118		module: &Self::DefinitionFile<'a>,
119		source: SourceId,
120		root: &RootContext,
121		checking_data: &mut CheckingData<T, Self>,
122	) -> (Names, LocalInformation);
123
124	/// Expected is used for eagerly setting function parameters
125	fn synthesise_expression<T: crate::ReadFromFS>(
126		expression: &Self::Expression<'_>,
127		expected_type: TypeId,
128		environment: &mut Environment,
129		checking_data: &mut crate::CheckingData<T, Self>,
130	) -> TypeId;
131
132	/// Expected is used for eagerly setting function parameters
133	fn synthesise_multiple_expression<'a, T: crate::ReadFromFS>(
134		expression: &'a Self::MultipleExpression<'a>,
135		expected_type: TypeId,
136		environment: &mut Environment,
137		checking_data: &mut crate::CheckingData<T, Self>,
138	) -> TypeId;
139
140	fn synthesise_type_parameter_extends<T: crate::ReadFromFS>(
141		parameter: &Self::TypeParameter<'_>,
142		environment: &mut Environment,
143		checking_data: &mut crate::CheckingData<T, Self>,
144	) -> TypeId;
145
146	fn synthesise_type_annotation<'a, T: crate::ReadFromFS>(
147		annotation: &'a Self::TypeAnnotation<'a>,
148		environment: &mut Environment,
149		checking_data: &mut crate::CheckingData<T, Self>,
150	) -> TypeId;
151
152	fn synthesise_block<'a, T: crate::ReadFromFS>(
153		block: &'a Self::Block<'a>,
154		environment: &mut Environment,
155		checking_data: &mut crate::CheckingData<T, Self>,
156	);
157
158	/// Don't need to return anything. All information recorded via changed to `environment`
159	fn synthesise_for_loop_initialiser<'a, T: crate::ReadFromFS>(
160		for_loop_initialiser: &'a Self::ForStatementInitiliser<'a>,
161		environment: &mut Environment,
162		checking_data: &mut crate::CheckingData<T, Self>,
163	);
164
165	fn expression_position<'a>(expression: &'a Self::Expression<'a>) -> Span;
166
167	fn multiple_expression_position<'a>(expression: &'a Self::MultipleExpression<'a>) -> Span;
168
169	fn type_parameter_name<'a>(parameter: &'a Self::TypeParameter<'a>) -> &'a str;
170
171	fn type_annotation_position<'a>(annotation: &'a Self::TypeAnnotation<'a>) -> Span;
172
173	fn parameter_constrained<'a>(parameter: &'a Self::TypeParameter<'a>) -> bool;
174
175	#[allow(clippy::fn_params_excessive_bools)]
176	fn parse_options(
177		is_js: bool,
178		extra_syntax: bool,
179		parse_comments: bool,
180		lsp_mode: bool,
181	) -> Self::ParseOptions;
182
183	fn owned_module_from_module(m: Self::Module<'static>) -> Self::OwnedModule;
184
185	/// For `for in` and `for of loops`
186	fn declare_and_assign_to_fields<'a, T: crate::ReadFromFS>(
187		field: &'a Self::VariableField<'a>,
188		environment: &mut Environment,
189		checking_data: &mut crate::CheckingData<T, Self>,
190		arguments: VariableRegisterArguments,
191	);
192}
193
194/// Contains all the modules and mappings for import statements
195///
196/// TODO could files and `synthesised_modules` be merged? (with a change to the source map crate)
197pub struct ModuleData<'a, FileReader, AST: ASTImplementation> {
198	pub(crate) file_reader: &'a FileReader,
199	pub(crate) parser_requirements: AST::ParserRequirements,
200	pub(crate) current_working_directory: PathBuf,
201	/// Contains the text content of files (for source maps and diagnostics)
202	pub(crate) files: MapFileStore<WithPathMap>,
203	/// To catch cyclic imports
204	pub(crate) _currently_checking_modules: HashSet<PathBuf>,
205	/// The result of checking. Includes exported variables and info
206	pub(crate) synthesised_modules: HashMap<SourceId, SynthesisedModule<AST::OwnedModule>>,
207}
208
209impl<'a, T, A> ModuleData<'a, T, A>
210where
211	T: crate::ReadFromFS,
212	A: ASTImplementation,
213{
214	pub(crate) fn new(
215		file_resolver: &'a T,
216		current_working_directory: PathBuf,
217		files: Option<MapFileStore<WithPathMap>>,
218		parser_requirements: A::ParserRequirements,
219	) -> Self {
220		Self {
221			files: files.unwrap_or_default(),
222			synthesised_modules: Default::default(),
223			_currently_checking_modules: Default::default(),
224			// custom_module_resolvers,
225			file_reader: file_resolver,
226			current_working_directory,
227			parser_requirements,
228		}
229	}
230
231	pub(crate) fn get_file(
232		&mut self,
233		path: &Path,
234		chronometer: Option<&mut Chronometer>,
235	) -> Option<File> {
236		// TODO only internal code should be able to do this
237		if let Some("bin") = path.extension().and_then(|s| s.to_str()) {
238			return self.file_reader.read_file(path).map(|s| File::Binary(s.clone()));
239		}
240
241		let get_source_at_path = self.files.get_source_at_path(path);
242
243		// eprintln!(
244		// 	"Found {:?} {:?} {:?}",
245		// 	get_source_at_path,
246		// 	path.display(),
247		// 	self.files.get_paths()
248		// );
249
250		if let Some(source) = get_source_at_path {
251			Some(File::Source(source, self.files.get_file_content(source)))
252		} else {
253			// Load into system
254			let current = chronometer.is_some().then(std::time::Instant::now);
255			let content = self.file_reader.read_file(path)?;
256			if let Ok(content) = String::from_utf8(content) {
257				if let Some(current) = current {
258					chronometer.unwrap().fs += current.elapsed();
259				}
260				let source_id = self.files.new_source_id(path.to_path_buf(), content);
261				Some(File::Source(source_id, self.files.get_file_content(source_id)))
262			} else {
263				eprintln!("{} is not valid Utf-8", path.display());
264				None
265			}
266		}
267	}
268}
269
270pub enum File {
271	Binary(Vec<u8>),
272	Source(SourceId, String),
273}
274
275#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, binary_serialize_derive::BinarySerializable)]
276pub struct VariableId(pub SourceId, pub u32);
277
278/// TODO split for annotations based functions
279#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, binary_serialize_derive::BinarySerializable)]
280pub struct FunctionId(pub SourceId, pub u32);
281
282#[derive(Debug)]
283pub enum Decidable<T> {
284	Known(T),
285	/// Points to poly type
286	Unknown(TypeId),
287}
288
289/// TODO
290pub enum PredicateBound<T> {
291	Always(T),
292}
293
294pub trait GenericTypeParameter {
295	fn get_name(&self) -> &str;
296}
297
298#[derive(Default)]
299pub struct Chronometer {
300	/// In binary .d.ts files
301	pub cached: Duration,
302	/// read actions
303	pub fs: Duration,
304	/// parsing. (TODO only of first file)
305	pub parse: Duration,
306	/// type checking (inc binding). TODO this includes parsing of imports
307	pub check: Duration,
308	/// parsed and type checked lines
309	pub lines: usize,
310}
311
312/// Contains logic for **checking phase** (none of the later steps)
313/// All data is global, non local to current scope
314/// TODO some of these should be mutex / ref cell
315pub struct CheckingData<'a, FSResolver, ModuleAST: ASTImplementation> {
316	// pub(crate) type_resolving_visitors: [Box<dyn TypeResolvingExpressionVisitor>],
317	// pub(crate) pre_type_visitors: FirstPassVisitors,
318	/// Type checking errors
319	pub diagnostics_container: DiagnosticsContainer,
320	/// TODO temp pub
321	pub local_type_mappings: TypeMappings,
322	/// All module information
323	pub(crate) modules: ModuleData<'a, FSResolver, ModuleAST>,
324	/// Options for checking
325	pub(crate) options: TypeCheckOptions,
326
327	// pub(crate) events: EventsStore,
328	pub types: TypeStore,
329
330	pub(crate) chronometer: Chronometer,
331
332	/// Do not repeat emitting unimplemented parts
333	unimplemented_items: HashSet<&'static str>,
334}
335
336impl<'a, T, A> CheckingData<'a, T, A>
337where
338	T: crate::ReadFromFS,
339	A: crate::ASTImplementation,
340{
341	// TODO improve on this function
342	pub fn new(
343		options: TypeCheckOptions,
344		resolver: &'a T,
345		existing_files: Option<MapFileStore<WithPathMap>>,
346		parser_requirements: A::ParserRequirements,
347	) -> Self {
348		// let custom_file_resolvers = HashMap::default();
349		let cwd = Default::default();
350		let modules = ModuleData::new(resolver, cwd, existing_files, parser_requirements);
351
352		Self {
353			options,
354			modules,
355			diagnostics_container: Default::default(),
356			local_type_mappings: Default::default(),
357			types: Default::default(),
358			unimplemented_items: Default::default(),
359			chronometer: Default::default(),
360		}
361	}
362
363	/// TODO temp, needs better place
364	pub fn raise_decidable_result_error(&mut self, span: SpanWithSource, value: bool) {
365		self.diagnostics_container.add_error(TypeCheckWarning::DeadBranch {
366			expression_span: span,
367			expression_value: value,
368		});
369	}
370
371	/// TODO temp, needs better place
372	pub fn raise_unimplemented_error(&mut self, item: &'static str, position: SpanWithSource) {
373		if self.unimplemented_items.insert(item) {
374			self.diagnostics_container
375				.add_warning(TypeCheckWarning::Unimplemented { item, position });
376		}
377	}
378
379	pub fn add_expression_mapping(&mut self, span: SpanWithSource, instance: Instance) {
380		self.local_type_mappings.expressions_to_instances.push(span, instance);
381	}
382}
383
384/// Used for transformers and other things after checking!!!!
385pub struct CheckOutput<A: crate::ASTImplementation> {
386	pub types: crate::types::TypeStore,
387	pub module_contents: MapFileStore<WithPathMap>,
388	pub modules: HashMap<SourceId, SynthesisedModule<A::OwnedModule>>,
389	pub diagnostics: crate::DiagnosticsContainer,
390	pub top_level_information: LocalInformation,
391	pub chronometer: crate::Chronometer,
392}
393
394impl<A: crate::ASTImplementation> CheckOutput<A> {
395	#[must_use]
396	pub fn get_type_at_position(&self, path: &str, pos: u32, debug: bool) -> Option<String> {
397		let source = self.module_contents.get_source_at_path(path.as_ref())?;
398		let module = &self.modules.get(&source)?;
399
400		module.get_instance_at_position(pos).map(|instance| {
401			crate::types::printing::print_type(
402				instance.get_value_on_ref(),
403				&self.types,
404				&ModuleInformation { top: &self.top_level_information, module: &module.info },
405				debug,
406			)
407		})
408	}
409
410	#[must_use]
411	pub fn get_type_at_position_with_span(
412		&self,
413		path: &str,
414		pos: u32,
415		debug: bool,
416	) -> Option<(String, SpanWithSource)> {
417		let source = self.module_contents.get_source_at_path(path.as_ref())?;
418		if let Some(module) = self.modules.get(&source) {
419			module.get_instance_at_position_with_span(pos).map(|(instance, range)| {
420				(
421					crate::types::printing::print_type(
422						instance.get_value_on_ref(),
423						&self.types,
424						&ModuleInformation {
425							top: &self.top_level_information,
426							module: &module.info,
427						},
428						debug,
429					),
430					SpanWithSource { start: range.start, end: range.end, source },
431				)
432			})
433		} else {
434			eprintln!("no module here???");
435			None
436		}
437	}
438
439	#[must_use]
440	pub fn get_module(&self, path: &str) -> Option<&A::OwnedModule> {
441		let source_id = self.module_contents.get_source_at_path(path.as_ref())?;
442		Some(&self.modules.get(&source_id).expect("no module").content)
443	}
444
445	#[must_use]
446	pub fn empty() -> Self {
447		Self {
448			types: Default::default(),
449			module_contents: Default::default(),
450			modules: Default::default(),
451			diagnostics: Default::default(),
452			top_level_information: Default::default(),
453			chronometer: Default::default(),
454		}
455	}
456}
457
458#[allow(clippy::needless_pass_by_value)]
459pub fn check_project<T: crate::ReadFromFS, A: crate::ASTImplementation>(
460	entry_points: Vec<PathBuf>,
461	type_definition_files: Vec<PathBuf>,
462	resolver: &T,
463	options: TypeCheckOptions,
464	parser_requirements: A::ParserRequirements,
465	existing_files: Option<MapFileStore<WithPathMap>>,
466) -> CheckOutput<A> {
467	let mut checking_data =
468		CheckingData::<T, A>::new(options, resolver, existing_files, parser_requirements);
469
470	let mut root = crate::context::RootContext::new_with_primitive_references();
471
472	crate::utilities::notify!("--- Reading definition files from {:?} ---", type_definition_files);
473
474	// Hide any debug messages from here
475	if !checking_data.options.debug_dts {
476		crate::utilities::pause_debug_mode();
477	}
478	add_definition_files_to_root(type_definition_files, &mut root, &mut checking_data);
479	crate::utilities::unpause_debug_mode();
480
481	if checking_data.diagnostics_container.contains_error() {
482		return CheckOutput {
483			types: checking_data.types,
484			module_contents: checking_data.modules.files,
485			modules: Default::default(),
486			diagnostics: checking_data.diagnostics_container,
487			top_level_information: Default::default(),
488			chronometer: checking_data.chronometer,
489		};
490	}
491
492	crate::utilities::notify!("--- Finished definition file ---");
493
494	for point in &entry_points {
495		// eprintln!("Trying to get {point} from {:?}", checking_data.modules.files.get_paths());
496		let current = checking_data.options.measure_time.then(std::time::Instant::now);
497
498		let entry_content = if let Some(source) =
499			checking_data.modules.files.get_source_at_path(point)
500		{
501			Some((source, checking_data.modules.files.get_file_content(source)))
502		} else if let Some(content) = checking_data.modules.file_reader.read_file(point) {
503			let content = String::from_utf8(content).expect("invalid entry point encoding");
504			let source = checking_data.modules.files.new_source_id(point.clone(), content.clone());
505			Some((source, content))
506		} else {
507			None
508		};
509		if let Some(current) = current {
510			checking_data.chronometer.fs += current.elapsed();
511		}
512
513		if let Some((source, content)) = entry_content {
514			let module = parse_source(point, source, content, &mut checking_data);
515
516			let current = checking_data.options.measure_time.then(std::time::Instant::now);
517			match module {
518				Ok(module) => {
519					let _module = root.new_module_context(source, module, &mut checking_data);
520					if let Some(current) = current {
521						checking_data.chronometer.check += current.elapsed();
522					}
523				}
524				Err(err) => {
525					checking_data.diagnostics_container.add_error(err);
526				}
527			}
528		} else {
529			checking_data.diagnostics_container.add_error(TypeCheckError::CannotOpenFile {
530				file: CouldNotOpenFile(point.clone()),
531				import_position: None,
532				possibles: checking_data
533					.modules
534					.files
535					.get_paths()
536					.keys()
537					.filter_map(|path| path.to_str())
538					.collect(),
539				partial_import_path: point.to_str().unwrap_or(""),
540			});
541			continue;
542		}
543	}
544
545	let CheckingData {
546		diagnostics_container,
547		local_type_mappings: _,
548		modules,
549		options: _,
550		types,
551		unimplemented_items: _,
552		chronometer,
553	} = checking_data;
554
555	CheckOutput {
556		types,
557		module_contents: modules.files,
558		modules: modules.synthesised_modules,
559		diagnostics: diagnostics_container,
560		top_level_information: root.info,
561		chronometer,
562	}
563}
564
565fn parse_source<T: crate::ReadFromFS, A: crate::ASTImplementation>(
566	path: &Path,
567	source: SourceId,
568	content: String,
569	checking_data: &mut CheckingData<T, A>,
570) -> Result<<A as ASTImplementation>::Module<'static>, <A as ASTImplementation>::ParseError> {
571	if checking_data.options.measure_time {
572		let code_lines =
573			content.lines().filter(|c| !(c.is_empty() || c.trim_start().starts_with('/'))).count();
574		checking_data.chronometer.lines += code_lines;
575	}
576
577	// TODO pause check timing
578	let current = checking_data.options.measure_time.then(std::time::Instant::now);
579
580	// TODO abstract using similar to import logic
581	let is_js = path.extension().and_then(|s| s.to_str()).map_or(false, |s| s.ends_with("js"));
582
583	let parse_options = A::parse_options(
584		is_js,
585		checking_data.options.extra_syntax,
586		checking_data.options.parse_comments,
587		checking_data.options.lsp_mode,
588	);
589
590	let result = A::module_from_string(
591		source,
592		content,
593		parse_options,
594		&mut checking_data.modules.parser_requirements,
595	);
596
597	if let Some(current) = current {
598		checking_data.chronometer.parse += current.elapsed();
599	}
600
601	result
602}
603
604const CACHE_MARKER: &[u8] = b"ezno-cache-file";
605
606#[derive(binary_serialize_derive::BinarySerializable)]
607pub(crate) struct Cache {
608	pub(crate) variables: HashMap<String, features::variables::VariableOrImport>,
609	pub(crate) named_types: HashMap<String, TypeId>,
610	pub(crate) info: LocalInformation,
611	pub(crate) types: TypeStore,
612	// /// Retains position information
613	// pub(crate) content: String,
614}
615
616pub(crate) fn add_definition_files_to_root<T: crate::ReadFromFS, A: crate::ASTImplementation>(
617	type_definition_files: Vec<PathBuf>,
618	root: &mut RootContext,
619	checking_data: &mut CheckingData<T, A>,
620) {
621	let length = type_definition_files.len();
622	for path in type_definition_files {
623		let chronometer =
624			checking_data.options.measure_time.then_some(&mut checking_data.chronometer);
625
626		let file = if path == PathBuf::from(crate::INTERNAL_DEFINITION_FILE_PATH) {
627			File::Binary(crate::INTERNAL_DEFINITION_FILE.to_owned())
628		} else if let Some(file) = checking_data.modules.get_file(&path, chronometer) {
629			file
630		} else {
631			checking_data.diagnostics_container.add_error(Diagnostic::Global {
632				reason: format!("could not find {}", path.display()),
633				kind: crate::DiagnosticKind::Error,
634			});
635			continue;
636		};
637
638		match file {
639			File::Binary(data) => {
640				let current = checking_data.options.measure_time.then(std::time::Instant::now);
641				deserialize_cache(length, data, checking_data, root);
642				if let Some(current) = current {
643					checking_data.chronometer.cached += current.elapsed();
644				}
645			}
646			File::Source(source_id, source) => {
647				if checking_data.options.measure_time {
648					let code_lines = source
649						.lines()
650						.filter(|c| !(c.is_empty() || c.trim_start().starts_with('/')))
651						.count();
652					checking_data.chronometer.lines += code_lines;
653				}
654
655				let current = checking_data.options.measure_time.then(std::time::Instant::now);
656				let result = A::definition_module_from_string(
657					source_id,
658					source,
659					&mut checking_data.modules.parser_requirements,
660				);
661				if let Some(current) = current {
662					checking_data.chronometer.parse += current.elapsed();
663				}
664
665				match result {
666					Ok(tdm) => {
667						let current =
668							checking_data.options.measure_time.then(std::time::Instant::now);
669
670						let (names, info) =
671							A::synthesise_definition_module(&tdm, source_id, root, checking_data);
672
673						// TODO bad. should be per file
674						if let Some(current) = current {
675							checking_data.chronometer.check += current.elapsed();
676						}
677
678						root.variables.extend(names.variables);
679						root.named_types.extend(names.named_types);
680						root.variable_names.extend(names.variable_names);
681						root.info.extend(info, None);
682					}
683					Err(err) => {
684						checking_data.diagnostics_container.add_error(err);
685						continue;
686					}
687				}
688			}
689		}
690	}
691}
692
693fn deserialize_cache<T: ReadFromFS, A: ASTImplementation>(
694	length: usize,
695	mut content: Vec<u8>,
696	checking_data: &mut CheckingData<T, A>,
697	root: &mut RootContext,
698) {
699	crate::utilities::notify!("Using cache :)");
700	assert_eq!(length, 1, "only a single cache is current supported");
701
702	let end_content =
703		content[CACHE_MARKER.len()..(CACHE_MARKER.len() + U32_BYTES as usize)].to_owned();
704
705	let at_end =
706		<u32 as BinarySerializable>::deserialize(&mut end_content.into_iter(), SourceId::NULL);
707
708	let source_id = {
709		// Get source and content which is at the end.
710		let mut drain =
711			content.drain((CACHE_MARKER.len() + U32_BYTES as usize + at_end as usize)..);
712
713		// Okay as end
714		let (_source_id, path) =
715			<(SourceId, String) as BinarySerializable>::deserialize(&mut drain, SourceId::NULL);
716
717		let get_source_at_path = checking_data.modules.files.get_source_at_path(Path::new(&path));
718
719		if let Some(source_id) = get_source_at_path {
720			eprintln!("reusing source id {source_id:?}");
721			source_id
722		} else {
723			// Collect from end
724			let source_content = String::from_utf8(drain.collect::<Vec<_>>()).unwrap();
725			checking_data.modules.files.new_source_id(path.into(), source_content)
726		}
727	};
728
729	let mut bytes = content.drain((CACHE_MARKER.len() + U32_BYTES as usize)..);
730
731	// TODO WIP
732	let Cache { variables, named_types, info, types } = Cache::deserialize(&mut bytes, source_id);
733
734	root.variables = variables;
735	root.named_types = named_types;
736	root.info = info;
737	checking_data.types = types;
738}
739
740const U32_BYTES: u32 = u32::BITS / u8::BITS;
741
742pub fn generate_cache<T: crate::ReadFromFS, A: crate::ASTImplementation>(
743	on: &Path,
744	read: &T,
745	parser_requirements: A::ParserRequirements,
746) -> Vec<u8> {
747	let mut checking_data =
748		CheckingData::<T, A>::new(Default::default(), read, None, parser_requirements);
749
750	let mut root = crate::context::RootContext::new_with_primitive_references();
751
752	{
753		add_definition_files_to_root(vec![on.to_path_buf()], &mut root, &mut checking_data);
754
755		assert!(
756			!checking_data.diagnostics_container.contains_error(),
757			"found error in definition file {:#?}",
758			checking_data.diagnostics_container.get_diagnostics()
759		);
760	}
761
762	let mut buf = CACHE_MARKER.to_vec();
763
764	// This reserves a u32 bytes which marks where the content lives
765	buf.extend_from_slice(&[0u8; (u32::BITS / u8::BITS) as usize]);
766
767	let cache = Cache {
768		variables: root.variables,
769		named_types: root.named_types,
770		info: root.info,
771		types: checking_data.types,
772	};
773
774	cache.serialize(&mut buf);
775
776	// Add content
777	{
778		let cache_len: usize = buf.len() - CACHE_MARKER.len() - U32_BYTES as usize;
779		// Set length
780		buf[CACHE_MARKER.len()..(CACHE_MARKER.len() + U32_BYTES as usize)]
781			.copy_from_slice(&(cache_len as u32).to_le_bytes());
782
783		// TODO not great
784		let Some(File::Source(source, content)) = checking_data.modules.get_file(on, None) else {
785			panic!()
786		};
787
788		let path = on.to_str().unwrap().to_owned();
789		(source, path).serialize(&mut buf);
790		buf.extend_from_slice(content.as_bytes());
791	}
792
793	buf
794}
795
796pub fn get_closest<'a, 'b>(
797	items: impl Iterator<Item = &'a str>,
798	closest_one: &'b str,
799) -> Option<Vec<&'a str>> {
800	const MIN_DISTANCE: usize = 2;
801	let candidates =
802		items.filter(|item| levenshtein(closest_one, item) <= MIN_DISTANCE).collect::<Vec<&str>>();
803	match candidates.len() {
804		0 => None,
805		1.. => Some(candidates),
806	}
807}