Skip to main content

ezno_checker/context/
information.rs

1use source_map::SpanWithSource;
2use std::collections::HashMap;
3
4use crate::{
5	events::{Event, RootReference},
6	features::functions::ClosureId,
7	types::{
8		calling::ThisValue,
9		properties::{Properties, PropertyKey, Publicity},
10		TypeStore,
11	},
12	PropertyValue, Type, TypeId, VariableId,
13};
14
15/// Things that are currently true or have happened
16#[derive(Debug, Default, binary_serialize_derive::BinarySerializable)]
17pub struct LocalInformation {
18	pub(crate) events: Vec<Event>,
19	/// TODO think about tasks. These are things that may happen at next stop point
20	pub(crate) queued_events: Vec<Event>,
21
22	/// This can be not have a value if not defined
23	pub(crate) variable_current_value: HashMap<VariableId, TypeId>,
24
25	/// For objects (inc classes) and interfaces (because of hoisting).
26	/// However properties on [`crate::types::ObjectNature::AnonomousObjectLiteral`] are held on there
27	pub(crate) current_properties: HashMap<TypeId, Properties>,
28
29	/// Can be modified (unfortunately) so here
30	pub(crate) prototypes: HashMap<TypeId, TypeId>,
31
32	/// `ContextId` is a mini context
33	pub(crate) closure_current_values: HashMap<(ClosureId, RootReference), TypeId>,
34
35	/// Not writeable, `TypeError: Cannot add property, object is not extensible`. TODO conditional ?
36	pub(crate) frozen: HashMap<TypeId, ObjectProtectionState>,
37
38	/// Object type (LHS), must always be RHS
39	///
40	/// *not quite the best place, but used in [`InformationChain`]*
41	pub(crate) object_constraints: HashMap<TypeId, TypeId>,
42
43	/// WIP narrowing
44	/// TODO how will chaining but not cycles work
45	pub(crate) narrowed_values: crate::Map<TypeId, TypeId>,
46
47	pub(crate) state: ReturnState,
48
49	/// For super calls etc
50	///
51	/// TODO not great that this has to be Option to satisfy Default
52	pub(crate) value_of_this: ThisValue,
53}
54
55#[derive(Debug, Clone, Copy, binary_serialize_derive::BinarySerializable)]
56pub enum ObjectProtectionState {
57	Frozen,
58	Sealed,
59	NoExtensions,
60}
61
62#[derive(Debug, Default, binary_serialize_derive::BinarySerializable, Clone)]
63pub(crate) enum ReturnState {
64	#[default]
65	Continued,
66	Rolling {
67		under: TypeId,
68		returned: TypeId,
69	},
70	Finished(TypeId),
71}
72
73impl ReturnState {
74	pub(crate) fn is_finished(&self) -> bool {
75		matches!(self, ReturnState::Finished(..))
76	}
77
78	pub(crate) fn get_returned(self, types: &mut TypeStore) -> TypeId {
79		match self {
80			ReturnState::Continued => TypeId::UNDEFINED_TYPE,
81			ReturnState::Rolling { under, returned } => {
82				types.new_conditional_type(under, returned, TypeId::UNDEFINED_TYPE)
83			}
84			ReturnState::Finished(ty) => ty,
85		}
86	}
87
88	pub(crate) fn append(&mut self, new: ReturnState) {
89		match self {
90			ReturnState::Continued => *self = new,
91			ReturnState::Rolling { .. } => match new {
92				ReturnState::Continued => {}
93				ReturnState::Rolling { .. } => {
94					crate::utilities::notify!("Warning not accepting second rolling");
95				}
96				new @ ReturnState::Finished(_) => {
97					crate::utilities::notify!("Warning overwriting conditional");
98					*self = new;
99				}
100			},
101			ReturnState::Finished(_) => todo!(),
102		}
103	}
104}
105
106impl LocalInformation {
107	/// For interfaces only
108	pub fn register_property_on_type(
109		&mut self,
110		on: TypeId,
111		publicity: Publicity,
112		under: PropertyKey<'static>,
113		to: PropertyValue,
114	) {
115		self.current_properties.entry(on).or_default().push((publicity, under, to));
116	}
117	pub fn register_property(
118		&mut self,
119		on: TypeId,
120		publicity: Publicity,
121		under: PropertyKey<'static>,
122		value: PropertyValue,
123		position: SpanWithSource,
124	) {
125		self.current_properties.entry(on).or_default().push((
126			publicity,
127			under.clone(),
128			value.clone(),
129		));
130		self.events.push(Event::Miscellaneous(
131			crate::events::MiscellaneousEvents::RegisterProperty {
132				on,
133				under,
134				value,
135				publicity,
136				position,
137			},
138		));
139	}
140
141	// /// This is how invocation contexts register throws...
142	// pub(crate) fn throw_value_in_info(&mut self, value: TypeId, position: SpanWithSource) {
143	// 	self.events.push(crate::events::FinalEvent::Throw { thrown: value, position }.into());
144	// }
145
146	#[must_use]
147	pub fn get_events(&self) -> &[Event] {
148		&self.events
149	}
150
151	/// Use `features::delete_property`
152	pub(crate) fn delete_property(
153		&mut self,
154		on: TypeId,
155		(publicity, key): (Publicity, PropertyKey<'static>),
156		position: SpanWithSource,
157		option: Option<TypeId>,
158	) {
159		// on_default() okay because might be in a nested context.
160		// entry empty does not mean no properties, just no properties set on this level
161		self.current_properties.entry(on).or_default().push((
162			publicity,
163			key.clone(),
164			PropertyValue::Deleted,
165		));
166
167		self.events.push(Event::Miscellaneous(crate::events::MiscellaneousEvents::Delete {
168			on,
169			publicity,
170			under: key,
171			into: option,
172			position,
173		}));
174	}
175
176	pub(crate) fn new_object(
177		&mut self,
178		prototype: Option<TypeId>,
179		types: &mut crate::types::TypeStore,
180		position: SpanWithSource,
181		// TODO if this on environment instead it could be worked out?
182		is_under_dyn: bool,
183	) -> TypeId {
184		let ty = types.register_type(Type::Object(crate::types::ObjectNature::RealDeal));
185		// crate::utilities::notify!("New object created under {:?}", ty);
186
187		if let Some(prototype) = prototype {
188			self.prototypes.insert(ty, prototype);
189		}
190
191		if is_under_dyn {
192			let prototype = match prototype {
193				Some(id) => crate::events::PrototypeArgument::Yeah(id),
194				None => crate::events::PrototypeArgument::None,
195			};
196			// TODO maybe register the environment if function ...
197			// TODO register properties
198			let value = Event::CreateObject { referenced_in_scope_as: ty, prototype, position };
199			self.events.push(value);
200		}
201
202		ty
203	}
204
205	#[must_use]
206	pub fn get_properties_on_type_for_this_level(
207		&self,
208		ty: TypeId,
209	) -> Option<&Vec<(Publicity, PropertyKey, PropertyValue)>> {
210		self.current_properties.get(&ty)
211	}
212
213	pub(crate) fn extend(&mut self, other: LocalInformation, condition: Option<TypeId>) {
214		if condition.is_some() {
215			todo!()
216		}
217		self.events.extend(other.events);
218		self.queued_events.extend(other.queued_events);
219		self.variable_current_value.extend(other.variable_current_value);
220		self.current_properties.extend(other.current_properties);
221		self.prototypes.extend(other.prototypes);
222		self.closure_current_values.extend(other.closure_current_values);
223		self.frozen.extend(other.frozen);
224		self.narrowed_values.extend(other.narrowed_values);
225		self.state = other.state;
226	}
227
228	/// TODO explain when `ref`
229	pub(crate) fn extend_ref(&mut self, other: &LocalInformation) {
230		self.events.extend(other.events.iter().cloned());
231		self.queued_events.extend(other.queued_events.iter().cloned());
232		self.variable_current_value.extend(other.variable_current_value.iter().clone());
233		self.prototypes.extend(other.prototypes.iter().clone());
234		self.current_properties
235			.extend(other.current_properties.iter().map(|(l, r)| (*l, r.clone())));
236		self.closure_current_values
237			.extend(other.closure_current_values.iter().map(|(l, r)| (l.clone(), *r)));
238		self.frozen.extend(other.frozen.clone());
239		self.narrowed_values.extend(other.narrowed_values.iter().copied());
240		self.state = other.state.clone();
241	}
242
243	#[must_use]
244	pub fn is_finished(&self) -> bool {
245		self.state.is_finished()
246	}
247}
248
249pub trait InformationChain {
250	fn get_chain_of_info(&self) -> impl Iterator<Item = &'_ LocalInformation>;
251
252	fn get_narrowed(&self, for_ty: TypeId) -> Option<TypeId> {
253		self.get_chain_of_info().find_map(|info| info.narrowed_values.get(&for_ty).copied())
254	}
255
256	fn get_narrowed_or_object(&self, for_ty: TypeId, types: &TypeStore) -> Option<TypeId> {
257		let value = self.get_narrowed(for_ty);
258		if let Some(value) = value {
259			Some(value)
260		} else if let Type::Constructor(crate::types::Constructor::ConditionalResult {
261			condition,
262			truthy_result,
263			otherwise_result,
264			result_union: _,
265		}) = types.get_type_by_id(for_ty)
266		{
267			let narrowed_condition = self.get_narrowed(*condition)?;
268			if let crate::Decidable::Known(condition) =
269				crate::types::is_type_truthy_falsy(narrowed_condition, types)
270			{
271				let value = if condition { truthy_result } else { otherwise_result };
272				Some(*value)
273			} else {
274				None
275			}
276		} else {
277			value
278		}
279	}
280}
281
282pub struct ModuleInformation<'a> {
283	pub top: &'a LocalInformation,
284	pub module: &'a LocalInformation,
285}
286
287impl<'a> InformationChain for ModuleInformation<'a> {
288	fn get_chain_of_info(&self) -> impl Iterator<Item = &'_ LocalInformation> {
289		IntoIterator::into_iter([self.top, self.module])
290	}
291}
292
293pub(crate) fn get_value_of_constant_import_variable(
294	variable: VariableId,
295	info: &impl InformationChain,
296) -> TypeId {
297	info.get_chain_of_info()
298		.find_map(|info| info.variable_current_value.get(&variable).copied())
299		.unwrap()
300}
301
302pub fn merge_info(
303	parents: &impl InformationChain,
304	onto: &mut LocalInformation,
305	condition: TypeId,
306	mut truthy: LocalInformation,
307	mut otherwise: Option<LocalInformation>,
308	types: &mut TypeStore,
309	position: SpanWithSource,
310) {
311	// TODO I think these are okay
312	// Bias sets what happens after
313	let (new_state, carry_information_from) = match (
314		truthy.state.clone(),
315		otherwise.as_ref().map_or(ReturnState::Continued, |o| o.state.clone()),
316	) {
317		(ReturnState::Continued, ReturnState::Continued) => (ReturnState::Continued, None),
318		(ReturnState::Finished(returned), ReturnState::Continued) => {
319			(ReturnState::Rolling { under: condition, returned }, Some(false))
320		}
321		(ReturnState::Continued, ReturnState::Finished(returned)) => (
322			ReturnState::Rolling { under: types.new_logical_negation_type(condition), returned },
323			Some(true),
324		),
325		(ReturnState::Continued, rhs @ ReturnState::Rolling { .. }) => (rhs, None),
326		(ReturnState::Rolling { under, returned }, ReturnState::Continued) => (
327			ReturnState::Rolling { under: types.new_logical_and_type(condition, under), returned },
328			None,
329		),
330		(
331			ReturnState::Rolling { under: truthy_under, returned: truthy_returned },
332			ReturnState::Rolling { under: otherwise_under, returned: otherwise_returned },
333		) => {
334			let under = types.new_logical_or_type(truthy_under, otherwise_under);
335			let returned =
336				types.new_conditional_type(condition, truthy_returned, otherwise_returned);
337			(ReturnState::Rolling { under, returned }, None)
338		}
339		(lhs @ ReturnState::Rolling { .. }, ReturnState::Finished(_)) => (lhs, Some(true)),
340		(ReturnState::Finished(_), rhs @ ReturnState::Rolling { .. }) => (rhs, Some(false)),
341		(ReturnState::Finished(truthy_return), ReturnState::Finished(otherwise_return)) => (
342			ReturnState::Finished(types.new_conditional_type(
343				condition,
344				truthy_return,
345				otherwise_return,
346			)),
347			None,
348		),
349	};
350
351	let truthy_events = truthy.events.len() as u32;
352	let otherwise_events = otherwise.as_ref().map_or(0, |f| f.events.len() as u32);
353
354	if truthy_events + otherwise_events != 0 {
355		onto.events.push(Event::Conditionally {
356			condition,
357			truthy_events,
358			otherwise_events,
359			position,
360		});
361
362		onto.events.append(&mut truthy.events);
363		if let Some(ref mut otherwise) = otherwise {
364			// crate::utilities::notify!("truthy events={:?}, otherwise events={:?}", truthy.events, otherwise.events);
365			onto.events.append(&mut otherwise.events);
366		}
367
368		onto.events.push(Event::EndOfControlFlow(truthy_events + otherwise_events));
369	}
370
371	if new_state.is_finished() {
372		onto.state = new_state;
373		return;
374	}
375
376	if let Some(carry_information_from) = carry_information_from {
377		#[allow(clippy::match_bool)]
378		match carry_information_from {
379			true => {
380				onto.extend(truthy, None);
381			}
382			false => {
383				if let Some(otherwise) = otherwise {
384					onto.extend(otherwise, None);
385				} else {
386					// Could negate existing, but starting again handles types better
387					let values = crate::features::narrowing::narrow_based_on_expression_into_vec(
388						condition, true, parents, types,
389					);
390
391					onto.narrowed_values = values;
392					onto.state = new_state;
393				}
394			}
395		}
396	} else {
397		onto.state = new_state;
398
399		// TODO don't need to do above some scope
400		for (var, true_value) in truthy.variable_current_value {
401			crate::utilities::notify!("{:?} {:?}", var, true_value);
402			// TODO don't get value above certain scope...
403			let otherwise_value = otherwise
404				.as_mut()
405				// Remove is important here
406				.and_then(|otherwise| otherwise.variable_current_value.remove(&var))
407				.or_else(|| onto.variable_current_value.get(&var).copied())
408				.or_else(|| {
409					parents
410						.get_chain_of_info()
411						.find_map(|info| info.variable_current_value.get(&var))
412						.copied()
413				})
414				.unwrap_or(TypeId::ERROR_TYPE);
415
416			let new = types.new_conditional_type(condition, true_value, otherwise_value);
417
418			onto.variable_current_value.insert(var, new);
419		}
420
421		// TODO temp fix for `... ? { ... } : { ... }`.
422		// TODO add undefineds to sides etc
423		for (on, properties) in truthy.current_properties {
424			// let properties = properties
425			// 	.into_iter()
426			// 	.map(|(publicity, key, value)| {
427			// 		let falsy_environment_property = otherwise
428			// 			.as_mut()
429			// 			.and_then(|otherwise| {
430			// 				pick_out_property(&mut otherwise.current_properties, (publicity, key), onto, types)
431			// 			});
432
433			// 		if let Some(existing) = falsy_environment_property {
434			// 			// Merging more complex properties has lots of issues
435			// 			todo!()
436			// 		} else {
437			// 			(publicity, key, PropertyValue::ConditionallyExists { condition, value })
438			// 		}
439			// 	})
440			// 	.collect();
441
442			if let Some(existing) = onto.current_properties.get_mut(&on) {
443				existing.extend(properties);
444			} else {
445				onto.current_properties.insert(on, properties);
446			}
447		}
448
449		if let Some(otherwise) = otherwise {
450			for (on, properties) in otherwise.current_properties {
451				if let Some(existing) = onto.current_properties.get_mut(&on) {
452					existing.extend(properties);
453				} else {
454					onto.current_properties.insert(on, properties);
455				}
456			}
457		}
458
459		// TODO set more information?
460	}
461}
462
463// `info_chain` and `types` are a bit excess, but `key_matches` requires it
464// TODO needs to delete afterwards, to block it out for subsequent
465fn _pick_out_property(
466	from: &mut Properties,
467	(want_publicity, want_key): (Publicity, &PropertyKey<'static>),
468	info_chain: &impl InformationChain,
469	types: &TypeStore,
470) -> Option<(Publicity, PropertyKey<'static>, PropertyValue)> {
471	from.iter()
472		.position(|(publicity, key, _)| {
473			*publicity == want_publicity
474				&& crate::types::key_matches((key, None), (want_key, None), info_chain, types).0
475		})
476		// TODO replace with deleted?
477		.map(|idx| from.remove(idx))
478}