diff --git a/.github/workflows/clippy-rustfmt-fix.yml b/.github/workflows/clippy-rustfmt-fix.yml
index fc2cfd76..16aa4d05 100644
--- a/.github/workflows/clippy-rustfmt-fix.yml
+++ b/.github/workflows/clippy-rustfmt-fix.yml
@@ -16,6 +16,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
+      - uses: dtolnay/rust-toolchain@stable
       - uses: actions/cache@v4
         with:
           path: ${{ env.CACHE_PATHS }}
diff --git a/.github/workflows/performance-and-size.yml b/.github/workflows/performance-and-size.yml
index 6d5dcdb6..e248bd27 100644
--- a/.github/workflows/performance-and-size.yml
+++ b/.github/workflows/performance-and-size.yml
@@ -37,13 +37,15 @@ jobs:
         env:
           CARGO_PROFILE_RELEASE_DEBUG: true
 
-      - name: Get base ezno
-        if: github.ref_name != 'main'
-        uses: actions/download-artifact@v4
-        continue-on-error: true
-        with:
-          name: latest-checker
-          path: previous-ezno
+      # TODO need to lookup existing workflow on main
+      # even if this worked, it might have issues with newer features added in this run
+      # - name: Get base ezno
+      #   if: github.ref_name != 'main'
+      #   uses: actions/download-artifact@v4
+      #   continue-on-error: true
+      #   with:
+      #     name: latest-checker
+      #     path: previous-ezno
 
       - name: Set compilers
         id: compilers
@@ -197,16 +199,16 @@ jobs:
             curl -sS $url > input.js
 
             echo "::group::Comparison"
-            hyperfine \
+            hyperfine -i \
               -L compiler ${{ steps.compilers.outputs.BINARIES }} \
               '{compiler} ast-explorer full input.js --timings'
             echo "::endgroup::"
           done
 
-      - name: Upload checker
-        if: github.ref == 'main'
-        uses: actions/upload-artifact@v4
-        with:
-          name: latest-checker
-          path: target/release/ezno
-          retention-days: 90
+      # - name: Upload checker
+      #   if: github.ref == 'main'
+      #   uses: actions/upload-artifact@v4
+      #   with:
+      #     name: latest-checker
+      #     path: target/release/ezno
+      #     retention-days: 90
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index e45c83c5..c6e1b695 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -95,7 +95,7 @@ jobs:
 
       - name: Run checker specification
         if: (steps.changes.outputs.checker == 'true' && github.event_name != 'pull_request') || github.ref_name == 'main'
-        run: cargo test
+        run: cargo test -p ezno-checker-specification
       
       - name: Run checker specification (w/ staging)
         if: steps.changes.outputs.checker == 'true' && github.event_name == 'pull_request'
diff --git a/README.md b/README.md
index 27dfc88b..f0c1165e 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ What Ezno is:
 
 What Ezno is not
 - **eNZo, the Z is in front of the N** (pronounce as 'Fresno' without the 'fr') 😀
-- Be on parity with TSC or 1:1, it has some different behaviors **but** should work in existing projects using TSC
+- Be on parity with TSC or 1:1, it has some different behaviors **but** should work in existing projects using TSC. [You can see a full comparison of emitted errors and warnings compared with TSC here](https://kaleidawave.github.io/ezno/comparison)
 - Faster as a means to serve large codebases. Cut out bloat and complex code first!
 - Smarter as a means to allow more *dynamic patterns*. Keep things simple!
 - A binary executable compiler. It takes in JavaScript (or a TypeScript or Ezno superset) and does similar processes to traditional compilers, but at the end emits JavaScript. However, in the future, it *could* generate a lower level format using its event (side-effect) representation.
diff --git a/checker/Cargo.toml b/checker/Cargo.toml
index 2461cae6..2b7614b9 100644
--- a/checker/Cargo.toml
+++ b/checker/Cargo.toml
@@ -39,7 +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 = [] }
+regress = { version = "0.10", 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 c4f51fa0..c2573ea2 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 ad67900c..b5c0e66f 100644
--- a/checker/definitions/overrides.d.ts
+++ b/checker/definitions/overrides.d.ts
@@ -37,7 +37,7 @@ declare class Array<T> {
 
     // TODO this argument
     map<U>(cb: (t: T, i?: number) => U): Array<U> {
-        const { length } = this, mapped: Array<U> = [];
+        const { length } = this, mapped: U[] = [];
         let i: number = 0;
         while (i < length) {
             const value = this[i];
@@ -48,7 +48,7 @@ declare class Array<T> {
 
     // // TODO any is debatable
     filter(cb: (t: T, i?: number) => any): Array<T> {
-        const { length } = this, filtered: Array<T> = [];
+        const { length } = this, filtered: T[] = [];
         let i: number = 0;
         while (i < length) {
             const value = this[i];
@@ -127,8 +127,9 @@ declare class Array<T> {
 
 type Record<K extends string, T> = { [P in K]: T }
 
-type LessThan<T extends number> = ExclusiveRange<NegativeInfinity, T>;
-type GreaterThan<T extends number> = ExclusiveRange<T, Infinity>;
+type ExclusiveRange<F extends number, C extends number> = GreaterThan<F> & LessThan<C>;
+type InclusiveRange<F extends number, C extends number> = (GreaterThan<F> & LessThan<C>) | (F | C);
+
 type Integer = MultipleOf<1>;
 
 declare class Map<T, U> {
@@ -202,7 +203,7 @@ declare class Promise<T> { }
 
 declare class RegExp {
     @Constant("regexp:constructor")
-    constructor(pattern: string, flags?: string);
+        constructor(pattern: string, flags?: string);
 
     @Constant("regexp:exec")
     exec(input: string): RegExpExecArray | null;
@@ -222,10 +223,10 @@ interface RegExpExecArray extends Array<string> {
      * The first match. This will always be present because `null` will be returned if there are no matches.
      */
     0: string;
-}
+    // }
 
-// es2018
-interface RegExpExecArray {
+    // // es2018
+    // interface RegExpExecArray {
     groups?: {
         [key: string]: string;
     };
diff --git a/checker/definitions/simple.d.ts b/checker/definitions/simple.d.ts
index aba85ea2..01aed46e 100644
--- a/checker/definitions/simple.d.ts
+++ b/checker/definitions/simple.d.ts
@@ -211,8 +211,9 @@ declare class Map<K, V> {
 
 type Record<K extends string, T> = { [P in K]: T }
 
-type LessThan<T extends number> = ExclusiveRange<NegativeInfinity, T>;
-type GreaterThan<T extends number> = ExclusiveRange<T, Infinity>;
+type ExclusiveRange<F extends number, C extends Number> = GreaterThan<F> & LessThan<C>;
+type InclusiveRange<F extends number, C extends Number> = (GreaterThan<F> & LessThan<C>) | (F | C);
+
 type Integer = MultipleOf<1>;
 
 /**
diff --git a/checker/examples/run_checker.rs b/checker/examples/run_checker.rs
index 124c86e8..8c54fb7e 100644
--- a/checker/examples/run_checker.rs
+++ b/checker/examples/run_checker.rs
@@ -20,6 +20,7 @@ fn main() {
 	let no_lib = args.iter().any(|item| item == "--no-lib");
 	let debug_dts = args.iter().any(|item| item == "--debug-dts");
 	let extras = args.iter().any(|item| item == "--extras");
+	let advanced_numbers = args.iter().any(|item| item == "--advanced-numbers");
 
 	let now = Instant::now();
 
@@ -46,6 +47,7 @@ fn main() {
 		max_inline_count: 600,
 		debug_dts,
 		extra_syntax: extras,
+		advanced_numbers,
 		..Default::default()
 	};
 
@@ -60,8 +62,7 @@ fn main() {
 
 	if args.iter().any(|arg| arg == "--types") {
 		eprintln!("Types:");
-		let types = result.types.into_vec_temp();
-		for (type_id, item) in &types[types.len().saturating_sub(60)..] {
+		for (type_id, item) in result.types.user_types() {
 			eprintln!("\t{type_id:?}: {item:?}");
 		}
 	}
@@ -74,6 +75,10 @@ fn main() {
 		}
 	}
 
+	if args.iter().any(|arg| arg == "--called-functions") {
+		eprintln!("Called function: {:?}", result.types.called_functions);
+	}
+
 	if args.iter().any(|arg| arg == "--time") {
 		let end = now.elapsed();
 		let count = result.diagnostics.into_iter().len();
diff --git a/checker/specification/Cargo.toml b/checker/specification/Cargo.toml
index 51591c22..81dbbd2b 100644
--- a/checker/specification/Cargo.toml
+++ b/checker/specification/Cargo.toml
@@ -7,9 +7,11 @@ publish = false
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [features]
-just-staging = []
+default = ["base"]
+base = []
 staging = []
-all = ["staging"]
+to_implement = []
+all = ["base", "staging", "to_implement"]
 
 [[test]]
 name = "specification_test"
diff --git a/checker/specification/build.rs b/checker/specification/build.rs
index 1b20193d..1c597410 100644
--- a/checker/specification/build.rs
+++ b/checker/specification/build.rs
@@ -12,7 +12,7 @@ fn main() -> Result<(), Box<dyn Error>> {
 	let out_path = Path::new(&std::env::var("OUT_DIR")?).join("specification.rs");
 	let mut out = File::create(out_path)?;
 
-	if cfg!(not(feature = "just-staging")) {
+	if cfg!(feature = "base") {
 		let specification = read_to_string("./specification.md")?;
 		markdown_lines_append_test_to_rust(specification.lines().enumerate(), &mut out)?;
 	}
@@ -26,7 +26,7 @@ fn main() -> Result<(), Box<dyn Error>> {
 		writeln!(&mut out, "}}").unwrap();
 	}
 
-	if cfg!(feature = "all") {
+	if cfg!(feature = "to_implement") {
 		let to_implement = read_to_string("./to_implement.md")?;
 		writeln!(&mut out, "mod to_implement {{ ").unwrap();
 		writeln!(&mut out, "use super::{{check_expected_diagnostics, TypeCheckOptions}}; ")
diff --git a/checker/specification/specification.md b/checker/specification/specification.md
index 88a305b0..7e2841af 100644
--- a/checker/specification/specification.md
+++ b/checker/specification/specification.md
@@ -306,16 +306,6 @@ const b = x.b;
 
 - No property 'b' on { a: 2 }
 
-#### `Object.keys`, `Object.values`, `Object.entries`
-
-```ts
-Object.keys({ a: 1, b: 2 }) satisfies ["a", "b"];
-Object.values({ a: 1, b: 2 }) satisfies [1, 2];
-Object.entries({ a: 1, b: 2 }) satisfies boolean;
-```
-
-- Expected boolean, found [["a", 1], ["b", 2]]
-
 #### Spread condition
 
 ```ts
@@ -813,7 +803,7 @@ const z: false = true || 4
 
 ```ts
 (4 === 2) satisfies true;
-(4 !== 2) satisfies string;
+(4 !== 5) satisfies string;
 ```
 
 - Expected true, found false
@@ -1330,7 +1320,7 @@ new MyClass("hi").value satisfies "hello"
 
 - Expected "hello", found "hi"
 
-#### `new` on function prototype
+#### `new` on function with assigned prototype
 
 ```ts
 function MyClass(value) {
@@ -1841,13 +1831,13 @@ kestrel(3)(2) satisfies 4
 
 ```ts
 function kestrel2(a) {
-	return _b => _c => a
+	return b => c => (a * b) + c
 }
 
-kestrel2(3)(2)(6) satisfies 4
+kestrel2(3)(2)(1) satisfies 4
 ```
 
-- Expected 4, found 3
+- Expected 4, found 7
 
 #### Carry across objects
 
@@ -1952,6 +1942,7 @@ function conditional(v: string) {
 		a++
 	}
 }
+
 conditional("x")
 a satisfies 2
 conditional("value")
@@ -2055,13 +2046,13 @@ stringIsHi(string) satisfies number;
 ```ts
 function func(param: boolean) {
 	let a = 2;
-    if (param) {
+	if (param) {
 		a = 3;
-        return a;
-    } else {
+		return a;
+	} else {
 		a = 7;
 	}
-    a satisfies string;
+	a satisfies string;
 }
 ```
 
@@ -2356,25 +2347,6 @@ try { checkedLn(-5) } catch {}
 
 - Conditional '[Error] { message: \"Cannot log\" }' was thrown in function
 
-#### Throw through internal callback
-
-```ts
-try {
-	[1, 2, 3].map((x: number) => {
-		if (x === 2) {
-			throw "error"
-		}
-	});
-	console.log("unreachable")
-} catch (e) {
-	e satisfies number;
-}
-```
-
-- Conditional '"error"' was thrown in function
-- Unreachable statement
-- Expected number, found "error"
-
 ### Collections
 
 > Some of these are built of exiting features.
@@ -2411,16 +2383,6 @@ x.push("hi");
 
 - Argument of type \"hi\" is not assignable to parameter of type number
 
-#### Array map
-
-> TODO other arguments (index and `this`)
-
-```ts
-[6, 8, 10].map(x => x + 1) satisfies [7, 8, 11];
-```
-
-- Expected [7, 8, 11], found [7, 9, 11]
-
 #### Mutation
 
 > This is part of [assignment mismatch](https://github.com/kaleidawave/ezno/issues/18)
@@ -2542,8 +2504,8 @@ getNumberBetweenFive() === 2.2;
 getNumberBetweenFive() === 7;
 ```
 
-- This equality is always false as InclusiveRange<0, 5> & Integer and 2.2 have no overlap
-- This equality is always false as InclusiveRange<0, 5> & Integer and 7 have no overlap
+- This equality is always false as GreaterThan<0> & LessThan<5> & Integer | 0 | 5 and 2.2 have no overlap
+- This equality is always false as GreaterThan<0> & LessThan<5> & Integer | 0 | 5 and 7 have no overlap
 
 #### Identity equality
 
@@ -2552,14 +2514,14 @@ getNumberBetweenFive() === 7;
 ```ts
 function func(a: string, b: number) {
 	(a === a) satisfies string;
-    (b === b) satisfies null;
+	(b === b) satisfies null;
 }
 ```
 
 - Expected string, found true
 - Expected null, found boolean
 
-#### Ranges for interal types
+#### Ranges for internal types
 
 ```ts
 function func(a: number) {
@@ -2579,20 +2541,87 @@ function func(a: number) {
 }
 ```
 
-- Expected null, found InclusiveRange\<-5, 5>
-- Expected string, found InclusiveRange\<18, 22>
+With advanced_numbers
+
+- Expected null, found GreaterThan<-5> & LessThan<5> | -5 | 5
+- Expected string, found GreaterThan<18> & LessThan<22> | 18 | 22
 
-#### Not disjoint
+#### Disjoint multiple of with range
+
+> TODO need to redo range to use interesection of less than and greater than
 
 ```ts
-function func(param: number) {
-	if (param !== 2) {
-		return param === 2
-	}
+function func1(a: number, b: number) {
+  if (a % 8 === 0 && 31 < b && b < 37) {
+    const x = a === b;
+  }
+  if (a % 10 === 0 && 31 < b && b < 37) {
+    const x = a === b;
+  }
+  if (a % 10 === 0 && 31 < b && b < 41) {
+    const x = a === b;
+  }
 }
 ```
 
-- This equality is always false as Not<2> and 2 have no overlap
+With advanced_numbers
+
+- This equality is always false as MultipleOf<10> and GreaterThan<31> & LessThan<37> have no overlap
+
+#### Modulo range
+
+```ts
+function func(x: number) {
+  return x % 5 === 6;
+}
+```
+
+- This equality is always false as ExclusiveRange<-5, 5> and 6 have no overlap
+
+#### Transistivity
+
+```ts
+function func(a: number, b: number, c: number) {
+  if (a < b && b < c)  {
+    const cond = (a < c) satisfies 5;
+  }
+}
+```
+
+- Expected 5, found true
+
+### Operators across conditions
+
+```ts
+function func(param: boolean) {
+    const value = param ? 1 : 2;
+    return value + 1;
+}
+
+func satisfies string;
+```
+
+With advanced_numbers
+
+- Expected string, found (param: boolean) => 2 | 3
+
+#### Disjoint not
+
+```ts
+function func1(param: Not<string>) {
+  return "hi" === param;
+}
+
+function func2(param: Not<string>) {
+  return 4 === param;
+}
+
+function func3(p1: Not<string>, p2: Not<number>) {
+  return p1 === p2;
+}
+```
+
+- This equality is always false as "hi" and Not\<string> have no overlap
 
 ### Statements, declarations and expressions
 
@@ -2709,6 +2738,9 @@ declare var x: number;
 declare var x: number;
 (x < 4) satisfies string;
 (x === 4) satisfies Math;
+(x !== 4) satisfies boolean;
+(x > 4) satisfies boolean;
+(x >= 4) satisfies boolean;
 ```
 
 - Expected string, found boolean
@@ -2961,8 +2993,8 @@ let x: BoxString<string, number>;
 
 ```ts
 interface X {
-    a: string
-    b: string
+	a: string
+	b: string
 }
 
 function func(x: X | null) {
@@ -3330,11 +3362,11 @@ doThingWithX(new Y())
 
 ```ts
 class Box<T> {
-    value: T;
+	value: T;
 
-    constructor(value: T) {
-        this.value = value;
-    }
+	constructor(value: T) {
+		this.value = value;
+	}
 }
 
 const myBox = new Box<number>("hi");
@@ -3747,10 +3779,29 @@ type Introduction = `Hello ${string}`;
 const first: Introduction = "Hello Ben";
 const second: Introduction = "Hi Ben";
 const third: `Hiya ${string}` = "Hello Ben";
+
+// Edge cases
+const invalidNum1: `${1}` = 1;
+const invalidNum2: `${1}` = "1";
+const invalidNum3: `${1}` = "2";
 ```
 
 - Type "Hi Ben" is not assignable to type Introduction
 - Type "Hello Ben" is not assignable to type `Hiya ${string}`
+- Type 1 is not assignable to type "1"
+- Type \"2\" is not assignable to type "1"
+
+#### Disjoint template literals
+
+```ts
+function func(a: `a${string}`, b: `b${string}`, c: string) {
+    const res1 = a === b;
+    const res2 = (b === c) satisfies string;
+}
+```
+
+- This equality is always false as `a${string}` and `b${string}` have no overlap
+- Expected string, found boolean
 
 #### Assigning to types as keys
 
@@ -4071,6 +4122,8 @@ x.a = "hi";
 x.a = 4;
 ```
 
+<!-- TODO this is incorrect!!!!, should be string -->
+
 - Type 4 does not meet property constraint "hi"
 
 #### `as` rewrite
@@ -4097,7 +4150,7 @@ x.property_b
 
 ```ts
 function x(p: { readonly a: string, b: string }) {
-    p.a = "hi";
+	p.a = "hi";
 	p.b = "hi";
 }
 ```
@@ -4283,10 +4336,10 @@ function logicNarrow(thing: any, other: any) {
 
 ```ts
 function func(param: boolean | string | number) {
-    if (typeof param === "boolean") {
-        return 5
-    }
-    param satisfies null;
+	if (typeof param === "boolean") {
+		return 5
+	}
+	param satisfies null;
 }
 ```
 
@@ -4296,9 +4349,9 @@ function func(param: boolean | string | number) {
 
 ```ts
 function func(param: Array<string> | string) {
-    if (param instanceof Array) {
+	if (param instanceof Array) {
 		param satisfies null;
-    }
+	}
 }
 ```
 
@@ -4308,9 +4361,9 @@ function func(param: Array<string> | string) {
 
 ```ts
 function func(param: any) {
-    if (param instanceof Array) {
+	if (param instanceof Array) {
 		param satisfies null;
-    }
+	}
 }
 ```
 
@@ -4320,10 +4373,10 @@ function func(param: any) {
 
 ```ts
 function narrowPropertyEquals(param: { tag: "a", a: string } | { tag: "b", b: number }) {
-    if (param.tag === "a") {
-        param.a satisfies string;
-        param satisfies null;
-    }
+	if (param.tag === "a") {
+		param.a satisfies string;
+		param satisfies null;
+	}
 }
 ```
 
@@ -4333,10 +4386,10 @@ function narrowPropertyEquals(param: { tag: "a", a: string } | { tag: "b", b: nu
 
 ```ts
 function narrowFromTag(param: { tag: "a", a: string } | { tag: "b", b: number }) {
-    if ("a" in param) {
-        param.a satisfies string;
-        param satisfies null;
-    }
+	if ("a" in param) {
+		param.a satisfies string;
+		param satisfies null;
+	}
 }
 ```
 
@@ -4348,9 +4401,9 @@ function narrowFromTag(param: { tag: "a", a: string } | { tag: "b", b: number })
 
 ```ts
 function buildObject(param: any) {
-    if ("a" in param) {
-        param satisfies null;
-    }
+	if ("a" in param) {
+		param satisfies null;
+	}
 }
 ```
 
@@ -4360,22 +4413,22 @@ function buildObject(param: any) {
 
 ```ts
 function conditional(param: boolean) {
-	const obj1 = {}, obj2 = {};
+	const obj1 = { b: 2 }, obj2 = { c: 6 };
 	const sum = param ? obj1 : obj2;
 	if (sum === obj1) {
-		sum.a = 2;
+		sum.a = 3;
 	}
 	[obj1, obj2] satisfies string;
 }
 ```
 
-- Expected string, found [{ a: 2 }, {}]
+- Expected string, found [{ b: 2, a: 3 }, { c: 6 }]
 
 #### From condition equality
 
 ```ts
 function conditional(param: boolean) {
-	const obj1 = { a: 1 }, obj2 = {};
+	const obj1 = { a: 1 }, obj2 = { b: 2};
 	const sum = param ? obj1 : obj2;
 	if (param) {
 		sum satisfies string;
@@ -4405,19 +4458,19 @@ function conditional(param: boolean) {
 
 ```ts
 function func1(param: string | number) {
-    if (typeof param === "number" && param > 0) {
-        param satisfies number;
-    } else {
-        param satisfies null;
+	if (typeof param === "number" && param > 0) {
+		param satisfies number;
+	} else {
+		param satisfies null;
 	}
 }
 
 function func2(param: string | number | boolean) {
-    if (typeof param === "string" || !(typeof param === "number")) {
-        param satisfies undefined;
-    } else {
-        param satisfies number;
-    }
+	if (typeof param === "string" || !(typeof param === "number")) {
+		param satisfies undefined;
+	} else {
+		param satisfies number;
+	}
 }
 ```
 
@@ -4432,10 +4485,10 @@ function func2(param: string | number | boolean) {
 function func(param: boolean) {
 	let a = param;
 	const inner = (value: boolean) => a = value;
-    if (a) {
+	if (a) {
 		inner(false);
 		a satisfies null;
-    }
+	}
 }
 ```
 
@@ -4445,15 +4498,15 @@ function func(param: boolean) {
 
 ```ts
 function func1(param: any): asserts param is number {
-    if (typeof param !== "string") {
-        throw "bad"
-    }
+	if (typeof param !== "string") {
+		throw "bad"
+	}
 }
 
 function func2(param: any): asserts param is boolean {
-    if (typeof param !== "boolean") {
-        throw "bad"
-    }
+	if (typeof param !== "boolean") {
+		throw "bad"
+	}
 }
 ```
 
@@ -4506,6 +4559,31 @@ function getName(name?: string) {
 
 - Expected undefined, found string
 
+#### Implication from equality
+
+```ts
+function func(a: boolean) {
+  const x = a ? 1 : 2;
+  if (x === 1) {
+    a satisfies "hi"
+  }
+}
+```
+
+- Expected "hi", found true
+
+#### Narrowing in for loop
+
+> Can't do modulo because of post mutation
+
+```ts
+for (let i = 0; i < 3; i++) {
+  const x = i === 50;
+}
+```
+
+- This equality is always false as LessThan<3> and 50 have no overlap
+
 ### Object constraint
 
 > Any references to a annotated variable **must** be within its LHS type. These test that it carries down to objects.
@@ -4597,9 +4675,18 @@ const x = { a: 3 };
 Object.setPrototypeOf(x, { a: 5, b: 2 });
 x.a satisfies 3;
 x.b satisfies string;
+
+const obj = Object.setPrototypeOf(
+  {}, 
+  Math.random() ? { a: 2 } : { get a() { return 0 } }
+);
+
+const result = 'a' in obj;
+result satisfies string;
 ```
 
 - Expected string, found 2
+- Expected string, found true
 
 #### Get prototype
 
@@ -5038,9 +5125,16 @@ function register(a: Literal<string>) {
 
 register("something")
 // `document.title` is an unknown string, non-literal
-register(document.title)
+register(document.title);
+
+function func(param: object) {
+	const obj = { a: 2 };
+	const obj1: Literal<object> = obj;
+	const obj2: Literal<object> = param;
+}
 ```
 
+- Type object is not assignable to type Literal\<object>
 - Argument of type string is not assignable to parameter of type Literal\<string>
 
 #### Number intrinsics
diff --git a/checker/specification/to_implement.md b/checker/specification/to_implement.md
index 11cce36d..5856c16b 100644
--- a/checker/specification/to_implement.md
+++ b/checker/specification/to_implement.md
@@ -770,6 +770,47 @@ a satisfies 0; b satisfies string;
 
 - Expected string, found 1
 
+#### Array map
+
+> TODO other arguments (index and `this`)
+
+```ts
+[6, 8, 10].map(x => x + 1) satisfies [7, 8, 11];
+```
+
+- Expected [7, 8, 11], found [7, 9, 11]
+
+#### `Object.keys`, `Object.values`, `Object.entries`
+
+```ts
+Object.keys({ a: 1, b: 2 }) satisfies ["a", "b"];
+Object.values({ a: 1, b: 2 }) satisfies [1, 2];
+Object.entries({ a: 1, b: 2 }) satisfies boolean;
+```
+
+- Expected boolean, found [["a", 1], ["b", 2]]
+
+#### Throw through internal callback
+
+> This test heavily relies on `.map` working
+
+```ts
+try {
+	[1, 2, 3].map((x: number) => {
+		if (x === 2) {
+			throw "error"
+		}
+	});
+	console.log("unreachable")
+} catch (e) {
+	e satisfies number;
+}
+```
+
+- Conditional '"error"' was thrown in function
+- Unreachable statement
+- Expected number, found "error"
+
 ### Control flow
 
 #### Conditional break
@@ -948,3 +989,16 @@ new RegExp("<string>x").group.string
 ```
 
 - ?
+
+### Properties
+
+#### Issue [#208](https://github.com/kaleidawave/ezno/issues/208)
+
+```ts
+let d = {};
+let e = 0;
+if (Math.random() > 0.2) { d.a = 0; }
+print_type('a' in d);
+```
+
+- Hmmm
diff --git a/checker/src/context/environment.rs b/checker/src/context/environment.rs
index a4a2dece..2c5decc7 100644
--- a/checker/src/context/environment.rs
+++ b/checker/src/context/environment.rs
@@ -14,11 +14,7 @@ use crate::{
 			AssignmentKind, AssignmentReturnStatus, IncrementOrDecrement, Reference,
 		},
 		modules::Exported,
-		objects::SpecialObject,
-		operations::{
-			evaluate_logical_operation_with_expression,
-			evaluate_pure_binary_operation_handle_errors, MathematicalAndBitwise,
-		},
+		operations::{evaluate_logical_operation_with_expression, MathematicalOrBitwiseOperation},
 		variables::{VariableMutability, VariableOrImport, VariableWithValue},
 	},
 	subtyping::{type_is_subtype, type_is_subtype_object, State, SubTypeResult, SubTypingOptions},
@@ -278,7 +274,7 @@ impl<'a> Environment<'a> {
 					}
 					AssignmentKind::PureUpdate(operator) => {
 						// Order matters here
-						let reference_position = reference.get_position();
+						// let reference_position = reference.get_position();
 						let existing = self.get_reference(
 							reference.clone(),
 							checking_data,
@@ -286,8 +282,9 @@ impl<'a> Environment<'a> {
 						);
 
 						let expression = expression.unwrap();
-						let expression_pos =
-							A::expression_position(expression).with_source(self.get_source());
+						// let expression_pos =
+						// 	A::expression_position(expression).with_source(self.get_source());
+
 						let rhs = A::synthesise_expression(
 							expression,
 							TypeId::ANY_TYPE,
@@ -295,22 +292,48 @@ impl<'a> Environment<'a> {
 							checking_data,
 						);
 
-						let new = evaluate_pure_binary_operation_handle_errors(
-							(existing, reference_position),
-							operator.into(),
-							(rhs, expression_pos),
-							checking_data,
+						let result = crate::features::operations::evaluate_mathematical_operation(
+							existing,
+							operator,
+							rhs,
 							self,
+							&mut checking_data.types,
+							checking_data.options.strict_casts,
+							checking_data.options.advanced_numbers,
 						);
-						let assignment_position =
-							assignment_position.with_source(self.get_source());
-						self.set_reference_handle_errors(
-							reference,
-							new,
-							assignment_position,
-							checking_data,
-						);
-						new
+						if let Ok(new) = result {
+							let assignment_position =
+								assignment_position.with_source(self.get_source());
+
+							self.set_reference_handle_errors(
+								reference,
+								new,
+								assignment_position,
+								checking_data,
+							);
+
+							new
+						} else {
+							checking_data.diagnostics_container.add_error(
+								crate::TypeCheckError::InvalidMathematicalOrBitwiseOperation {
+									operator,
+									lhs: crate::diagnostics::TypeStringRepresentation::from_type_id(
+										existing,
+										self,
+										&checking_data.types,
+										false,
+									),
+									rhs: crate::diagnostics::TypeStringRepresentation::from_type_id(
+										rhs,
+										self,
+										&checking_data.types,
+										false,
+									),
+									position: assignment_position.with_source(self.get_source()),
+								},
+							);
+							TypeId::ERROR_TYPE
+						}
 					}
 					AssignmentKind::IncrementOrDecrement(direction, return_kind) => {
 						// let value =
@@ -323,31 +346,57 @@ impl<'a> Environment<'a> {
 						);
 
 						// TODO existing needs to be cast to number!!
-
-						let new = evaluate_pure_binary_operation_handle_errors(
-							(existing, position),
-							match direction {
-								IncrementOrDecrement::Increment => MathematicalAndBitwise::Add,
-								IncrementOrDecrement::Decrement => MathematicalAndBitwise::Subtract,
+						let operator = match direction {
+							IncrementOrDecrement::Increment => MathematicalOrBitwiseOperation::Add,
+							IncrementOrDecrement::Decrement => {
+								MathematicalOrBitwiseOperation::Subtract
 							}
-							.into(),
-							(TypeId::ONE, source_map::Nullable::NULL),
-							checking_data,
-							self,
-						);
+						};
 
-						let assignment_position =
-							assignment_position.with_source(self.get_source());
-						self.set_reference_handle_errors(
-							reference,
-							new,
-							assignment_position,
-							checking_data,
+						let result = crate::features::operations::evaluate_mathematical_operation(
+							existing,
+							operator,
+							TypeId::ONE,
+							self,
+							&mut checking_data.types,
+							checking_data.options.strict_casts,
+							checking_data.options.advanced_numbers,
 						);
+						if let Ok(new) = result {
+							let assignment_position =
+								assignment_position.with_source(self.get_source());
+
+							self.set_reference_handle_errors(
+								reference,
+								new,
+								assignment_position,
+								checking_data,
+							);
 
-						match return_kind {
-							AssignmentReturnStatus::Previous => existing,
-							AssignmentReturnStatus::New => new,
+							match return_kind {
+								AssignmentReturnStatus::Previous => existing,
+								AssignmentReturnStatus::New => new,
+							}
+						} else {
+							checking_data.diagnostics_container.add_error(
+								crate::TypeCheckError::InvalidMathematicalOrBitwiseOperation {
+									operator,
+									lhs: crate::diagnostics::TypeStringRepresentation::from_type_id(
+										existing,
+										self,
+										&checking_data.types,
+										false,
+									),
+									rhs: crate::diagnostics::TypeStringRepresentation::from_type_id(
+										TypeId::ONE,
+										self,
+										&checking_data.types,
+										false,
+									),
+									position,
+								},
+							);
+							TypeId::ERROR_TYPE
 						}
 					}
 					AssignmentKind::ConditionalUpdate(operator) => {
@@ -890,45 +939,41 @@ impl<'a> Environment<'a> {
 							info.variable_current_value.get(&og_var.get_origin_variable_id())
 						})
 						.copied();
-					let narrowed = current_value.and_then(|cv| self.get_narrowed(cv));
 
-					if let Some(precise) = narrowed.or(current_value) {
-						let ty = checking_data.types.get_type_by_id(precise);
-
-						// TODO temp for function
-						if let Type::SpecialObject(SpecialObject::Function(..)) = ty {
-							return Ok(VariableWithValue(og_var.clone(), precise));
-						} else if let Type::RootPolyType(PolyNature::Open(_)) = ty {
-							crate::utilities::notify!(
-								"Open poly type '{}' treated as immutable free variable",
-								name
-							);
-							return Ok(VariableWithValue(og_var.clone(), precise));
-						} else if let Type::Constant(_) = ty {
-							return Ok(VariableWithValue(og_var.clone(), precise));
-						}
+					// TODO WIP
+					let narrowed = current_value
+						.and_then(|cv| self.get_narrowed_or_object(cv, &checking_data.types));
 
-						crate::utilities::notify!("Free variable with value!");
-					} else {
-						crate::utilities::notify!("Free variable with no current value");
+					if let Some(precise) = narrowed.or(current_value) {
+						// let ty = checking_data.types.get_type_by_id(precise);
+
+						// // TODO temp for function
+						// let value = if let Type::SpecialObject(SpecialObject::Function(..)) = ty {
+						// 	return Ok(VariableWithValue(og_var.clone(), precise));
+						// } else if let Type::RootPolyType(PolyNature::Open(_)) = ty {
+						// 	crate::utilities::notify!(
+						// 		"Open poly type '{}' treated as immutable free variable",
+						// 		name
+						// 	);
+						// 	return Ok(VariableWithValue(og_var.clone(), precise));
+						// } else if let Type::Constant(_) = ty {
+						// };
+
+						return Ok(VariableWithValue(og_var.clone(), precise));
 					}
 
-					if let Some(narrowed) = narrowed {
-						narrowed
+					crate::utilities::notify!("Free variable with no current value");
+					let constraint = checking_data
+						.local_type_mappings
+						.variables_to_constraints
+						.0
+						.get(&og_var.get_origin_variable_id());
+
+					if let Some(constraint) = constraint {
+						*constraint
 					} else {
-						let constraint = checking_data
-							.local_type_mappings
-							.variables_to_constraints
-							.0
-							.get(&og_var.get_origin_variable_id());
-						if let Some(constraint) = constraint {
-							*constraint
-						} else {
-							crate::utilities::notify!(
-								"TODO record that free variable is `any` here"
-							);
-							TypeId::ANY_TYPE
-						}
+						crate::utilities::notify!("TODO record that free variable is `any` here");
+						TypeId::ANY_TYPE
 					}
 				}
 				VariableMutability::Mutable { reassignment_constraint } => {
@@ -940,12 +985,15 @@ impl<'a> Environment<'a> {
 					for ctx in self.parents_iter() {
 						if let GeneralContext::Syntax(s) = ctx {
 							if s.possibly_mutated_variables.contains(&variable_id) {
+								crate::utilities::notify!("Possibly mutated variables");
 								break;
 							}
-							if let Some(value) =
+
+							if let Some(current_value) =
 								get_on_ctx!(ctx.info.variable_current_value.get(&variable_id))
+									.copied()
 							{
-								return Ok(VariableWithValue(og_var.clone(), *value));
+								return Ok(VariableWithValue(og_var.clone(), current_value));
 							}
 
 							if s.context_type.scope.is_dynamic_boundary().is_some() {
@@ -986,8 +1034,11 @@ impl<'a> Environment<'a> {
 				}
 			}
 
-			let ty = if let Some(value) = reused_reference {
-				value
+			let ty = if let Some(reused_reference) = reused_reference {
+				// TODO temp. I believe this can break type contracts because of mutations
+				// but needed here because of for loop narrowing
+				let narrowed = self.get_narrowed_or_object(reused_reference, &checking_data.types);
+				narrowed.unwrap_or(reused_reference)
 			} else {
 				// TODO dynamic ?
 				let ty = Type::RootPolyType(crate::types::PolyNature::FreeVariable {
@@ -1022,6 +1073,7 @@ impl<'a> Environment<'a> {
 					self,
 					of,
 					None::<&crate::types::generics::substitution::SubstitutionArguments<'static>>,
+					&checking_data.types,
 				)
 				.expect("import not assigned yet");
 				return Ok(VariableWithValue(og_var.clone(), current_value));
@@ -1031,7 +1083,9 @@ impl<'a> Environment<'a> {
 				self,
 				og_var.get_id(),
 				None::<&crate::types::generics::substitution::SubstitutionArguments<'static>>,
+				&checking_data.types,
 			);
+
 			if let Some(current_value) = current_value {
 				Ok(VariableWithValue(og_var.clone(), current_value))
 			} else {
diff --git a/checker/src/context/information.rs b/checker/src/context/information.rs
index 486587a4..6f13dcd8 100644
--- a/checker/src/context/information.rs
+++ b/checker/src/context/information.rs
@@ -252,6 +252,31 @@ pub trait InformationChain {
 	fn get_narrowed(&self, for_ty: TypeId) -> Option<TypeId> {
 		self.get_chain_of_info().find_map(|info| info.narrowed_values.get(&for_ty).copied())
 	}
+
+	fn get_narrowed_or_object(&self, for_ty: TypeId, types: &TypeStore) -> Option<TypeId> {
+		let value = self.get_narrowed(for_ty);
+		if let Some(value) = value {
+			Some(value)
+		} else if let Type::Constructor(crate::types::Constructor::ConditionalResult {
+			condition,
+			truthy_result,
+			otherwise_result,
+			result_union: _,
+		}) = types.get_type_by_id(for_ty)
+		{
+			let narrowed_condition = self.get_narrowed(*condition)?;
+			if let crate::Decidable::Known(condition) =
+				crate::types::is_type_truthy_falsy(narrowed_condition, types)
+			{
+				let value = if condition { truthy_result } else { otherwise_result };
+				Some(*value)
+			} else {
+				None
+			}
+		} else {
+			value
+		}
+	}
 }
 
 pub struct ModuleInformation<'a> {
@@ -393,13 +418,61 @@ pub fn merge_info(
 			onto.variable_current_value.insert(var, new);
 		}
 
-		// TODO temp fix for `... ? { ... } : { ... }`. Breaks for the fact that property
-		// properties might be targeting something above the current condition (e.g. `x ? (y.a = 2) : false`);
-		onto.current_properties.extend(truthy.current_properties.drain());
-		if let Some(ref mut otherwise) = otherwise {
-			onto.current_properties.extend(otherwise.current_properties.drain());
+		// TODO temp fix for `... ? { ... } : { ... }`.
+		// TODO add undefineds to sides etc
+		for (on, properties) in truthy.current_properties {
+			// let properties = properties
+			// 	.into_iter()
+			// 	.map(|(publicity, key, value)| {
+			// 		let falsy_environment_property = otherwise
+			// 			.as_mut()
+			// 			.and_then(|otherwise| {
+			// 				pick_out_property(&mut otherwise.current_properties, (publicity, key), onto, types)
+			// 			});
+
+			// 		if let Some(existing) = falsy_environment_property {
+			// 			// Merging more complex properties has lots of issues
+			// 			todo!()
+			// 		} else {
+			// 			(publicity, key, PropertyValue::ConditionallyExists { condition, value })
+			// 		}
+			// 	})
+			// 	.collect();
+
+			if let Some(existing) = onto.current_properties.get_mut(&on) {
+				existing.extend(properties);
+			} else {
+				onto.current_properties.insert(on, properties);
+			}
+		}
+
+		if let Some(otherwise) = otherwise {
+			for (on, properties) in otherwise.current_properties {
+				if let Some(existing) = onto.current_properties.get_mut(&on) {
+					existing.extend(properties);
+				} else {
+					onto.current_properties.insert(on, properties);
+				}
+			}
 		}
 
 		// TODO set more information?
 	}
 }
+
+// `info_chain` and `types` are a bit excess, but `key_matches` requires it
+// TODO needs to delete afterwards, to block it out for subsequent
+fn _pick_out_property(
+	from: &mut Properties,
+	(want_publicity, want_key): (Publicity, &PropertyKey<'static>),
+	info_chain: &impl InformationChain,
+	types: &TypeStore,
+) -> Option<(Publicity, PropertyKey<'static>, PropertyValue)> {
+	from.iter()
+		.position(|(publicity, key, _)| {
+			*publicity == want_publicity
+				&& crate::types::key_matches((key, None), (want_key, None), info_chain, types).0
+		})
+		// TODO replace with deleted?
+		.map(|idx| from.remove(idx))
+}
diff --git a/checker/src/context/mod.rs b/checker/src/context/mod.rs
index 25126536..8749cdbe 100644
--- a/checker/src/context/mod.rs
+++ b/checker/src/context/mod.rs
@@ -920,9 +920,7 @@ impl<T: ContextType> Context<T> {
 		for (on, constraint) in object_constraints {
 			match self.info.object_constraints.entry(on) {
 				Entry::Occupied(mut existing) => {
-					let new = types
-						.new_and_type(*existing.get(), constraint)
-						.expect("creating impossible restriction");
+					let new = types.new_and_type(*existing.get(), constraint);
 					existing.insert(new);
 				}
 				Entry::Vacant(v) => {
@@ -980,11 +978,11 @@ impl<T: ContextType> Context<T> {
 	}
 
 	pub(crate) fn get_prototype(&self, on: TypeId) -> TypeId {
-		if let Some(prototype) = self.info.prototypes.get(&on) {
+		if let Some(prototype) = self.get_chain_of_info().find_map(|info| info.prototypes.get(&on))
+		{
 			*prototype
-		} else if let Some(parent) = self.context_type.get_parent() {
-			get_on_ctx!(parent.get_prototype(on))
 		} else {
+			crate::utilities::notify!("Could not find prototype");
 			TypeId::OBJECT_TYPE
 		}
 	}
@@ -1017,6 +1015,7 @@ pub(crate) fn get_value_of_variable(
 	info: &impl InformationChain,
 	on: VariableId,
 	closures: Option<&impl ClosureChain>,
+	types: &TypeStore,
 ) -> Option<TypeId> {
 	for fact in info.get_chain_of_info() {
 		let res = if let Some(closures) = closures {
@@ -1032,7 +1031,7 @@ pub(crate) fn get_value_of_variable(
 
 		// TODO in remaining info, don't loop again
 		if let Some(res) = res {
-			let narrowed = info.get_narrowed(res);
+			let narrowed = info.get_narrowed_or_object(res, types);
 			return Some(narrowed.unwrap_or(res));
 		}
 	}
diff --git a/checker/src/context/root.rs b/checker/src/context/root.rs
index 69f0ffd7..2d57e70a 100644
--- a/checker/src/context/root.rs
+++ b/checker/src/context/root.rs
@@ -76,8 +76,8 @@ impl RootContext {
 			("Capitalize".to_owned(), TypeId::STRING_CAPITALIZE),
 			("Uncapitalize".to_owned(), TypeId::STRING_UNCAPITALIZE),
 			("NoInfer".to_owned(), TypeId::NO_INFER),
-			("InclusiveRange".to_owned(), TypeId::INCLUSIVE_RANGE),
-			("ExclusiveRange".to_owned(), TypeId::EXCLUSIVE_RANGE),
+			("GreaterThan".to_owned(), TypeId::GREATER_THAN),
+			("LessThan".to_owned(), TypeId::LESS_THAN),
 			("MultipleOf".to_owned(), TypeId::MULTIPLE_OF),
 			("NotNotANumber".to_owned(), TypeId::NUMBER_BUT_NOT_NOT_A_NUMBER),
 			("Not".to_owned(), TypeId::NOT_RESTRICTION),
diff --git a/checker/src/diagnostics.rs b/checker/src/diagnostics.rs
index 78943ade..2d1afed0 100644
--- a/checker/src/diagnostics.rs
+++ b/checker/src/diagnostics.rs
@@ -423,7 +423,7 @@ pub(crate) enum TypeCheckError<'a> {
 	#[allow(clippy::upper_case_acronyms)]
 	VariableUsedInTDZ(VariableUsedInTDZ),
 	InvalidMathematicalOrBitwiseOperation {
-		operator: crate::features::operations::MathematicalAndBitwise,
+		operator: crate::features::operations::MathematicalOrBitwiseOperation,
 		lhs: TypeStringRepresentation,
 		rhs: TypeStringRepresentation,
 		position: SpanWithSource,
@@ -958,6 +958,7 @@ pub enum TypeCheckWarning {
 	DisjointEquality {
 		lhs: TypeStringRepresentation,
 		rhs: TypeStringRepresentation,
+		result: bool,
 		position: SpanWithSource,
 	},
 	ItemMustBeUsedWithFlag {
@@ -1039,11 +1040,15 @@ impl From<TypeCheckWarning> for Diagnostic {
 					kind,
 				}
 			}
-			TypeCheckWarning::DisjointEquality { lhs, rhs, position } => Diagnostic::Position {
-				reason: format!("This equality is always false as {lhs} and {rhs} have no overlap"),
-				position,
-				kind,
-			},
+			TypeCheckWarning::DisjointEquality { lhs, rhs, position, result } => {
+				Diagnostic::Position {
+					reason: format!(
+						"This equality is always {result} as {lhs} and {rhs} have no overlap"
+					),
+					position,
+					kind,
+				}
+			}
 		}
 	}
 }
diff --git a/checker/src/events/application.rs b/checker/src/events/application.rs
index d3ddba9f..dc6db249 100644
--- a/checker/src/events/application.rs
+++ b/checker/src/events/application.rs
@@ -88,6 +88,7 @@ pub(crate) fn apply_events(
 									top_environment,
 									*id,
 									Some(type_arguments),
+									types,
 								);
 								if let Some(ty) = value {
 									ty
@@ -778,6 +779,11 @@ pub(crate) fn apply_events(
 					};
 					type_arguments.set_during_application(*referenced_in_scope_as, new_function_ty);
 				}
+				super::MiscellaneousEvents::MarkFunctionAsCalled(id) => {
+					if top_environment.is_always_run() {
+						types.called_functions.insert(*id);
+					}
+				}
 			},
 			Event::FinalEvent(final_event) => {
 				// I think this is okay
diff --git a/checker/src/events/mod.rs b/checker/src/events/mod.rs
index a376db6f..7bf999d8 100644
--- a/checker/src/events/mod.rs
+++ b/checker/src/events/mod.rs
@@ -185,6 +185,9 @@ pub enum MiscellaneousEvents {
 	},
 	/// Creates a new function or class
 	CreateConstructor { referenced_in_scope_as: TypeId, function: FunctionId },
+	/// While [`Event::CallsType`] marks callbacks (and parameters) as being called, it does not mark regular function as being called
+	/// this event has no effect but does add it to the global `HashSet` of called types
+	MarkFunctionAsCalled(FunctionId),
 }
 
 /// A break in application
@@ -252,7 +255,6 @@ pub enum ApplicationResult {
 		position: SpanWithSource,
 	},
 	/// From a `throw ***` statement (or expression).
-	///
 	Throw {
 		thrown: TypeId,
 		position: SpanWithSource,
diff --git a/checker/src/events/printing.rs b/checker/src/events/printing.rs
index 9e128329..72f86321 100644
--- a/checker/src/events/printing.rs
+++ b/checker/src/events/printing.rs
@@ -162,6 +162,9 @@ pub fn debug_effects<C: InformationChain>(
 				buf.push_str("end");
 			}
 			Event::Miscellaneous(misc) => match misc {
+				super::MiscellaneousEvents::MarkFunctionAsCalled(_) => {
+					buf.push_str("Calls inlined function");
+				}
 				super::MiscellaneousEvents::Has { .. } => {
 					buf.push_str("Has");
 				}
diff --git a/checker/src/features/assignments.rs b/checker/src/features/assignments.rs
index 486aab57..2813e7b3 100644
--- a/checker/src/features/assignments.rs
+++ b/checker/src/features/assignments.rs
@@ -5,7 +5,7 @@ use crate::{
 	TypeId,
 };
 
-use super::operations::{LogicalOperator, MathematicalAndBitwise};
+use super::operations::{LogicalOperator, MathematicalOrBitwiseOperation};
 
 /// A single or multiple items to assign to
 pub enum Assignable<A: crate::ASTImplementation> {
@@ -50,7 +50,7 @@ pub enum AssignableArrayDestructuringField<A: crate::ASTImplementation> {
 /// Increment and decrement are are not binary add subtract as they cast their lhs to number
 pub enum AssignmentKind {
 	Assign,
-	PureUpdate(MathematicalAndBitwise),
+	PureUpdate(MathematicalOrBitwiseOperation),
 	ConditionalUpdate(LogicalOperator),
 	IncrementOrDecrement(IncrementOrDecrement, AssignmentReturnStatus),
 }
diff --git a/checker/src/features/conditional.rs b/checker/src/features/conditional.rs
index caa7b332..d46db3a3 100644
--- a/checker/src/features/conditional.rs
+++ b/checker/src/features/conditional.rs
@@ -47,6 +47,7 @@ where
 			&mut checking_data.types,
 		);
 
+		crate::utilities::notify!("Narrowed value {:?} in true branch", values);
 		truthy_environment.info.narrowed_values = values;
 
 		let result = then_evaluate(&mut truthy_environment, checking_data);
@@ -81,6 +82,7 @@ where
 			&mut checking_data.types,
 		);
 
+		crate::utilities::notify!("Narrowed value {:?} in false branch", values);
 		falsy_environment.info.narrowed_values = values;
 
 		let result = else_evaluate(&mut falsy_environment, checking_data);
diff --git a/checker/src/features/constant_functions.rs b/checker/src/features/constant_functions.rs
index a7b18ccf..ecf8497b 100644
--- a/checker/src/features/constant_functions.rs
+++ b/checker/src/features/constant_functions.rs
@@ -146,6 +146,13 @@ pub(crate) fn call_constant_function(
 				Err(ConstantFunctionError::CannotComputeConstant)
 			}
 		}
+		"debug_number" => {
+			let arg = arguments.iter().next().unwrap();
+			Ok(ConstantOutput::Diagnostic(format!(
+				"number: {:?}",
+				crate::types::intrinsics::get_range_and_mod_class(arg.value, types)
+			)))
+		}
 		"print_type" | "debug_type" | "print_and_debug_type" | "debug_type_independent" => {
 			fn to_string(
 				print: bool,
@@ -291,12 +298,16 @@ pub(crate) fn call_constant_function(
 		}
 		"setPrototypeOf" => {
 			if let [first, second] = arguments {
-				let _prototype = environment
-					.info
-					.prototypes
-					.insert(first.non_spread_type().unwrap(), second.non_spread_type().unwrap());
-				// TODO
-				Ok(ConstantOutput::Value(TypeId::UNDEFINED_TYPE))
+				if let (Ok(first), Ok(second)) = (first.non_spread_type(), second.non_spread_type())
+				{
+					let _prototype = environment.info.prototypes.insert(first, second);
+
+					crate::utilities::notify!("Set {:?} prototype to {:?}", first, second);
+
+					Ok(ConstantOutput::Value(first))
+				} else {
+					Err(ConstantFunctionError::CannotComputeConstant)
+				}
 			} else {
 				Err(ConstantFunctionError::CannotComputeConstant)
 			}
diff --git a/checker/src/features/functions.rs b/checker/src/features/functions.rs
index 849e5bf8..43081834 100644
--- a/checker/src/features/functions.rs
+++ b/checker/src/features/functions.rs
@@ -822,6 +822,7 @@ where
 		let closes_over = create_closed_over_references(
 			&function_environment.context_type.closed_over_references,
 			&function_environment,
+			&checking_data.types,
 		);
 
 		let Syntax {
@@ -1112,7 +1113,7 @@ pub fn new_name_expected_object(
 		&mut environment.info,
 	);
 
-	types.new_and_type(expected, name_object.build_object()).unwrap()
+	types.new_and_type(expected, name_object.build_object()) // .unwrap()
 }
 
 /// Reverse of the above
@@ -1137,6 +1138,7 @@ pub fn extract_name(expecting: TypeId, types: &TypeStore, environment: &Environm
 	}
 }
 
+#[must_use]
 pub fn class_generics_to_function_generics(
 	prototype: TypeId,
 	types: &TypeStore,
diff --git a/checker/src/features/iteration.rs b/checker/src/features/iteration.rs
index d7b2c0cf..e638cbb1 100644
--- a/checker/src/features/iteration.rs
+++ b/checker/src/features/iteration.rs
@@ -138,8 +138,6 @@ pub fn synthesise_iteration<T: crate::ReadFromFS, A: crate::ASTImplementation>(
 			// }
 		}
 		IterationBehavior::DoWhile(condition) => {
-			// let is_do_while = matches!(behavior, IterationBehavior::DoWhile(..));
-
 			// Same as above but condition is evaluated at end. Don't know whether events should be evaluated once...?
 			let (condition, result, ..) = environment.new_lexical_environment_fold_into_parent(
 				Scope::Iteration { label },
@@ -252,6 +250,20 @@ pub fn synthesise_iteration<T: crate::ReadFromFS, A: crate::ASTImplementation>(
 										TypeId::TRUE
 									};
 
+									let values =
+										super::narrowing::narrow_based_on_expression_into_vec(
+											condition,
+											false,
+											environment,
+											&mut checking_data.types,
+										);
+
+									crate::utilities::notify!(
+										"Narrowed values in loop {:?}",
+										values
+									);
+									environment.info.narrowed_values = values;
+
 									// TODO not always needed
 									add_loop_described_break_event(
 										condition,
@@ -494,8 +506,9 @@ pub(crate) fn run_iteration_block(
 					let events_to_be_applied = iterations * events.len();
 
 					crate::utilities::notify!(
-						"count = {:?}. with",
-						(events_to_be_applied, input.max_inline)
+						"applying {:?} events (max {:?})",
+						events_to_be_applied,
+						input.max_inline
 					);
 					(events_to_be_applied < input.max_inline as usize).then_some(iterations)
 				});
@@ -508,20 +521,6 @@ pub(crate) fn run_iteration_block(
 
 				crate::utilities::notify!("Running {} times", iterations);
 
-				// crate::utilities::notify!(
-				// 	"Evaluating a constant amount of iterations {:?}",
-				// 	iterations
-				// );
-
-				// if let InitialVariablesInput::Calculated(initial) = initial {
-				// 	for (variable_id, initial_value) in initial.iter() {
-				// 		invocation_context
-				// 			.get_latest_info(top_environment)
-				// 			.variable_current_value
-				// 			.insert(*variable_id, *initial_value);
-				// 	}
-				// }
-
 				run_iteration_loop(
 					invocation_context,
 					iterations,
@@ -672,7 +671,7 @@ fn evaluate_unknown_iteration_for_loop(
 	let initial = match initial {
 		RunBehavior::Run => ClosedOverVariables(Default::default()),
 		RunBehavior::References(v) => {
-			crate::features::create_closed_over_references(&v, top_environment)
+			crate::features::create_closed_over_references(&v, top_environment, types)
 		}
 	};
 
@@ -909,8 +908,9 @@ fn calculate_result_of_loop(
 					result: _,
 				}) = value_after_running_expressions_in_loop
 				{
+					let assignment = crate::types::helpers::get_origin(*assignment, types);
 					debug_assert!(
-						assignment == less_than_reference_type_id,
+						assignment == *less_than_reference_type_id,
 						"incrementor free variable type not the same as condition free variable type?"
 					);
 
diff --git a/checker/src/features/mod.rs b/checker/src/features/mod.rs
index 572559fe..0af0ae5f 100644
--- a/checker/src/features/mod.rs
+++ b/checker/src/features/mod.rs
@@ -28,7 +28,7 @@ use crate::{
 	diagnostics::TypeStringRepresentation,
 	events::RootReference,
 	types::{
-		get_constraint,
+		get_constraint, helpers,
 		logical::{Logical, LogicalOrValid},
 		properties, PartiallyAppliedGenerics, TypeStore,
 	},
@@ -219,6 +219,7 @@ fn get_promise_value(constraint: TypeId, types: &TypeStore) -> Option<TypeId> {
 pub(crate) fn create_closed_over_references(
 	closed_over_references: &ClosedOverReferencesInScope,
 	current_environment: &Environment,
+	types: &TypeStore,
 ) -> ClosedOverVariables {
 	ClosedOverVariables(
 		closed_over_references
@@ -229,9 +230,9 @@ pub(crate) fn create_closed_over_references(
 						let c = None::<
 							&crate::types::generics::substitution::SubstitutionArguments<'static>,
 						>;
-						let get_value_of_variable =
-							get_value_of_variable(current_environment, *on, c);
-						let ty = if let Some(value) = get_value_of_variable {
+						let value = get_value_of_variable(current_environment, *on, c, types);
+
+						let ty = if let Some(value) = value {
 							value
 						} else {
 							// TODO think we are getting rid of this
@@ -430,58 +431,72 @@ pub(crate) fn has_property(
 	information: &impl InformationChain,
 	types: &mut TypeStore,
 ) -> TypeId {
-	match types.get_type_by_id(rhs) {
-		Type::Interface { .. }
-		| Type::Class { .. }
-		| Type::Constant(_)
-		| Type::FunctionReference(_)
-		| Type::Object(_)
-		| Type::PartiallyAppliedGenerics(_)
-		| Type::And(_, _)
-		| Type::SpecialObject(_)
-		| Type::Narrowed { .. }
-		| Type::AliasTo { .. } => {
-			let result = properties::get_property_unbound(
-				(rhs, None),
-				(publicity, key, None),
-				false,
-				information,
-				types,
-			);
-			match result {
-				Ok(LogicalOrValid::Logical(result)) => match result {
-					Logical::Pure(_) => TypeId::TRUE,
-					Logical::Or { .. } => {
-						crate::utilities::notify!("or or implies `in`");
-						TypeId::UNIMPLEMENTED_ERROR_TYPE
-					}
-					Logical::Implies { .. } => {
-						crate::utilities::notify!("or or implies `in`");
+	if let Some((condition, truthy, falsy)) = helpers::get_type_as_conditional(rhs, types) {
+		let truthy_result = has_property((publicity, key), truthy, information, types);
+		let otherwise_result = has_property((publicity, key), falsy, information, types);
+		types.new_conditional_type(condition, truthy_result, otherwise_result)
+	} else {
+		match types.get_type_by_id(rhs) {
+			Type::Interface { .. }
+			| Type::Class { .. }
+			| Type::Constant(_)
+			| Type::FunctionReference(_)
+			| Type::Object(_)
+			| Type::PartiallyAppliedGenerics(_)
+			| Type::And(_, _)
+			| Type::SpecialObject(_)
+			| Type::Narrowed { .. }
+			| Type::AliasTo { .. } => {
+				let result = properties::get_property_unbound(
+					(rhs, None),
+					(publicity, key, None),
+					false,
+					information,
+					types,
+				);
+				match result {
+					Ok(LogicalOrValid::Logical(result)) => match result {
+						Logical::Pure(_) => TypeId::TRUE,
+						Logical::Or { condition, left, right } => {
+							// TODO some problems here, need to recurse
+							let (left, right) = (*left, *right);
+							if let (LogicalOrValid::Logical(_), LogicalOrValid::Logical(_)) =
+								(&left, right)
+							{
+								TypeId::TRUE
+							} else if let LogicalOrValid::Logical(_) = left {
+								condition
+							} else {
+								types.new_logical_negation_type(condition)
+							}
+						}
+						Logical::Implies { .. } => {
+							crate::utilities::notify!("or or implies `in`");
+							TypeId::UNIMPLEMENTED_ERROR_TYPE
+						}
+						Logical::BasedOnKey { .. } => {
+							crate::utilities::notify!("mapped in");
+							TypeId::UNIMPLEMENTED_ERROR_TYPE
+						}
+					},
+					Ok(LogicalOrValid::NeedsCalculation(result)) => {
+						crate::utilities::notify!("TODO {:?}", result);
 						TypeId::UNIMPLEMENTED_ERROR_TYPE
 					}
-					Logical::BasedOnKey { .. } => {
-						crate::utilities::notify!("mapped in");
-						TypeId::UNIMPLEMENTED_ERROR_TYPE
+					Err(err) => {
+						crate::utilities::notify!("TODO {:?}", err);
+						TypeId::FALSE
 					}
-				},
-				Ok(LogicalOrValid::NeedsCalculation(result)) => {
-					crate::utilities::notify!("TODO {:?}", result);
-					TypeId::UNIMPLEMENTED_ERROR_TYPE
-				}
-				Err(err) => {
-					crate::utilities::notify!("TODO {:?}", err);
-					TypeId::FALSE
 				}
 			}
-		}
-		Type::Or(_, _) => {
-			crate::utilities::notify!("Condtionally");
-			TypeId::UNIMPLEMENTED_ERROR_TYPE
-		}
-		Type::RootPolyType(_) | Type::Constructor(_) => {
-			crate::utilities::notify!("Queue event / create dependent");
-			let constraint = get_constraint(rhs, types).unwrap();
-			has_property((publicity, key), constraint, information, types)
+			Type::Or(_, _) => {
+				unreachable!()
+			}
+			Type::RootPolyType(_) | Type::Constructor(_) => {
+				crate::utilities::notify!("Queue event / create dependent");
+				let constraint = get_constraint(rhs, types).unwrap();
+				has_property((publicity, key), constraint, information, types)
+			}
 		}
 	}
 }
diff --git a/checker/src/features/narrowing.rs b/checker/src/features/narrowing.rs
index 1720ff17..52d840be 100644
--- a/checker/src/features/narrowing.rs
+++ b/checker/src/features/narrowing.rs
@@ -2,13 +2,13 @@ use crate::{
 	context::InformationChain,
 	types::{
 		self, as_logical_and, as_logical_not, as_logical_or,
-		helpers::{get_conditional, get_origin},
+		helpers::{get_origin, get_type_as_conditional},
 		properties, Constant, Constructor, PolyNature, TypeOperator, TypeStore,
 	},
 	Map, Type, TypeId,
 };
 
-use super::operations::{CanonicalEqualityAndInequality, MathematicalAndBitwise};
+use super::operations::{CanonicalEqualityAndInequality, MathematicalOrBitwiseOperation};
 
 pub fn narrow_based_on_expression_into_vec(
 	condition: TypeId,
@@ -18,6 +18,7 @@ pub fn narrow_based_on_expression_into_vec(
 ) -> Map<TypeId, TypeId> {
 	let mut into = Default::default();
 	narrow_based_on_expression(condition, negate, &mut into, information, types);
+	into.iter_mut().for_each(|(on, value)| *value = types.new_narrowed(*on, *value));
 	into
 }
 
@@ -36,28 +37,41 @@ pub fn narrow_based_on_expression(
 				operator: CanonicalEqualityAndInequality::StrictEqual,
 				rhs,
 			} => {
+				let lhs_type = types.get_type_by_id(*lhs);
 				if let Type::Constructor(Constructor::TypeOperator(TypeOperator::TypeOf(on))) =
-					types.get_type_by_id(*lhs)
+					lhs_type
 				{
-					let from = get_origin(*on, types);
+					let from = *on;
+					let origin = get_origin(from, types);
+
 					if let Type::Constant(Constant::String(c)) = types.get_type_by_id(*rhs) {
-						let narrowed_to = crate::features::string_name_to_type(c);
-						if let Some(narrowed_to) = narrowed_to {
+						let type_from_name = crate::features::string_name_to_type(c);
+						if let Some(type_from_name) = type_from_name {
 							if negate {
-								let mut result = Vec::new();
-								build_union_from_filter(
-									from,
-									Filter::Not(&Filter::IsType(narrowed_to)),
-									&mut result,
-									information,
-									types,
-								);
-								let narrowed_to = types.new_or_type_from_iterator(result);
-								let narrowed = types.new_narrowed(from, narrowed_to);
-								into.insert(from, narrowed);
+								crate::utilities::notify!("{:?}", from);
+								// TODO temp fix
+								let narrowed_to = if let Some(TypeId::ANY_TYPE) =
+									crate::types::get_constraint(from, types)
+								{
+									crate::types::intrinsics::new_intrinsic(
+										&crate::types::intrinsics::Intrinsic::Not,
+										type_from_name,
+										types,
+									)
+								} else {
+									let mut result = Vec::new();
+									build_union_from_filter(
+										from,
+										Filter::Not(&Filter::IsType(type_from_name)),
+										&mut result,
+										information,
+										types,
+									);
+									types.new_or_type_from_iterator(result)
+								};
+								into.insert(origin, narrowed_to);
 							} else {
-								let narrowed = types.new_narrowed(from, narrowed_to);
-								into.insert(from, narrowed);
+								into.insert(origin, type_from_name);
 							}
 						} else {
 							crate::utilities::notify!("Type name was (shouldn't be here)");
@@ -67,83 +81,102 @@ pub fn narrow_based_on_expression(
 					}
 				} else if let Type::Constructor(Constructor::BinaryOperator {
 					lhs: operand,
-					operator: MathematicalAndBitwise::Modulo,
+					operator: MathematicalOrBitwiseOperation::Modulo,
 					rhs: modulo,
 					result: _,
-				}) = types.get_type_by_id(*lhs)
+				}) = lhs_type
 				{
-					if *rhs == TypeId::ZERO {
-						crate::utilities::notify!("TODO only if sensible");
+					if negate {
+						crate::utilities::notify!("TODO do we not divisable by?");
+						return;
+					}
+					let (operand, rhs, modulo) = (*operand, *rhs, *modulo);
+					let operand = get_origin(operand, types);
+					crate::utilities::notify!("Here {:?}", types.get_type_by_id(modulo));
+					let narrowed_to = crate::types::intrinsics::new_intrinsic(
+						&crate::types::intrinsics::Intrinsic::MultipleOf,
+						modulo,
+						types,
+					);
 
-						let (from, modulo) = (*operand, *modulo);
-						if negate {
-							crate::utilities::notify!("TODO do we not divisable by?");
-						} else {
-							let narrowed_to = crate::types::intrinsics::new_intrinsic(
-								&crate::types::intrinsics::Intrinsic::MultipleOf,
-								modulo,
-								types,
-							);
-							let narrowed = types.new_narrowed(from, narrowed_to);
-							into.insert(from, narrowed);
-						}
+					// TODO also from == x - 1 etc
+					let narrowed_to = if rhs == TypeId::ZERO {
+						narrowed_to
 					} else {
-						crate::utilities::notify!("maybe subtract LHS");
-					}
+						types.register_type(Type::Constructor(
+							crate::types::Constructor::BinaryOperator {
+								lhs: narrowed_to,
+								operator: super::operations::MathematicalOrBitwiseOperation::Add,
+								rhs,
+								result: TypeId::NUMBER_TYPE,
+							},
+						))
+					};
+					into.insert(operand, narrowed_to);
 				} else {
-					if let Type::RootPolyType(PolyNature::Parameter { .. }) =
-						types.get_type_by_id(*lhs)
-					{
-						crate::utilities::notify!(
-							"lhs is {:?} with {:?}",
-							lhs,
-							types.get_type_by_id(*rhs)
-						);
+					if let Type::RootPolyType(PolyNature::Parameter { .. }) = lhs_type {
+						crate::utilities::notify!("lhs is {:?} with {:?}", lhs_type, rhs);
 					}
 
 					if negate && lhs == rhs {
-						into.insert(*lhs, types.new_narrowed(*lhs, TypeId::NOT_NOT_A_NUMBER));
+						into.insert(*lhs, TypeId::NOT_NOT_A_NUMBER);
 						return;
 					}
 
-					let lhs = get_origin(*lhs, types);
+					let lhs = *lhs;
+					let rhs = *rhs;
 
 					let result = if negate {
 						// TODO wip
-						let narrowed_to = if get_conditional(lhs, types).is_some() {
+						let narrowed_to = if get_type_as_conditional(lhs, types).is_some() {
 							let mut result = Vec::new();
 							build_union_from_filter(
 								lhs,
-								Filter::Not(&Filter::IsType(*rhs)),
+								Filter::Not(&Filter::IsType(rhs)),
 								&mut result,
 								information,
 								types,
 							);
 							// crate::utilities::notify!("Here {:?} {:?}", (filter, lhs), result);
 
-							let narrowed_to = types.new_or_type_from_iterator(result);
-							types.new_narrowed(lhs, narrowed_to)
+							types.new_or_type_from_iterator(result)
 						} else {
 							crate::types::intrinsics::new_intrinsic(
 								&crate::types::intrinsics::Intrinsic::Not,
-								*rhs,
+								rhs,
 								types,
 							)
 						};
-						types.new_narrowed(lhs, narrowed_to)
+						narrowed_to
 					} else {
-						*rhs
+						rhs
 					};
 
 					into.insert(lhs, result);
 
-					// PROPERTY HERE
-					if let Type::Constructor(Constructor::Property {
+					// CONDITION NARROWING HERE ((x ? 1 : 2) == 1 => x)
+					// There are missed conditons around things like `typeof` etc (oh well)
+					// it should be done higher up
+					if let Type::Constructor(Constructor::ConditionalResult {
+						condition,
+						truthy_result,
+						otherwise_result,
+						result_union: _,
+					}) = types.get_type_by_id(get_origin(lhs, types))
+					{
+						if crate::types::helpers::type_equal(*truthy_result, rhs, types) {
+							narrow_based_on_expression(*condition, false, into, information, types);
+						} else if crate::types::helpers::type_equal(*otherwise_result, rhs, types) {
+							narrow_based_on_expression(*condition, true, into, information, types);
+						}
+					}
+					// PROPERTY NARROWING HERE (x.a: b => x: {a: b})
+					else if let Type::Constructor(Constructor::Property {
 						on,
 						under,
 						result: _,
 						mode: _,
-					}) = types.get_type_by_id(lhs)
+					}) = types.get_type_by_id(get_origin(lhs, types))
 					{
 						let on = *on;
 						let narrowed_to = if !negate
@@ -170,7 +203,7 @@ pub fn narrow_based_on_expression(
 						// 	on,
 						// 	types.get_type_by_id(narrowed_to)
 						// );
-						into.insert(on, types.new_narrowed(on, narrowed_to));
+						into.insert(on, narrowed_to);
 					}
 				}
 			}
@@ -179,26 +212,41 @@ pub fn narrow_based_on_expression(
 				operator: CanonicalEqualityAndInequality::LessThan,
 				rhs,
 			} => {
-				let (lhs, rhs) = (*lhs, *rhs);
 				if negate {
+					crate::utilities::notify!("Skipping negate on less");
 					return;
 				}
+				let lhs = get_origin(*lhs, types);
+				let rhs = get_origin(*rhs, types);
 				if types.get_type_by_id(lhs).is_dependent() {
 					let narrowed_to = crate::types::intrinsics::new_intrinsic(
 						&crate::types::intrinsics::Intrinsic::LessThan,
 						rhs,
 						types,
 					);
-					let narrowed = types.new_narrowed(lhs, narrowed_to);
-					into.insert(lhs, narrowed);
-				} else if types.get_type_by_id(rhs).is_dependent() {
+					// TODO need to merge. This is very bad
+					let narrowed_to = if let Some(existing) = into.get(&lhs) {
+						crate::utilities::notify!("Here");
+						types.new_and_type(*existing, narrowed_to)
+					} else {
+						narrowed_to
+					};
+					into.insert(lhs, narrowed_to);
+				}
+				if types.get_type_by_id(rhs).is_dependent() {
 					let narrowed_to = crate::types::intrinsics::new_intrinsic(
 						&crate::types::intrinsics::Intrinsic::GreaterThan,
 						lhs,
 						types,
 					);
-					let narrowed = types.new_narrowed(rhs, narrowed_to);
-					into.insert(rhs, narrowed);
+					// TODO need to merge. This is very bad
+					let narrowed_to = if let Some(existing) = into.get(&rhs) {
+						crate::utilities::notify!("Here");
+						types.new_and_type(narrowed_to, *existing)
+					} else {
+						narrowed_to
+					};
+					into.insert(rhs, narrowed_to);
 				}
 			}
 			Constructor::TypeOperator(TypeOperator::IsPrototype { lhs, rhs_prototype }) => {
@@ -214,8 +262,7 @@ pub fn narrow_based_on_expression(
 					build_union_from_filter(constraint, filter, &mut result, information, types);
 					types.new_or_type_from_iterator(result)
 				};
-				let narrowed = types.new_narrowed(lhs, narrowed_to);
-				into.insert(lhs, narrowed);
+				into.insert(lhs, narrowed_to);
 			}
 			Constructor::TypeExtends(crate::types::TypeExtends { item, extends }) => {
 				let (item, extends) = (*item, *extends);
@@ -231,8 +278,7 @@ pub fn narrow_based_on_expression(
 					build_union_from_filter(constraint, filter, &mut result, information, types);
 					types.new_or_type_from_iterator(result)
 				};
-				let narrowed = types.new_narrowed(item, narrowed_to);
-				into.insert(item, narrowed);
+				into.insert(item, narrowed_to);
 			}
 			Constructor::TypeOperator(TypeOperator::HasProperty(on, under)) => {
 				let on = *on;
@@ -253,11 +299,10 @@ pub fn narrow_based_on_expression(
 					);
 					types.new_or_type_from_iterator(items)
 				};
-				into.insert(on, types.new_narrowed(on, narrowed_to));
+				into.insert(on, narrowed_to);
 			}
 			constructor => {
 				if let Some(condition) = as_logical_not(constructor, types) {
-					crate::utilities::notify!("Here");
 					narrow_based_on_expression(condition, !negate, into, information, types);
 				} else if let Some((lhs, rhs)) = as_logical_and(constructor, types) {
 					// De Morgan's laws
@@ -311,8 +356,6 @@ pub fn narrow_based_on_expression(
 									crate::types::get_constraint(rhs_request, types)
 										.unwrap_or(rhs_request),
 								);
-								// TODO
-								// let narrowed = types.new_narrowed(rhs, narrowed_to);
 								into.insert(on, types.new_or_type(lhs_request, rhs_request));
 							} else {
 								// Only when we have two results is it useful
@@ -338,10 +381,7 @@ pub fn narrow_based_on_expression(
 				types,
 			);
 			let narrowed_to = types.new_or_type_from_iterator(result);
-			into.insert(
-				condition,
-				types.register_type(Type::Narrowed { from: condition, narrowed_to }),
-			);
+			into.insert(condition, narrowed_to);
 		}
 	}
 }
@@ -364,10 +404,11 @@ pub(crate) enum Filter<'a> {
 static NULL_OR_UNDEFINED: Filter<'static> = Filter::NullOrUndefined;
 pub(crate) static NOT_NULL_OR_UNDEFINED: Filter<'static> = Filter::Not(&NULL_OR_UNDEFINED);
 
-static FASLY: Filter<'static> = Filter::Falsy;
+pub(crate) static FASLY: Filter<'static> = Filter::Falsy;
 pub(crate) static NOT_FASLY: Filter<'static> = Filter::Not(&FASLY);
 
 impl<'a> Filter<'a> {
+	// Returns `true` if `value` passes filter
 	pub(crate) fn type_matches_filter(
 		&self,
 		value: TypeId,
@@ -415,8 +456,12 @@ impl<'a> Filter<'a> {
 				}
 			}
 			Filter::HasProperty { property, filter } => {
-				let value =
-					types::properties::get_simple_value(information, value, property, types);
+				let value = types::properties::get_simple_property_value(
+					information,
+					value,
+					property,
+					types,
+				);
 				if let Some(value) = value {
 					let matches = filter.type_matches_filter(value, information, types, negate);
 					crate::utilities::notify!("Value {:?}", (value, negate, matches));
@@ -432,6 +477,12 @@ impl<'a> Filter<'a> {
 				(allowed_match && is_null_or_undefined) || (!allowed_match && !is_null_or_undefined)
 			}
 			Filter::Falsy => {
+				let can_be_falsy = [TypeId::NUMBER_TYPE, TypeId::STRING_TYPE, TypeId::BOOLEAN_TYPE];
+
+				if can_be_falsy.contains(&value) {
+					return true;
+				}
+
 				let is_falsy = [
 					TypeId::NULL_TYPE,
 					TypeId::UNDEFINED_TYPE,
@@ -447,6 +498,7 @@ impl<'a> Filter<'a> {
 	}
 }
 
+/// Important that this does not handle `any` well with negated filters. It needs to generate negated types but only has non-mutable access to `TypeStore`
 #[allow(clippy::used_underscore_binding)]
 pub(crate) fn build_union_from_filter(
 	on: TypeId,
@@ -455,11 +507,14 @@ pub(crate) fn build_union_from_filter(
 	information: &impl InformationChain,
 	types: &TypeStore,
 ) {
-	if let Some((_condition, lhs, rhs)) = get_conditional(on, types) {
+	if let Some(constraint) = crate::types::get_constraint(on, types) {
+		build_union_from_filter(constraint, filter, found, information, types);
+	} else if let TypeId::BOOLEAN_TYPE = on {
+		build_union_from_filter(TypeId::TRUE, filter, found, information, types);
+		build_union_from_filter(TypeId::FALSE, filter, found, information, types);
+	} else if let Some((_condition, lhs, rhs)) = get_type_as_conditional(on, types) {
 		build_union_from_filter(lhs, filter, found, information, types);
 		build_union_from_filter(rhs, filter, found, information, types);
-	} else if let Some(constraint) = crate::types::get_constraint(on, types) {
-		build_union_from_filter(constraint, filter, found, information, types);
 	} else {
 		let not_already_added = !found.contains(&on);
 		if not_already_added && filter.type_matches_filter(on, information, types, false) {
diff --git a/checker/src/features/operations.rs b/checker/src/features/operations.rs
deleted file mode 100644
index 78a88f83..00000000
--- a/checker/src/features/operations.rs
+++ /dev/null
@@ -1,849 +0,0 @@
-//! Contains logic for mathematical, bitwise and logical operators
-
-use derive_enum_from_into::EnumFrom;
-use source_map::{Span, SpanWithSource};
-
-use crate::{
-	diagnostics::{TypeCheckError, TypeStringRepresentation},
-	features::conditional::new_conditional_context,
-	types::{
-		cast_as_number, cast_as_string, helpers::simple_subtype, intrinsics, is_type_truthy_falsy,
-		Constructor, PartiallyAppliedGenerics, TypeStore,
-	},
-	CheckingData, Constant, Decidable, Environment, Type, TypeId,
-};
-
-use super::objects::SpecialObject;
-
-/// For these **binary** operations both operands are synthesised
-#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)]
-pub enum MathematicalAndBitwise {
-	Add,
-	Subtract,
-	Multiply,
-	Divide,
-	Modulo,
-	Exponent,
-	BitwiseShiftLeft,
-	BitwiseShiftRight,
-	BitwiseShiftRightUnsigned,
-	BitwiseAnd,
-	BitwiseXOr,
-	BitwiseOr,
-}
-
-#[derive(Clone, Copy, Debug, EnumFrom)]
-pub enum PureBinaryOperation {
-	MathematicalAndBitwise(MathematicalAndBitwise),
-	// Some of these can be reduced
-	EqualityAndInequality(EqualityAndInequality),
-}
-
-/// TODO report errors better here
-pub fn evaluate_pure_binary_operation_handle_errors<
-	T: crate::ReadFromFS,
-	A: crate::ASTImplementation,
->(
-	(lhs, lhs_pos): (TypeId, SpanWithSource),
-	operator: PureBinaryOperation,
-	(rhs, rhs_pos): (TypeId, SpanWithSource),
-	checking_data: &mut CheckingData<T, A>,
-	environment: &mut Environment,
-) -> TypeId {
-	match operator {
-		PureBinaryOperation::MathematicalAndBitwise(operator) => {
-			if let (MathematicalAndBitwise::Exponent, TypeId::ZERO) = (operator, rhs) {
-				// This holds for NaN. Thus can do in every case
-				return TypeId::ONE;
-			}
-
-			let result = evaluate_mathematical_operation(
-				lhs,
-				operator,
-				rhs,
-				environment,
-				&mut checking_data.types,
-				checking_data.options.strict_casts,
-			);
-
-			match result {
-				Ok(result) => result,
-				Err(_err) => {
-					let position = lhs_pos
-						.without_source()
-						.union(rhs_pos.without_source())
-						.with_source(environment.get_source());
-
-					checking_data.diagnostics_container.add_error(
-						TypeCheckError::InvalidMathematicalOrBitwiseOperation {
-							operator,
-							lhs: TypeStringRepresentation::from_type_id(
-								lhs,
-								environment,
-								&checking_data.types,
-								false,
-							),
-							rhs: TypeStringRepresentation::from_type_id(
-								rhs,
-								environment,
-								&checking_data.types,
-								false,
-							),
-							position,
-						},
-					);
-					TypeId::ERROR_TYPE
-				}
-			}
-		}
-		PureBinaryOperation::EqualityAndInequality(operator) => {
-			// Cannot error, but can be always true or false
-			let result = evaluate_equality_inequality_operation(
-				lhs,
-				&operator,
-				rhs,
-				environment,
-				&mut checking_data.types,
-				checking_data.options.strict_casts,
-			);
-
-			if let Ok((result, warning)) = result {
-				if let EqualityAndInequalityResultKind::Disjoint = warning {
-					let position = lhs_pos
-						.without_source()
-						.union(rhs_pos.without_source())
-						.with_source(environment.get_source());
-
-					checking_data.diagnostics_container.add_warning(
-						crate::TypeCheckWarning::DisjointEquality {
-							lhs: TypeStringRepresentation::from_type_id(
-								lhs,
-								environment,
-								&checking_data.types,
-								false,
-							),
-							rhs: TypeStringRepresentation::from_type_id(
-								rhs,
-								environment,
-								&checking_data.types,
-								false,
-							),
-							position,
-						},
-					);
-				}
-
-				result
-			} else {
-				let position = lhs_pos
-					.without_source()
-					.union(rhs_pos.without_source())
-					.with_source(environment.get_source());
-
-				checking_data.diagnostics_container.add_error(
-					crate::TypeCheckError::InvalidEqualityOperation {
-						operator,
-						lhs: TypeStringRepresentation::from_type_id(
-							lhs,
-							environment,
-							&checking_data.types,
-							false,
-						),
-						rhs: TypeStringRepresentation::from_type_id(
-							rhs,
-							environment,
-							&checking_data.types,
-							false,
-						),
-						position,
-					},
-				);
-
-				TypeId::ERROR_TYPE
-			}
-		}
-	}
-}
-
-/// TODO proper error type
-pub fn evaluate_mathematical_operation(
-	lhs: TypeId,
-	operator: MathematicalAndBitwise,
-	rhs: TypeId,
-	info: &impl crate::context::InformationChain,
-	types: &mut TypeStore,
-	strict_casts: bool,
-) -> Result<TypeId, ()> {
-	fn attempt_constant_math_operator(
-		lhs: TypeId,
-		operator: MathematicalAndBitwise,
-		rhs: TypeId,
-		types: &mut TypeStore,
-		strict_casts: bool,
-	) -> Result<TypeId, ()> {
-		if let MathematicalAndBitwise::Add = operator {
-			let constant = match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) {
-				(Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) => {
-					Constant::Number(lhs + rhs)
-				}
-				(Type::Constant(lhs), Type::Constant(rhs)) => {
-					let mut first = cast_as_string(lhs, strict_casts)?;
-					let second = cast_as_string(rhs, strict_casts)?;
-					// Concatenate strings
-					first.push_str(&second);
-					Constant::String(first)
-				}
-				_ => return Err(()),
-			};
-			Ok(types.new_constant_type(constant))
-		} else {
-			match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) {
-				(Type::Constant(c1), Type::Constant(c2)) => {
-					let lhs = cast_as_number(c1, strict_casts).unwrap_or(f64::NAN);
-					let rhs = cast_as_number(c2, strict_casts).unwrap_or(f64::NAN);
-					// TODO hopefully Rust implementation is the same as JS
-					#[allow(clippy::cast_possible_truncation)]
-					let value = match operator {
-						MathematicalAndBitwise::Add => unreachable!(),
-						MathematicalAndBitwise::Subtract => lhs - rhs,
-						MathematicalAndBitwise::Multiply => lhs * rhs,
-						MathematicalAndBitwise::Divide => lhs / rhs,
-						MathematicalAndBitwise::Modulo => lhs % rhs,
-						MathematicalAndBitwise::Exponent => lhs.powf(rhs),
-						MathematicalAndBitwise::BitwiseShiftLeft => {
-							f64::from((lhs as i32).checked_shl(rhs as u32).unwrap_or(0))
-						}
-						MathematicalAndBitwise::BitwiseShiftRight => {
-							f64::from((lhs as i32).checked_shr(rhs as u32).unwrap_or(0))
-						}
-						MathematicalAndBitwise::BitwiseShiftRightUnsigned => {
-							(lhs as i32).wrapping_shr(rhs as u32).into()
-						}
-						MathematicalAndBitwise::BitwiseAnd => {
-							f64::from((lhs as i32) & (rhs as i32))
-						}
-						MathematicalAndBitwise::BitwiseXOr => {
-							f64::from((lhs as i32) ^ (rhs as i32))
-						}
-						MathematicalAndBitwise::BitwiseOr => f64::from((lhs as i32) | (rhs as i32)),
-					};
-					let value = ordered_float::NotNan::try_from(value);
-					let ty = match value {
-						Ok(value) => types.new_constant_type(Constant::Number(value)),
-						Err(_) => TypeId::NAN,
-					};
-					Ok(ty)
-				}
-				_ => Err(()),
-			}
-		}
-	}
-
-	if lhs == TypeId::ERROR_TYPE || rhs == TypeId::ERROR_TYPE {
-		return Ok(TypeId::ERROR_TYPE);
-	}
-
-	let is_dependent =
-		types.get_type_by_id(lhs).is_dependent() || types.get_type_by_id(rhs).is_dependent();
-
-	if is_dependent {
-		let can_be_string = if let MathematicalAndBitwise::Add = operator {
-			let left_is_string = simple_subtype(lhs, TypeId::STRING_TYPE, info, types);
-			let right_is_string = simple_subtype(lhs, TypeId::STRING_TYPE, info, types);
-			let left_is_string_or_number =
-				left_is_string || simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types);
-			let right_is_string_or_number =
-				right_is_string || simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types);
-			if !left_is_string_or_number || !right_is_string_or_number {
-				return Err(());
-			}
-			left_is_string || right_is_string
-		} else {
-			let left_is_number = simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types);
-			if !left_is_number || !simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) {
-				return Err(());
-			}
-			false
-		};
-
-		// :)
-		if let (MathematicalAndBitwise::Exponent, TypeId::ONE, true) =
-			(operator, rhs, intrinsics::is_not_not_a_number(lhs, types))
-		{
-			return Ok(lhs);
-		} else if let (MathematicalAndBitwise::Add, TypeId::ZERO)
-		| (MathematicalAndBitwise::Multiply, TypeId::ONE) = (operator, rhs)
-		{
-			return Ok(lhs);
-		} else if let (MathematicalAndBitwise::Add, TypeId::ZERO)
-		| (MathematicalAndBitwise::Multiply, TypeId::ONE) = (operator, lhs)
-		{
-			return Ok(rhs);
-		}
-
-		let result = if can_be_string {
-			TypeId::STRING_TYPE
-		} else if let (
-			MathematicalAndBitwise::Add | MathematicalAndBitwise::Multiply,
-			Some(lhs_range),
-			Some(rhs_range),
-		) = (operator, intrinsics::get_range(lhs, types), intrinsics::get_range(rhs, types))
-		{
-			match operator {
-				MathematicalAndBitwise::Add => {
-					intrinsics::range_to_type(lhs_range.space_addition(rhs_range), types)
-				}
-				MathematicalAndBitwise::Multiply => {
-					intrinsics::range_to_type(lhs_range.space_multiplication(rhs_range), types)
-				}
-				_ => unreachable!(),
-			}
-		} else {
-			TypeId::NUMBER_TYPE
-		};
-
-		let constructor = crate::types::Constructor::BinaryOperator { lhs, operator, rhs, result };
-		Ok(types.register_type(crate::Type::Constructor(constructor)))
-	} else {
-		attempt_constant_math_operator(lhs, operator, rhs, types, strict_casts)
-	}
-}
-
-/// Not canonical / reducible form of [`CanonicalEqualityAndInequality`].
-/// (for examples `a > b` is equivalent to `b < a` (after side effects) and `a !== b` is equivalent to `!(a === b)`)
-#[derive(Clone, Copy, Debug)]
-pub enum EqualityAndInequality {
-	StrictEqual,
-	StrictNotEqual,
-	Equal,
-	NotEqual,
-	GreaterThan,
-	LessThan,
-	LessThanOrEqual,
-	GreaterThanOrEqual,
-}
-
-/// Canonical / irreducible form of [`EqualityAndInequality`].
-#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)]
-pub enum CanonicalEqualityAndInequality {
-	StrictEqual,
-	LessThan,
-}
-
-pub enum EqualityAndInequalityResultKind {
-	Constant,
-	Disjoint,
-	Condition,
-}
-
-pub fn evaluate_equality_inequality_operation(
-	mut lhs: TypeId,
-	operator: &EqualityAndInequality,
-	mut rhs: TypeId,
-	info: &impl crate::context::InformationChain,
-	types: &mut TypeStore,
-	strict_casts: bool,
-) -> Result<(TypeId, EqualityAndInequalityResultKind), ()> {
-	// `NaN == t` is always true
-	if lhs == TypeId::NAN || rhs == TypeId::NAN {
-		return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Constant));
-	}
-
-	match operator {
-		EqualityAndInequality::StrictEqual => {
-			// crate::utilities::notify!("{:?} === {:?}", lhs, rhs);
-
-			let left_dependent = types.get_type_by_id(lhs).is_dependent();
-			let is_dependent = left_dependent || types.get_type_by_id(rhs).is_dependent();
-
-			if is_dependent {
-				if lhs == rhs
-					&& intrinsics::is_not_not_a_number(lhs, types)
-					&& intrinsics::is_not_not_a_number(rhs, types)
-				{
-					// I think this is okay
-					return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant));
-				}
-
-				// Checks lhs and rhs type to see if they overlap
-				if crate::types::disjoint::types_are_disjoint(
-					lhs,
-					rhs,
-					&mut Vec::new(),
-					info,
-					types,
-				) {
-					return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint));
-				}
-
-				// Sort if `*constant* == ...`. Ideally want constant type on the RHS
-				let (lhs, rhs) = if left_dependent { (lhs, rhs) } else { (rhs, rhs) };
-				let constructor = crate::types::Constructor::CanonicalRelationOperator {
-					lhs,
-					operator: CanonicalEqualityAndInequality::StrictEqual,
-					rhs,
-				};
-
-				Ok((
-					types.register_type(crate::Type::Constructor(constructor)),
-					EqualityAndInequalityResultKind::Condition,
-				))
-			} else {
-				match attempt_constant_equality(lhs, rhs, types) {
-					Ok(ty) => Ok((
-						if ty { TypeId::TRUE } else { TypeId::FALSE },
-						EqualityAndInequalityResultKind::Constant,
-					)),
-					Err(()) => {
-						unreachable!(
-							"should have been caught `is_dependent` above, {:?} === {:?}",
-							types.get_type_by_id(lhs),
-							types.get_type_by_id(rhs)
-						)
-					}
-				}
-			}
-		}
-		EqualityAndInequality::LessThan => {
-			fn attempt_less_than(
-				lhs: TypeId,
-				rhs: TypeId,
-				types: &mut TypeStore,
-				strict_casts: bool,
-			) -> Result<bool, ()> {
-				// Similar but reversed semantics to add
-				match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) {
-					(
-						Type::Constant(Constant::String(string1)),
-						Type::Constant(Constant::String(string2)),
-					) => {
-						// Yah rust includes string alphanumerical equivalence of strings
-						Ok(string1 < string2)
-					}
-					(Type::Constant(c1), Type::Constant(c2)) => {
-						let lhs = cast_as_number(c1, strict_casts)?;
-						let rhs = cast_as_number(c2, strict_casts)?;
-						Ok(lhs < rhs)
-					}
-					(lhs, rhs) => {
-						crate::utilities::notify!("{:?}", (lhs, rhs));
-						// Ok(TypeId::OPEN_BOOLEAN_TYPE)
-						Err(())
-					}
-				}
-			}
-
-			let is_dependent = types.get_type_by_id(lhs).is_dependent()
-				|| types.get_type_by_id(rhs).is_dependent();
-
-			if is_dependent {
-				{
-					if let Type::Constructor(Constructor::BinaryOperator {
-						lhs: op_lhs,
-						operator,
-						rhs: op_rhs,
-						result: _,
-					}) = types.get_type_by_id(lhs)
-					{
-						if let (
-							Type::Constant(Constant::Number(add)),
-							MathematicalAndBitwise::Add,
-							Type::Constant(Constant::Number(lt)),
-						) = (types.get_type_by_id(*op_rhs), operator, types.get_type_by_id(rhs))
-						{
-							crate::utilities::notify!("Shifted LT");
-							lhs = *op_lhs;
-							rhs = types.register_type(Type::Constant(Constant::Number(lt - add)));
-						}
-					}
-				}
-
-				{
-					// let lhs = get_constraint(lhs, types).unwrap_or(lhs);
-					// let rhs = get_constraint(rhs, types).unwrap_or(rhs);
-
-					if !simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types)
-						|| !simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types)
-					{
-						return Err(());
-					}
-
-					// Tidies some things for counting loop iterations
-
-					// Checking disjoint-ness for inequalities (TODO under option) via distribution
-					if let (Some(lhs_range), Some(rhs_range)) =
-						(intrinsics::get_range(lhs, types), intrinsics::get_range(rhs, types))
-					{
-						if lhs_range.below(rhs_range) {
-							return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant));
-						}
-						if lhs_range.above(rhs_range) {
-							return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint));
-						}
-					}
-				}
-
-				let constructor = Constructor::CanonicalRelationOperator {
-					lhs,
-					operator: CanonicalEqualityAndInequality::LessThan,
-					rhs,
-				};
-				Ok((
-					types.register_type(crate::Type::Constructor(constructor)),
-					EqualityAndInequalityResultKind::Condition,
-				))
-			} else {
-				attempt_less_than(lhs, rhs, types, strict_casts).map(|value| {
-					(
-						if value { TypeId::TRUE } else { TypeId::FALSE },
-						EqualityAndInequalityResultKind::Constant,
-					)
-				})
-			}
-		}
-		// equal OR less than
-		EqualityAndInequality::LessThanOrEqual => {
-			let (equality_result, warning) = evaluate_equality_inequality_operation(
-				lhs,
-				&EqualityAndInequality::StrictEqual,
-				rhs,
-				info,
-				types,
-				strict_casts,
-			)?;
-
-			if equality_result == TypeId::TRUE {
-				Ok((equality_result, warning))
-			} else if equality_result == TypeId::FALSE {
-				evaluate_equality_inequality_operation(
-					lhs,
-					&EqualityAndInequality::LessThan,
-					rhs,
-					info,
-					types,
-					strict_casts,
-				)
-			} else {
-				let (less_than_result, warning) = evaluate_equality_inequality_operation(
-					lhs,
-					&EqualityAndInequality::LessThan,
-					rhs,
-					info,
-					types,
-					strict_casts,
-				)?;
-				Ok((types.new_logical_or_type(equality_result, less_than_result), warning))
-			}
-		}
-		EqualityAndInequality::StrictNotEqual => {
-			let (equality_result, kind) = evaluate_equality_inequality_operation(
-				lhs,
-				&EqualityAndInequality::StrictEqual,
-				rhs,
-				info,
-				types,
-				strict_casts,
-			)?;
-			if let EqualityAndInequalityResultKind::Condition = kind {
-				Ok((types.new_logical_negation_type(equality_result), kind))
-			} else {
-				let negated = if let TypeId::TRUE = equality_result {
-					TypeId::FALSE
-				} else if let TypeId::FALSE = equality_result {
-					TypeId::TRUE
-				} else {
-					todo!()
-				};
-				Ok((negated, kind))
-			}
-		}
-		EqualityAndInequality::Equal => {
-			crate::utilities::notify!("TODO equal operator");
-			Ok((TypeId::OPEN_BOOLEAN_TYPE, EqualityAndInequalityResultKind::Condition))
-		}
-		EqualityAndInequality::NotEqual => {
-			let (equality_result, kind) = evaluate_equality_inequality_operation(
-				lhs,
-				&EqualityAndInequality::Equal,
-				rhs,
-				info,
-				types,
-				strict_casts,
-			)?;
-			if let EqualityAndInequalityResultKind::Condition = kind {
-				Ok((types.new_logical_negation_type(equality_result), kind))
-			} else {
-				let negated = if let TypeId::TRUE = equality_result {
-					TypeId::FALSE
-				} else if let TypeId::FALSE = equality_result {
-					TypeId::TRUE
-				} else {
-					todo!()
-				};
-				Ok((negated, kind))
-			}
-		}
-		// Swapping operands!
-		EqualityAndInequality::GreaterThan => evaluate_equality_inequality_operation(
-			rhs,
-			&EqualityAndInequality::LessThan,
-			lhs,
-			info,
-			types,
-			strict_casts,
-		),
-		// Swapping operands!
-		EqualityAndInequality::GreaterThanOrEqual => evaluate_equality_inequality_operation(
-			rhs,
-			&EqualityAndInequality::LessThanOrEqual,
-			lhs,
-			info,
-			types,
-			strict_casts,
-		),
-	}
-}
-
-#[allow(clippy::let_and_return)]
-pub fn is_null_or_undefined(
-	ty: TypeId,
-	info: &impl crate::context::InformationChain,
-	types: &mut TypeStore,
-) -> TypeId {
-	let is_null = evaluate_equality_inequality_operation(
-		ty,
-		&EqualityAndInequality::StrictEqual,
-		TypeId::NULL_TYPE,
-		info,
-		types,
-		false,
-	)
-	.map_or(TypeId::ERROR_TYPE, |(left, _)| left);
-
-	if let TypeId::TRUE = is_null {
-		is_null
-	} else {
-		let is_undefined = evaluate_equality_inequality_operation(
-			ty,
-			&EqualityAndInequality::StrictEqual,
-			TypeId::UNDEFINED_TYPE,
-			info,
-			types,
-			false,
-		)
-		.map_or(TypeId::ERROR_TYPE, |(left, _)| left);
-
-		if let TypeId::FALSE = is_null {
-			is_undefined
-		} else {
-			types.new_logical_or_type(is_null, is_undefined)
-		}
-	}
-}
-
-#[derive(Copy, Clone, Debug)]
-pub enum LogicalOperator {
-	And,
-	Or,
-	/// TODO is this canonical?
-	NullCoalescing,
-}
-
-/// TODO strict casts!
-pub fn evaluate_logical_operation_with_expression<
-	'a,
-	T: crate::ReadFromFS,
-	A: crate::ASTImplementation,
->(
-	lhs: (TypeId, Span),
-	operator: LogicalOperator,
-	rhs: &'a A::Expression<'a>,
-	checking_data: &mut CheckingData<T, A>,
-	environment: &mut Environment,
-	expecting: TypeId,
-) -> Result<TypeId, ()> {
-	match operator {
-		LogicalOperator::And => Ok(new_conditional_context(
-			environment,
-			lhs,
-			|env: &mut Environment, data: &mut CheckingData<T, A>| {
-				A::synthesise_expression(rhs, expecting, env, data)
-			},
-			Some(|_env: &mut Environment, _data: &mut CheckingData<T, A>| lhs.0),
-			checking_data,
-		)),
-		LogicalOperator::Or => Ok(new_conditional_context(
-			environment,
-			lhs,
-			|env: &mut Environment, checking_data: &mut CheckingData<T, A>| {
-				if let Some(constraint) = crate::types::get_constraint(lhs.0, &checking_data.types)
-				{
-					let mut result = Vec::new();
-					super::narrowing::build_union_from_filter(
-						constraint,
-						super::narrowing::NOT_FASLY,
-						&mut result,
-						env,
-						&checking_data.types,
-					);
-					let narrowed_to = checking_data.types.new_or_type_from_iterator(result);
-					checking_data.types.register_type(Type::Narrowed { from: lhs.0, narrowed_to })
-				} else {
-					lhs.0
-				}
-			},
-			Some(|env: &mut Environment, data: &mut CheckingData<T, A>| {
-				A::synthesise_expression(rhs, expecting, env, data)
-			}),
-			checking_data,
-		)),
-		LogicalOperator::NullCoalescing => {
-			let is_lhs_null_or_undefined =
-				is_null_or_undefined(lhs.0, environment, &mut checking_data.types);
-			// Equivalent to: `(lhs is null or undefined) ? lhs : rhs`
-			Ok(new_conditional_context(
-				environment,
-				(is_lhs_null_or_undefined, lhs.1),
-				|env: &mut Environment, checking_data: &mut CheckingData<T, A>| {
-					if let Some(constraint) =
-						crate::types::get_constraint(lhs.0, &checking_data.types)
-					{
-						let mut result = Vec::new();
-						super::narrowing::build_union_from_filter(
-							constraint,
-							super::narrowing::NOT_NULL_OR_UNDEFINED,
-							&mut result,
-							env,
-							&checking_data.types,
-						);
-						let narrowed_to = checking_data.types.new_or_type_from_iterator(result);
-						checking_data
-							.types
-							.register_type(Type::Narrowed { from: lhs.0, narrowed_to })
-					} else {
-						lhs.0
-					}
-				},
-				Some(|env: &mut Environment, data: &mut CheckingData<T, A>| {
-					A::synthesise_expression(rhs, expecting, env, data)
-				}),
-				checking_data,
-			))
-		}
-	}
-}
-
-/// `typeof` and some others done elsewhere
-#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)]
-pub enum UnaryOperation {
-	/// Treated as `(value ? false : true)`
-	LogicalNot,
-	/// Treated as `0 - value` (could also do -1 * value?)
-	Negation,
-	/// Treated as `value ^ 0xFFFF_FFFF`
-	BitwiseNot,
-}
-
-/// Tries to evaluate unary operation for constant terms. Else delegates to binary operations that handle equivalent thing
-pub fn evaluate_unary_operator(
-	operator: UnaryOperation,
-	operand: TypeId,
-	info: &impl crate::context::InformationChain,
-	types: &mut TypeStore,
-	strict_casts: bool,
-) -> Result<TypeId, ()> {
-	if operand == TypeId::ERROR_TYPE {
-		return Ok(operand);
-	}
-
-	match operator {
-		UnaryOperation::LogicalNot => {
-			if let Decidable::Known(value) = is_type_truthy_falsy(operand, types) {
-				if value {
-					Ok(TypeId::FALSE)
-				} else {
-					Ok(TypeId::TRUE)
-				}
-			} else {
-				let is_boolean = simple_subtype(operand, TypeId::BOOLEAN_TYPE, info, types);
-				if is_boolean {
-					Ok(types.new_logical_negation_type(operand))
-				} else {
-					Err(())
-				}
-			}
-		}
-		UnaryOperation::Negation | UnaryOperation::BitwiseNot => {
-			if let Type::Constant(cst) = types.get_type_by_id(operand) {
-				let value = cast_as_number(cst, strict_casts).expect("hmm");
-				let value = match operator {
-					UnaryOperation::BitwiseNot => f64::from(!(value as i32)),
-					UnaryOperation::Negation => -value,
-					UnaryOperation::LogicalNot => unreachable!(),
-				};
-				let value = ordered_float::NotNan::try_from(value);
-				Ok(match value {
-					Ok(value) => types.new_constant_type(Constant::Number(value)),
-					Err(_) => TypeId::NAN,
-				})
-			} else {
-				match operator {
-					UnaryOperation::BitwiseNot => evaluate_mathematical_operation(
-						TypeId::MAX_U32,
-						MathematicalAndBitwise::BitwiseXOr,
-						operand,
-						info,
-						types,
-						strict_casts,
-					),
-					UnaryOperation::Negation => evaluate_mathematical_operation(
-						TypeId::ZERO,
-						MathematicalAndBitwise::Subtract,
-						operand,
-						info,
-						types,
-						strict_casts,
-					),
-					UnaryOperation::LogicalNot => unreachable!("handled above"),
-				}
-			}
-		}
-	}
-}
-
-/// Returns whether lhs and rhs are always equal or never equal. TODO more
-///
-/// TODO return decidable.
-fn attempt_constant_equality(lhs: TypeId, rhs: TypeId, types: &mut TypeStore) -> Result<bool, ()> {
-	if lhs == rhs {
-		Ok(true)
-	} else if matches!(lhs, TypeId::NULL_TYPE | TypeId::UNDEFINED_TYPE)
-		|| matches!(rhs, TypeId::NULL_TYPE | TypeId::UNDEFINED_TYPE)
-	{
-		// If above `==`` failed => false (as always have same `TypeId`)
-		Ok(false)
-	} else {
-		let lhs = types.get_type_by_id(lhs);
-		let rhs = types.get_type_by_id(rhs);
-		if let (Type::Constant(cst1), Type::Constant(cst2)) = (lhs, rhs) {
-			Ok(cst1 == cst2)
-		} else if let (Type::Object(..) | Type::SpecialObject(SpecialObject::Function(..)), _)
-		| (_, Type::Object(..) | Type::SpecialObject(SpecialObject::Function(..))) = (lhs, rhs)
-		{
-			// Same objects and functions always have same type id. Poly case doesn't occur here
-			Ok(false)
-		}
-		// Temp fix for closures
-		else if let (
-			Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: on_lhs, .. }),
-			Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: on_rhs, .. }),
-		) = (lhs, rhs)
-		{
-			// TODO does this work?
-			attempt_constant_equality(*on_lhs, *on_rhs, types)
-		} else {
-			crate::utilities::notify!("{:?} === {:?} is apparently false", lhs, rhs);
-			Err(())
-		}
-	}
-}
diff --git a/checker/src/features/operations/logical.rs b/checker/src/features/operations/logical.rs
new file mode 100644
index 00000000..25ecfc72
--- /dev/null
+++ b/checker/src/features/operations/logical.rs
@@ -0,0 +1,113 @@
+use super::{
+	super::{conditional, narrowing},
+	relation::is_null_or_undefined,
+};
+use crate::{Type, TypeId};
+
+#[derive(Copy, Clone, Debug)]
+pub enum LogicalOperator {
+	And,
+	Or,
+	/// TODO is this canonical?
+	NullCoalescing,
+}
+
+/// TODO strict casts!
+pub fn evaluate_logical_operation_with_expression<
+	'a,
+	T: crate::ReadFromFS,
+	A: crate::ASTImplementation,
+>(
+	lhs: (TypeId, source_map::Span),
+	operator: LogicalOperator,
+	rhs: &'a A::Expression<'a>,
+	checking_data: &mut crate::CheckingData<T, A>,
+	environment: &mut crate::Environment,
+	expecting: TypeId,
+) -> Result<TypeId, ()> {
+	match operator {
+		LogicalOperator::And => Ok(conditional::new_conditional_context(
+			environment,
+			lhs,
+			|env: &mut crate::Environment, data: &mut crate::CheckingData<T, A>| {
+				A::synthesise_expression(rhs, expecting, env, data)
+			},
+			Some(|env: &mut crate::Environment, checking_data: &mut crate::CheckingData<T, A>| {
+				if let Some(constraint) = crate::types::get_constraint(lhs.0, &checking_data.types)
+				{
+					let mut result = Vec::new();
+					narrowing::build_union_from_filter(
+						constraint,
+						narrowing::FASLY,
+						&mut result,
+						env,
+						&checking_data.types,
+					);
+					let narrowed_to = checking_data.types.new_or_type_from_iterator(result);
+					checking_data.types.register_type(Type::Narrowed { from: lhs.0, narrowed_to })
+				} else {
+					lhs.0
+				}
+			}),
+			checking_data,
+		)),
+		LogicalOperator::Or => Ok(conditional::new_conditional_context(
+			environment,
+			lhs,
+			|env: &mut crate::Environment, checking_data: &mut crate::CheckingData<T, A>| {
+				if let Some(constraint) = crate::types::get_constraint(lhs.0, &checking_data.types)
+				{
+					let mut result = Vec::new();
+					narrowing::build_union_from_filter(
+						constraint,
+						narrowing::NOT_FASLY,
+						&mut result,
+						env,
+						&checking_data.types,
+					);
+					let narrowed_to = checking_data.types.new_or_type_from_iterator(result);
+					checking_data.types.register_type(Type::Narrowed { from: lhs.0, narrowed_to })
+				} else {
+					lhs.0
+				}
+			},
+			Some(|env: &mut crate::Environment, data: &mut crate::CheckingData<T, A>| {
+				A::synthesise_expression(rhs, expecting, env, data)
+			}),
+			checking_data,
+		)),
+		LogicalOperator::NullCoalescing => {
+			let is_lhs_null_or_undefined =
+				is_null_or_undefined(lhs.0, environment, &mut checking_data.types);
+			// Equivalent to: `(lhs is null or undefined) ? lhs : rhs`
+			Ok(conditional::new_conditional_context(
+				environment,
+				(is_lhs_null_or_undefined, lhs.1),
+				|env: &mut crate::Environment, checking_data: &mut crate::CheckingData<T, A>| {
+					if let Some(constraint) =
+						crate::types::get_constraint(lhs.0, &checking_data.types)
+					{
+						let mut result = Vec::new();
+						narrowing::build_union_from_filter(
+							constraint,
+							narrowing::NOT_NULL_OR_UNDEFINED,
+							&mut result,
+							env,
+							&checking_data.types,
+						);
+						let narrowed_to = checking_data.types.new_or_type_from_iterator(result);
+						checking_data
+							.types
+							.register_type(Type::Narrowed { from: lhs.0, narrowed_to })
+					} else {
+						lhs.0
+					}
+				},
+				Some(|env: &mut crate::Environment, data: &mut crate::CheckingData<T, A>| {
+					A::synthesise_expression(rhs, expecting, env, data)
+				}),
+				checking_data,
+			))
+		}
+	}
+}
diff --git a/checker/src/features/operations/mathematical_bitwise.rs b/checker/src/features/operations/mathematical_bitwise.rs
new file mode 100644
index 00000000..240e659a
--- /dev/null
+++ b/checker/src/features/operations/mathematical_bitwise.rs
@@ -0,0 +1,262 @@
+use crate::{
+	types::{cast_as_number, cast_as_string, helpers, intrinsics, Constant, Type},
+	TypeId,
+};
+
+/// For these **binary** operations both operands are synthesised
+#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)]
+pub enum MathematicalOrBitwiseOperation {
+	Add,
+	Subtract,
+	Multiply,
+	Divide,
+	Modulo,
+	Exponent,
+	BitwiseShiftLeft,
+	BitwiseShiftRight,
+	BitwiseShiftRightUnsigned,
+	BitwiseAnd,
+	BitwiseXOr,
+	BitwiseOr,
+}
+
+/// TODO proper error type
+pub fn evaluate_mathematical_operation(
+	lhs: TypeId,
+	operator: MathematicalOrBitwiseOperation,
+	rhs: TypeId,
+	info: &impl crate::context::InformationChain,
+	types: &mut crate::TypeStore,
+	strict_casts: bool,
+	operate_on_number_intrinsics: bool,
+) -> Result<TypeId, ()> {
+	fn attempt_constant_math_operator(
+		lhs: TypeId,
+		operator: MathematicalOrBitwiseOperation,
+		rhs: TypeId,
+		types: &mut crate::TypeStore,
+		strict_casts: bool,
+	) -> Result<TypeId, ()> {
+		if let MathematicalOrBitwiseOperation::Add = operator {
+			match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) {
+				(Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) => {
+					Ok(types.new_constant_type(Constant::Number(lhs + rhs)))
+				}
+				(Type::Constant(lhs), Type::Constant(rhs)) => {
+					let mut first = cast_as_string(lhs, strict_casts)?;
+					let second = cast_as_string(rhs, strict_casts)?;
+					// Concatenate strings
+					first.push_str(&second);
+					Ok(types.new_constant_type(Constant::String(first)))
+				}
+				(lhs, rhs) => {
+					crate::utilities::notify!("here {:?} + {:?}", lhs, rhs);
+					Err(())
+				}
+			}
+		} else {
+			match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) {
+				(Type::Constant(c1), Type::Constant(c2)) => {
+					let lhs = cast_as_number(c1, strict_casts).unwrap_or(f64::NAN);
+					let rhs = cast_as_number(c2, strict_casts).unwrap_or(f64::NAN);
+					// TODO hopefully Rust implementation is the same as JS
+					#[allow(clippy::cast_possible_truncation)]
+					let value = match operator {
+						MathematicalOrBitwiseOperation::Add => unreachable!(),
+						MathematicalOrBitwiseOperation::Subtract => lhs - rhs,
+						MathematicalOrBitwiseOperation::Multiply => lhs * rhs,
+						MathematicalOrBitwiseOperation::Divide => lhs / rhs,
+						MathematicalOrBitwiseOperation::Modulo => lhs % rhs,
+						MathematicalOrBitwiseOperation::Exponent => lhs.powf(rhs),
+						MathematicalOrBitwiseOperation::BitwiseShiftLeft => {
+							f64::from((lhs as i32).checked_shl(rhs as u32).unwrap_or(0))
+						}
+						MathematicalOrBitwiseOperation::BitwiseShiftRight => {
+							f64::from((lhs as i32).checked_shr(rhs as u32).unwrap_or(0))
+						}
+						MathematicalOrBitwiseOperation::BitwiseShiftRightUnsigned => {
+							(lhs as i32).wrapping_shr(rhs as u32).into()
+						}
+						MathematicalOrBitwiseOperation::BitwiseAnd => {
+							f64::from((lhs as i32) & (rhs as i32))
+						}
+						MathematicalOrBitwiseOperation::BitwiseXOr => {
+							f64::from((lhs as i32) ^ (rhs as i32))
+						}
+						MathematicalOrBitwiseOperation::BitwiseOr => {
+							f64::from((lhs as i32) | (rhs as i32))
+						}
+					};
+					let value = ordered_float::NotNan::try_from(value);
+					let ty = match value {
+						Ok(value) => types.new_constant_type(Constant::Number(value)),
+						Err(_) => TypeId::NAN,
+					};
+					Ok(ty)
+				}
+				(lhs, rhs) => {
+					crate::utilities::notify!("here {:?} @ {:?}", lhs, rhs);
+					Err(())
+				}
+			}
+		}
+	}
+
+	let lhs_ty = types.get_type_by_id(lhs);
+	let rhs_ty = types.get_type_by_id(rhs);
+
+	if lhs == TypeId::ERROR_TYPE || rhs == TypeId::ERROR_TYPE {
+		return Ok(TypeId::ERROR_TYPE);
+	}
+
+	crate::utilities::notify!("lhs={:?}, rhs={:?}", lhs_ty, rhs_ty);
+	let either_dependent = lhs_ty.is_dependent() || rhs_ty.is_dependent();
+
+	if either_dependent {
+		let can_be_string = if let MathematicalOrBitwiseOperation::Add = operator {
+			let left_is_string = helpers::simple_subtype(lhs, TypeId::STRING_TYPE, info, types);
+			let right_is_string = helpers::simple_subtype(lhs, TypeId::STRING_TYPE, info, types);
+			let left_is_string_or_number =
+				left_is_string || helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types);
+			let right_is_string_or_number =
+				right_is_string || helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types);
+			if !left_is_string_or_number || !right_is_string_or_number {
+				crate::utilities::notify!("Here");
+				return Err(());
+			}
+
+			left_is_string || right_is_string
+		} else {
+			if !helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types)
+				|| !helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types)
+			{
+				crate::utilities::notify!("Here :/");
+				return Err(());
+			}
+
+			false
+		};
+
+		// :)
+		if let (MathematicalOrBitwiseOperation::Exponent, TypeId::ZERO) = (operator, rhs) {
+			// This holds for NaN. Thus can do in every case
+			return Ok(TypeId::ONE);
+		} else if let (MathematicalOrBitwiseOperation::Exponent, TypeId::ONE, true) =
+			(operator, rhs, intrinsics::is_not_not_a_number(lhs, types))
+		{
+			return Ok(lhs);
+		} else if let (MathematicalOrBitwiseOperation::Add, TypeId::ZERO)
+		| (MathematicalOrBitwiseOperation::Multiply, TypeId::ONE) = (operator, rhs)
+		{
+			return Ok(lhs);
+		} else if let (MathematicalOrBitwiseOperation::Add, TypeId::ZERO)
+		| (MathematicalOrBitwiseOperation::Multiply, TypeId::ONE) = (operator, lhs)
+		{
+			return Ok(rhs);
+		}
+
+		if let (
+			MathematicalOrBitwiseOperation::Add | MathematicalOrBitwiseOperation::Multiply,
+			(lhs_range, lhs_modulo),
+			(rhs_range, rhs_modulo),
+			true,
+		) = (
+			operator,
+			intrinsics::get_range_and_mod_class(lhs, types),
+			intrinsics::get_range_and_mod_class(rhs, types),
+			operate_on_number_intrinsics,
+		) {
+			crate::utilities::notify!(
+				"{:?} with {:?}. {:?} & {:?}",
+				(lhs_range, lhs_modulo),
+				(rhs_range, rhs_modulo),
+				rhs_range.and_then(crate::utilities::float_range::FloatRange::as_single),
+				lhs_range.and_then(crate::utilities::float_range::FloatRange::as_single)
+			);
+			if let (Some(lhs_range), Some(rhs_range)) = (lhs_range, rhs_range) {
+				let range = match operator {
+					MathematicalOrBitwiseOperation::Add => lhs_range.space_addition(rhs_range),
+					MathematicalOrBitwiseOperation::Multiply => {
+						lhs_range.space_multiplication(rhs_range)
+					}
+					_ => unreachable!(),
+				};
+				// That is a lot types
+				let result = intrinsics::range_to_type(range, types);
+				let constructor =
+					crate::types::Constructor::BinaryOperator { lhs, operator, rhs, result };
+
+				return Ok(types.register_type(crate::Type::Constructor(constructor)));
+			}
+
+			// else if let (Some(lhs_modulo), Some(offset)) =
+			// 	(lhs_modulo, rhs_range.and_then(crate::utilities::float_range::FloatRange::as_single))
+			// {
+			// 	crate::utilities::notify!("Here");
+			// 	let mod_class = lhs_modulo.offset(offset);
+			// 	intrinsics::modulo_to_type(mod_class, types)
+			// } else if let (Some(rhs_modulo), Some(offset)) =
+			// 	(rhs_modulo, lhs_range.and_then(crate::utilities::float_range::FloatRange::as_single))
+			// {
+			// 	let mod_class = rhs_modulo.offset(offset);
+			// 	intrinsics::modulo_to_type(mod_class, types)
+			// }
+		}
+
+		if let (Some((condition, lhs_truthy_result, lhs_falsy_result)), true) =
+			(helpers::get_type_as_conditional(lhs, types), operate_on_number_intrinsics)
+		{
+			crate::utilities::notify!("Here lhs dependent");
+			let truthy_result = evaluate_mathematical_operation(
+				lhs_truthy_result,
+				operator,
+				rhs,
+				info,
+				types,
+				strict_casts,
+				operate_on_number_intrinsics,
+			)?;
+			let falsy_result = evaluate_mathematical_operation(
+				lhs_falsy_result,
+				operator,
+				rhs,
+				info,
+				types,
+				strict_casts,
+				operate_on_number_intrinsics,
+			)?;
+
+			Ok(types.new_conditional_type(condition, truthy_result, falsy_result))
+		} else if let (true, Some((condition, rhs_truthy_result, rhs_falsy_result))) =
+			(operate_on_number_intrinsics, helpers::get_type_as_conditional(rhs, types))
+		{
+			let truthy_result = evaluate_mathematical_operation(
+				lhs,
+				operator,
+				rhs_truthy_result,
+				info,
+				types,
+				strict_casts,
+				operate_on_number_intrinsics,
+			)?;
+			let falsy_result = evaluate_mathematical_operation(
+				lhs,
+				operator,
+				rhs_falsy_result,
+				info,
+				types,
+				strict_casts,
+				operate_on_number_intrinsics,
+			)?;
+
+			Ok(types.new_conditional_type(condition, truthy_result, falsy_result))
+		} else {
+			let result = if can_be_string { TypeId::STRING_TYPE } else { TypeId::NUMBER_TYPE };
+			let constructor =
+				crate::types::Constructor::BinaryOperator { lhs, operator, rhs, result };
+			Ok(types.register_type(crate::Type::Constructor(constructor)))
+		}
+	} else {
+		attempt_constant_math_operator(lhs, operator, rhs, types, strict_casts)
+	}
+}
diff --git a/checker/src/features/operations/mod.rs b/checker/src/features/operations/mod.rs
new file mode 100644
index 00000000..a0e3855f
--- /dev/null
+++ b/checker/src/features/operations/mod.rs
@@ -0,0 +1,12 @@
+mod logical;
+mod mathematical_bitwise;
+mod relation;
+mod unary;
+
+pub use logical::{evaluate_logical_operation_with_expression, LogicalOperator};
+pub use mathematical_bitwise::{evaluate_mathematical_operation, MathematicalOrBitwiseOperation};
+pub use relation::{
+	evaluate_equality_inequality_operation, is_null_or_undefined, CanonicalEqualityAndInequality,
+	EqualityAndInequality, EqualityAndInequalityResultKind,
+};
+pub use unary::{evaluate_unary_operator, UnaryOperation};
diff --git a/checker/src/features/operations/relation.rs b/checker/src/features/operations/relation.rs
new file mode 100644
index 00000000..b703e135
--- /dev/null
+++ b/checker/src/features/operations/relation.rs
@@ -0,0 +1,370 @@
+use super::MathematicalOrBitwiseOperation;
+use crate::types::{
+	cast_as_number, disjoint, get_constraint, helpers, intrinsics, Constant, Constructor,
+	PartiallyAppliedGenerics, Type, TypeId,
+};
+
+/// Not canonical / reducible form of [`CanonicalEqualityAndInequality`].
+/// (for examples `a > b` is equivalent to `b < a` (after side effects) and `a !== b` is equivalent to `!(a === b)`)
+#[derive(Clone, Copy, Debug)]
+pub enum EqualityAndInequality {
+	StrictEqual,
+	StrictNotEqual,
+	Equal,
+	NotEqual,
+	GreaterThan,
+	LessThan,
+	LessThanOrEqual,
+	GreaterThanOrEqual,
+}
+
+/// Canonical / irreducible form of [`EqualityAndInequality`].
+#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)]
+pub enum CanonicalEqualityAndInequality {
+	StrictEqual,
+	LessThan,
+}
+
+pub enum EqualityAndInequalityResultKind {
+	Constant,
+	Disjoint,
+	Condition,
+}
+
+pub fn evaluate_equality_inequality_operation(
+	mut lhs: TypeId,
+	operator: &EqualityAndInequality,
+	mut rhs: TypeId,
+	info: &impl crate::context::InformationChain,
+	types: &mut crate::types::TypeStore,
+	strict_casts: bool,
+) -> Result<(TypeId, EqualityAndInequalityResultKind), ()> {
+	// `NaN == t` is always true
+	if lhs == TypeId::NAN || rhs == TypeId::NAN {
+		return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Constant));
+	}
+
+	match operator {
+		EqualityAndInequality::StrictEqual => {
+			// crate::utilities::notify!("{:?} === {:?}", lhs, rhs);
+
+			if let (Type::Constant(lhs), Type::Constant(rhs)) =
+				(types.get_type_by_id(lhs), types.get_type_by_id(rhs))
+			{
+				let result = if lhs == rhs { TypeId::TRUE } else { TypeId::FALSE };
+				return Ok((result, EqualityAndInequalityResultKind::Constant));
+			}
+
+			if let (
+				Type::Object(_) | Type::SpecialObject(_),
+				Type::Object(_) | Type::SpecialObject(_),
+			) = (types.get_type_by_id(lhs), types.get_type_by_id(rhs))
+			{
+				let result = if lhs == rhs { TypeId::TRUE } else { TypeId::FALSE };
+				return Ok((result, EqualityAndInequalityResultKind::Constant));
+			}
+
+			// if is_dependent {
+			if lhs == rhs
+				&& intrinsics::is_not_not_a_number(lhs, types)
+				&& intrinsics::is_not_not_a_number(rhs, types)
+			{
+				// I think this is okay
+				return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant));
+			}
+
+			// Checks lhs and rhs type to see if they overlap
+			if disjoint::types_are_disjoint(lhs, rhs, &mut Vec::new(), info, types) {
+				return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint));
+			}
+
+			let left_dependent = types.get_type_by_id(lhs).is_dependent();
+
+			// Sort if `*constant* == ...`. Ideally want constant type on the RHS
+			let (lhs, rhs) = if left_dependent { (lhs, rhs) } else { (rhs, rhs) };
+			let operator = CanonicalEqualityAndInequality::StrictEqual;
+			let result_ty =
+				types.register_type(Type::Constructor(Constructor::CanonicalRelationOperator {
+					lhs,
+					operator,
+					rhs,
+				}));
+
+			Ok((result_ty, EqualityAndInequalityResultKind::Condition))
+
+			// } else {
+			// 	match attempt_constant_equality(lhs, rhs, types) {
+			// 		Ok(ty) => Ok((
+			// 			if ty { TypeId::TRUE } else { TypeId::FALSE },
+			// 			EqualityAndInequalityResultKind::Constant,
+			// 		)),
+			// 		Err(()) => {
+			// 			unreachable!(
+			// 				"should have been caught `is_dependent` above, {:?} === {:?}",
+			// 				types.get_type_by_id(lhs),
+			// 				types.get_type_by_id(rhs)
+			// 			)
+			// 		}
+			// 	}
+			// }
+		}
+		EqualityAndInequality::LessThan => {
+			fn attempt_less_than(
+				lhs: TypeId,
+				rhs: TypeId,
+				types: &mut crate::types::TypeStore,
+				strict_casts: bool,
+			) -> Result<bool, ()> {
+				// Similar but reversed semantics to add
+				match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) {
+					(
+						Type::Constant(Constant::String(string1)),
+						Type::Constant(Constant::String(string2)),
+					) => {
+						// Yah rust includes string alphanumerical equivalence of strings
+						Ok(string1 < string2)
+					}
+					(Type::Constant(c1), Type::Constant(c2)) => {
+						let lhs = cast_as_number(c1, strict_casts)?;
+						let rhs = cast_as_number(c2, strict_casts)?;
+						Ok(lhs < rhs)
+					}
+					(lhs, rhs) => {
+						crate::utilities::notify!("{:?}", (lhs, rhs));
+						// Ok(TypeId::OPEN_BOOLEAN_TYPE)
+						Err(())
+					}
+				}
+			}
+
+			let either_is_dependent = types.get_type_by_id(lhs).is_dependent()
+				|| types.get_type_by_id(rhs).is_dependent();
+
+			if either_is_dependent {
+				{
+					if let Type::Constructor(Constructor::BinaryOperator {
+						lhs: op_lhs,
+						operator,
+						rhs: op_rhs,
+						result: _,
+					}) = types.get_type_by_id(lhs)
+					{
+						if let (
+							Type::Constant(Constant::Number(add)),
+							MathematicalOrBitwiseOperation::Add,
+							Type::Constant(Constant::Number(lt)),
+						) = (types.get_type_by_id(*op_rhs), operator, types.get_type_by_id(rhs))
+						{
+							crate::utilities::notify!("Shifted LT");
+							lhs = *op_lhs;
+							rhs = types.register_type(Type::Constant(Constant::Number(lt - add)));
+						}
+					}
+				}
+
+				{
+					if !helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types)
+						|| !helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types)
+					{
+						return Err(());
+					}
+
+					let lhs = get_constraint(lhs, types).unwrap_or(lhs);
+					let rhs = get_constraint(rhs, types).unwrap_or(rhs);
+
+					// Tidies some things for counting loop iterations
+
+					// Checking disjoint-ness for inequalities (TODO under option) via distribution
+					if let ((Some(lhs_range), _), (Some(rhs_range), _)) = (
+						intrinsics::get_range_and_mod_class(lhs, types),
+						intrinsics::get_range_and_mod_class(rhs, types),
+					) {
+						crate::utilities::notify!("{:?}", (lhs_range, rhs_range));
+						if lhs_range.below(rhs_range) {
+							return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant));
+						}
+						if lhs_range.above(rhs_range) {
+							return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint));
+						}
+					}
+
+					// Transitivity
+					// TODO extra from &
+					if let (
+						Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
+							on: TypeId::LESS_THAN,
+							arguments: larg,
+						}),
+						Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
+							on: TypeId::GREATER_THAN,
+							arguments: rarg,
+						}),
+					) = (types.get_type_by_id(lhs), types.get_type_by_id(rhs))
+					{
+						crate::utilities::notify!("{:?} {:?}", larg, rarg);
+						// Only one level
+						let transitivity = larg.get_structure_restriction(TypeId::NUMBER_GENERIC)
+							== rarg.get_structure_restriction(TypeId::NUMBER_GENERIC);
+						if transitivity {
+							return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant));
+						}
+					}
+				}
+
+				let constructor = Constructor::CanonicalRelationOperator {
+					lhs,
+					operator: CanonicalEqualityAndInequality::LessThan,
+					rhs,
+				};
+				Ok((
+					types.register_type(crate::Type::Constructor(constructor)),
+					EqualityAndInequalityResultKind::Condition,
+				))
+			} else {
+				attempt_less_than(lhs, rhs, types, strict_casts).map(|value| {
+					(
+						if value { TypeId::TRUE } else { TypeId::FALSE },
+						EqualityAndInequalityResultKind::Constant,
+					)
+				})
+			}
+		}
+		// equal OR less than
+		EqualityAndInequality::LessThanOrEqual => {
+			let (equality_result, warning) = evaluate_equality_inequality_operation(
+				lhs,
+				&EqualityAndInequality::StrictEqual,
+				rhs,
+				info,
+				types,
+				strict_casts,
+			)?;
+
+			if equality_result == TypeId::TRUE {
+				Ok((equality_result, warning))
+			} else if equality_result == TypeId::FALSE {
+				evaluate_equality_inequality_operation(
+					lhs,
+					&EqualityAndInequality::LessThan,
+					rhs,
+					info,
+					types,
+					strict_casts,
+				)
+			} else {
+				let (less_than_result, warning) = evaluate_equality_inequality_operation(
+					lhs,
+					&EqualityAndInequality::LessThan,
+					rhs,
+					info,
+					types,
+					strict_casts,
+				)?;
+				Ok((types.new_logical_or_type(equality_result, less_than_result), warning))
+			}
+		}
+		EqualityAndInequality::StrictNotEqual => {
+			let (equality_result, kind) = evaluate_equality_inequality_operation(
+				lhs,
+				&EqualityAndInequality::StrictEqual,
+				rhs,
+				info,
+				types,
+				strict_casts,
+			)?;
+			if let EqualityAndInequalityResultKind::Condition = kind {
+				Ok((types.new_logical_negation_type(equality_result), kind))
+			} else {
+				let negated = if let TypeId::TRUE = equality_result {
+					TypeId::FALSE
+				} else if let TypeId::FALSE = equality_result {
+					TypeId::TRUE
+				} else {
+					todo!()
+				};
+				Ok((negated, kind))
+			}
+		}
+		EqualityAndInequality::Equal => {
+			crate::utilities::notify!("TODO equal operator");
+			Ok((TypeId::OPEN_BOOLEAN_TYPE, EqualityAndInequalityResultKind::Condition))
+		}
+		EqualityAndInequality::NotEqual => {
+			let (equality_result, kind) = evaluate_equality_inequality_operation(
+				lhs,
+				&EqualityAndInequality::Equal,
+				rhs,
+				info,
+				types,
+				strict_casts,
+			)?;
+			if let EqualityAndInequalityResultKind::Condition = kind {
+				Ok((types.new_logical_negation_type(equality_result), kind))
+			} else {
+				let negated = if let TypeId::TRUE = equality_result {
+					TypeId::FALSE
+				} else if let TypeId::FALSE = equality_result {
+					TypeId::TRUE
+				} else {
+					todo!()
+				};
+				Ok((negated, kind))
+			}
+		}
+		// Swapping operands!
+		EqualityAndInequality::GreaterThan => evaluate_equality_inequality_operation(
+			rhs,
+			&EqualityAndInequality::LessThan,
+			lhs,
+			info,
+			types,
+			strict_casts,
+		),
+		// Swapping operands!
+		EqualityAndInequality::GreaterThanOrEqual => evaluate_equality_inequality_operation(
+			rhs,
+			&EqualityAndInequality::LessThanOrEqual,
+			lhs,
+			info,
+			types,
+			strict_casts,
+		),
+	}
+}
+
+#[allow(clippy::let_and_return)]
+pub fn is_null_or_undefined(
+	ty: TypeId,
+	info: &impl crate::context::InformationChain,
+	types: &mut crate::types::TypeStore,
+) -> TypeId {
+	let is_null = evaluate_equality_inequality_operation(
+		ty,
+		&EqualityAndInequality::StrictEqual,
+		TypeId::NULL_TYPE,
+		info,
+		types,
+		false,
+	)
+	.map_or(TypeId::ERROR_TYPE, |(left, _)| left);
+
+	if let TypeId::TRUE = is_null {
+		is_null
+	} else {
+		let is_undefined = evaluate_equality_inequality_operation(
+			ty,
+			&EqualityAndInequality::StrictEqual,
+			TypeId::UNDEFINED_TYPE,
+			info,
+			types,
+			false,
+		)
+		.map_or(TypeId::ERROR_TYPE, |(left, _)| left);
+
+		if let TypeId::FALSE = is_null {
+			is_undefined
+		} else {
+			types.new_logical_or_type(is_null, is_undefined)
+		}
+	}
+}
diff --git a/checker/src/features/operations/unary.rs b/checker/src/features/operations/unary.rs
new file mode 100644
index 00000000..5c44e324
--- /dev/null
+++ b/checker/src/features/operations/unary.rs
@@ -0,0 +1,87 @@
+use crate::{
+	types::{
+		cast_as_number, helpers::simple_subtype, is_type_truthy_falsy, Constant, Type, TypeId,
+	},
+	Decidable,
+};
+
+/// `typeof` and some others done elsewhere
+#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)]
+pub enum UnaryOperation {
+	/// Treated as `(value ? false : true)`
+	LogicalNot,
+	/// Treated as `0 - value` (could also do -1 * value?)
+	Negation,
+	/// Treated as `value ^ 0xFFFF_FFFF`
+	BitwiseNot,
+}
+
+/// Tries to evaluate unary operation for constant terms. Else delegates to binary operations that handle equivalent thing
+pub fn evaluate_unary_operator(
+	operator: UnaryOperation,
+	operand: TypeId,
+	info: &impl crate::context::InformationChain,
+	types: &mut crate::TypeStore,
+	strict_casts: bool,
+) -> Result<TypeId, ()> {
+	if operand == TypeId::ERROR_TYPE {
+		return Ok(operand);
+	}
+
+	match operator {
+		UnaryOperation::LogicalNot => {
+			if let Decidable::Known(value) = is_type_truthy_falsy(operand, types) {
+				if value {
+					Ok(TypeId::FALSE)
+				} else {
+					Ok(TypeId::TRUE)
+				}
+			} else {
+				let is_boolean = simple_subtype(operand, TypeId::BOOLEAN_TYPE, info, types);
+				if is_boolean {
+					Ok(types.new_logical_negation_type(operand))
+				} else {
+					Err(())
+				}
+			}
+		}
+		UnaryOperation::Negation | UnaryOperation::BitwiseNot => {
+			if let Type::Constant(cst) = types.get_type_by_id(operand) {
+				let value = cast_as_number(cst, strict_casts).expect("hmm");
+				let value = match operator {
+					UnaryOperation::BitwiseNot => f64::from(!(value as i32)),
+					UnaryOperation::Negation => -value,
+					UnaryOperation::LogicalNot => unreachable!(),
+				};
+				let value = ordered_float::NotNan::try_from(value);
+				Ok(match value {
+					Ok(value) => types.new_constant_type(Constant::Number(value)),
+					Err(_) => TypeId::NAN,
+				})
+			} else {
+				match operator {
+					UnaryOperation::BitwiseNot => super::evaluate_mathematical_operation(
+						TypeId::MAX_U32,
+						super::MathematicalOrBitwiseOperation::BitwiseXOr,
+						operand,
+						info,
+						types,
+						strict_casts,
+						false,
+					),
+					UnaryOperation::Negation => super::evaluate_mathematical_operation(
+						TypeId::ZERO,
+						super::MathematicalOrBitwiseOperation::Subtract,
+						operand,
+						info,
+						types,
+						strict_casts,
+						// TODO
+						true,
+					),
+					UnaryOperation::LogicalNot => unreachable!("handled above"),
+				}
+			}
+		}
+	}
+}
diff --git a/checker/src/features/template_literal.rs b/checker/src/features/template_literal.rs
index 747877b2..ab68d775 100644
--- a/checker/src/features/template_literal.rs
+++ b/checker/src/features/template_literal.rs
@@ -144,11 +144,12 @@ where
 				checking_data.types.new_constant_type(Constant::String(static_part.to_owned()));
 			let result = super::operations::evaluate_mathematical_operation(
 				acc,
-				crate::features::operations::MathematicalAndBitwise::Add,
+				crate::features::operations::MathematicalOrBitwiseOperation::Add,
 				lhs,
 				environment,
 				&mut checking_data.types,
 				checking_data.options.strict_casts,
+				checking_data.options.advanced_numbers,
 			);
 			if let Ok(result) = result {
 				acc = result;
@@ -164,11 +165,12 @@ where
 			);
 			let result = super::operations::evaluate_mathematical_operation(
 				acc,
-				crate::features::operations::MathematicalAndBitwise::Add,
+				crate::features::operations::MathematicalOrBitwiseOperation::Add,
 				rhs,
 				environment,
 				&mut checking_data.types,
 				checking_data.options.strict_casts,
+				checking_data.options.advanced_numbers,
 			);
 			if let Ok(result) = result {
 				acc = result;
@@ -184,11 +186,12 @@ where
 				checking_data.types.new_constant_type(Constant::String(final_part.to_owned()));
 			let result = super::operations::evaluate_mathematical_operation(
 				acc,
-				crate::features::operations::MathematicalAndBitwise::Add,
+				crate::features::operations::MathematicalOrBitwiseOperation::Add,
 				value,
 				environment,
 				&mut checking_data.types,
 				checking_data.options.strict_casts,
+				checking_data.options.advanced_numbers,
 			);
 			if let Ok(result) = result {
 				result
diff --git a/checker/src/options.rs b/checker/src/options.rs
index 5fd2919c..19fc0ef5 100644
--- a/checker/src/options.rs
+++ b/checker/src/options.rs
@@ -54,6 +54,11 @@ pub struct TypeCheckOptions {
 
 	pub measure_time: bool,
 
+	/// Enables two things:
+	/// - range and modulo class inference
+	/// - modifications to ranges and classes based on operations
+	pub advanced_numbers: bool,
+
 	/// Printing internal diagnostics in dts
 	pub debug_dts: bool,
 }
@@ -79,6 +84,7 @@ impl Default for TypeCheckOptions {
 			measure_time: false,
 			debug_dts: false,
 			extra_syntax: true,
+			advanced_numbers: false,
 		}
 	}
 }
diff --git a/checker/src/synthesis/block.rs b/checker/src/synthesis/block.rs
index 9f0bf7ea..ee9b8af5 100644
--- a/checker/src/synthesis/block.rs
+++ b/checker/src/synthesis/block.rs
@@ -42,7 +42,10 @@ pub(super) fn synthesise_block<T: crate::ReadFromFS>(
 		!matches!(
 			e,
 			StatementOrDeclaration::Statement(
-				Statement::Comment(..) | Statement::MultiLineComment(..) | Statement::Empty(..)
+				Statement::Comment(..)
+					| Statement::MultiLineComment(..)
+					| Statement::Empty(..)
+					| Statement::AestheticSemiColon(..)
 			) | StatementOrDeclaration::Declaration(Declaration::Function(..))
 		)
 	}) {
diff --git a/checker/src/synthesis/classes.rs b/checker/src/synthesis/classes.rs
index 294df65e..9479c1bc 100644
--- a/checker/src/synthesis/classes.rs
+++ b/checker/src/synthesis/classes.rs
@@ -125,7 +125,7 @@ fn synthesise_class_declaration_extends_and_members<
 	let _name = P::as_option_str(&class.name).map_or_else(String::new, str::to_owned);
 	let class_prototype = class_type;
 
-	crate::utilities::notify!("At start {:?}", environment.context_type.free_variables);
+	// crate::utilities::notify!("At start {:?}", environment.context_type.free_variables);
 
 	let extends = class.extends.as_ref().map(|extends_expression| {
 		let extends =
@@ -192,8 +192,6 @@ fn synthesise_class_declaration_extends_and_members<
 					}
 				};
 
-				crate::utilities::notify!("{:?}", (getter_setter, is_async, is_generator));
-
 				let internal_marker = if let (true, ParserPropertyKey::Identifier(name, _, _)) =
 					(is_declare, method.name.get_ast_ref())
 				{
@@ -209,6 +207,25 @@ fn synthesise_class_declaration_extends_and_members<
 				let internal = internal_marker.is_some();
 				let has_defined_this = method.parameters.leading.0.is_some();
 
+				let this_shape = if internal && !has_defined_this {
+					TypeId::ANY_TYPE
+				} else if let Type::Class { type_parameters: Some(type_parameters), .. } =
+					checking_data.types.get_type_by_id(class_prototype)
+				{
+					use source_map::Nullable;
+					let arguments = crate::types::GenericArguments::ExplicitRestrictions(
+						type_parameters
+							.iter()
+							.map(|id| (*id, (*id, crate::SpanWithSource::NULL)))
+							.collect(),
+					);
+					checking_data.types.register_type(Type::PartiallyAppliedGenerics(
+						crate::types::PartiallyAppliedGenerics { on: class_prototype, arguments },
+					))
+				} else {
+					class_prototype
+				};
+
 				let behavior = FunctionRegisterBehavior::ClassMethod {
 					is_async,
 					is_generator,
@@ -216,11 +233,7 @@ fn synthesise_class_declaration_extends_and_members<
 					super_type: None,
 					// TODO
 					expecting: TypeId::ANY_TYPE,
-					this_shape: if internal && !has_defined_this {
-						TypeId::ANY_TYPE
-					} else {
-						class_prototype
-					},
+					this_shape,
 					internal_marker,
 					name: key.into_name_type(&mut checking_data.types),
 				};
@@ -291,7 +304,13 @@ fn synthesise_class_declaration_extends_and_members<
 				);
 				static_property_keys.push(key);
 			}
-			ClassMember::Indexer { name, indexer_type, return_type, is_readonly, position } => {
+			ClassMember::Indexer {
+				name: _name,
+				indexer_type,
+				return_type,
+				is_readonly,
+				position,
+			} => {
 				// TODO this redoes work done at registration. Because the info gets overwritten
 				let key = synthesise_type_annotation(indexer_type, environment, checking_data);
 				let value = synthesise_type_annotation(return_type, environment, checking_data);
@@ -306,7 +325,7 @@ fn synthesise_class_declaration_extends_and_members<
 				// TODO check declare
 
 				// TODO WIP
-				crate::utilities::notify!("Indexing (again) for  '{}'", name);
+				// crate::utilities::notify!("Indexing (again) for '{}' {:?}", name, value);
 				let value = PropertyValue::Value(value);
 				environment.info.register_property_on_type(
 					class_type,
@@ -367,8 +386,6 @@ fn synthesise_class_declaration_extends_and_members<
 		environment.info.variable_current_value.insert(variable, class_variable_type);
 	}
 
-	crate::utilities::notify!("At end {:?}", environment.context_type.free_variables);
-
 	{
 		// Static items and blocks
 		static_property_keys.reverse();
@@ -699,7 +716,13 @@ fn register_extends_and_member<T: crate::ReadFromFS>(
 					);
 				}
 			}
-			ClassMember::Indexer { name, indexer_type, return_type, is_readonly, position } => {
+			ClassMember::Indexer {
+				name: _name,
+				indexer_type,
+				return_type,
+				is_readonly,
+				position,
+			} => {
 				// TODO think this is okay
 				let key = synthesise_type_annotation(indexer_type, environment, checking_data);
 				let value = synthesise_type_annotation(return_type, environment, checking_data);
@@ -714,9 +737,8 @@ fn register_extends_and_member<T: crate::ReadFromFS>(
 				// TODO check declare
 
 				// TODO WIP
-				crate::utilities::notify!("Indexing for  '{}'", name);
+				// crate::utilities::notify!("Indexing for '{}' {:?}", name, class_type);
 				let value = PropertyValue::Value(value);
-				crate::utilities::notify!("{:?}", class_type);
 				environment.info.register_property_on_type(
 					class_type,
 					Publicity::Public,
diff --git a/checker/src/synthesis/expressions.rs b/checker/src/synthesis/expressions.rs
index 08e95b32..b7ab5cfa 100644
--- a/checker/src/synthesis/expressions.rs
+++ b/checker/src/synthesis/expressions.rs
@@ -32,9 +32,10 @@ use crate::{
 		objects::ObjectBuilder,
 		operations::is_null_or_undefined,
 		operations::{
-			evaluate_logical_operation_with_expression,
-			evaluate_pure_binary_operation_handle_errors, evaluate_unary_operator,
-			EqualityAndInequality, MathematicalAndBitwise, UnaryOperation,
+			evaluate_equality_inequality_operation, evaluate_logical_operation_with_expression,
+			evaluate_mathematical_operation, evaluate_unary_operator, EqualityAndInequality,
+			EqualityAndInequalityResultKind, LogicalOperator, MathematicalOrBitwiseOperation,
+			UnaryOperation,
 		},
 		template_literal::synthesise_template_literal_expression,
 		variables::VariableWithValue,
@@ -46,7 +47,7 @@ use crate::{
 	},
 	types::{
 		calling::{CallingInput, UnsynthesisedArgument},
-		get_larger_type,
+		helpers::get_larger_type,
 		logical::{Logical, LogicalOrValid},
 		printing::{print_property_key, print_type},
 		properties::{
@@ -251,11 +252,9 @@ pub(super) fn synthesise_expression<T: crate::ReadFromFS>(
 			| BinaryOperator::NullCoalescing = operator
 			{
 				let operator = match operator {
-					BinaryOperator::LogicalAnd => crate::features::operations::LogicalOperator::And,
-					BinaryOperator::LogicalOr => crate::features::operations::LogicalOperator::Or,
-					BinaryOperator::NullCoalescing => {
-						crate::features::operations::LogicalOperator::NullCoalescing
-					}
+					BinaryOperator::LogicalAnd => LogicalOperator::And,
+					BinaryOperator::LogicalOr => LogicalOperator::Or,
+					BinaryOperator::NullCoalescing => LogicalOperator::NullCoalescing,
 					_ => unreachable!(),
 				};
 				return evaluate_logical_operation_with_expression(
@@ -267,49 +266,120 @@ pub(super) fn synthesise_expression<T: crate::ReadFromFS>(
 					expecting, // TODO unwrap
 				)
 				.unwrap();
-			}
+			} else if let BinaryOperator::StrictEqual
+			| BinaryOperator::StrictNotEqual
+			| BinaryOperator::Equal
+			| BinaryOperator::NotEqual
+			| BinaryOperator::GreaterThan
+			| BinaryOperator::LessThan
+			| BinaryOperator::LessThanEqual
+			| BinaryOperator::GreaterThanEqual = operator
+			{
+				let rhs_ty =
+					synthesise_expression(rhs, environment, checking_data, TypeId::ANY_TYPE);
+				let operator = match operator {
+					BinaryOperator::StrictEqual => EqualityAndInequality::StrictEqual,
+					BinaryOperator::StrictNotEqual => EqualityAndInequality::StrictNotEqual,
+					BinaryOperator::Equal => EqualityAndInequality::Equal,
+					BinaryOperator::NotEqual => EqualityAndInequality::NotEqual,
+					BinaryOperator::GreaterThan => EqualityAndInequality::GreaterThan,
+					BinaryOperator::LessThan => EqualityAndInequality::LessThan,
+					BinaryOperator::LessThanEqual => EqualityAndInequality::LessThanOrEqual,
+					BinaryOperator::GreaterThanEqual => EqualityAndInequality::GreaterThanOrEqual,
+					_ => unreachable!(),
+				};
+				let result = evaluate_equality_inequality_operation(
+					lhs_ty,
+					&operator,
+					rhs_ty,
+					environment,
+					&mut checking_data.types,
+					checking_data.options.strict_casts,
+				);
 
-			let rhs_ty = synthesise_expression(rhs, environment, checking_data, TypeId::ANY_TYPE);
+				if let Ok((result, warning)) = result {
+					if let EqualityAndInequalityResultKind::Disjoint = warning {
+						let lhs_pos =
+							ASTNode::get_position(&**lhs).with_source(environment.get_source());
+						let rhs_pos =
+							ASTNode::get_position(&**rhs).with_source(environment.get_source());
+						let position = lhs_pos
+							.without_source()
+							.union(rhs_pos.without_source())
+							.with_source(environment.get_source());
+
+						checking_data.diagnostics_container.add_warning(
+							crate::TypeCheckWarning::DisjointEquality {
+								lhs: TypeStringRepresentation::from_type_id(
+									lhs_ty,
+									environment,
+									&checking_data.types,
+									false,
+								),
+								rhs: TypeStringRepresentation::from_type_id(
+									rhs_ty,
+									environment,
+									&checking_data.types,
+									false,
+								),
+								result: result == TypeId::TRUE,
+								position,
+							},
+						);
+					}
+
+					return result;
+				}
+
+				let lhs_pos = ASTNode::get_position(&**lhs).with_source(environment.get_source());
+				let rhs_pos = ASTNode::get_position(&**rhs).with_source(environment.get_source());
+				let position = lhs_pos
+					.without_source()
+					.union(rhs_pos.without_source())
+					.with_source(environment.get_source());
+
+				checking_data.diagnostics_container.add_error(
+					crate::TypeCheckError::InvalidEqualityOperation {
+						operator,
+						lhs: TypeStringRepresentation::from_type_id(
+							lhs_ty,
+							environment,
+							&checking_data.types,
+							false,
+						),
+						rhs: TypeStringRepresentation::from_type_id(
+							rhs_ty,
+							environment,
+							&checking_data.types,
+							false,
+						),
+						position,
+					},
+				);
 
-			if lhs_ty == TypeId::ERROR_TYPE || rhs_ty == TypeId::ERROR_TYPE {
 				return TypeId::ERROR_TYPE;
 			}
 
-			let lhs_pos = ASTNode::get_position(&**lhs).with_source(environment.get_source());
-			let rhs_pos = ASTNode::get_position(&**rhs).with_source(environment.get_source());
-
+			let rhs_ty = synthesise_expression(rhs, environment, checking_data, TypeId::ANY_TYPE);
 			let operator = match operator {
-				BinaryOperator::Add => MathematicalAndBitwise::Add.into(),
-				BinaryOperator::Subtract => MathematicalAndBitwise::Subtract.into(),
-				BinaryOperator::Multiply => MathematicalAndBitwise::Multiply.into(),
-				BinaryOperator::Divide => MathematicalAndBitwise::Divide.into(),
-				BinaryOperator::Modulo => MathematicalAndBitwise::Modulo.into(),
-				BinaryOperator::Exponent => MathematicalAndBitwise::Exponent.into(),
-				BinaryOperator::BitwiseShiftLeft => MathematicalAndBitwise::BitwiseShiftLeft.into(),
+				BinaryOperator::Add => MathematicalOrBitwiseOperation::Add,
+				BinaryOperator::Subtract => MathematicalOrBitwiseOperation::Subtract,
+				BinaryOperator::Multiply => MathematicalOrBitwiseOperation::Multiply,
+				BinaryOperator::Divide => MathematicalOrBitwiseOperation::Divide,
+				BinaryOperator::Modulo => MathematicalOrBitwiseOperation::Modulo,
+				BinaryOperator::Exponent => MathematicalOrBitwiseOperation::Exponent,
+				BinaryOperator::BitwiseShiftLeft => {
+					MathematicalOrBitwiseOperation::BitwiseShiftLeft
+				}
 				BinaryOperator::BitwiseShiftRight => {
-					MathematicalAndBitwise::BitwiseShiftRight.into()
+					MathematicalOrBitwiseOperation::BitwiseShiftRight
 				}
 				BinaryOperator::BitwiseShiftRightUnsigned => {
-					MathematicalAndBitwise::BitwiseShiftRightUnsigned.into()
-				}
-				BinaryOperator::BitwiseAnd => MathematicalAndBitwise::BitwiseAnd.into(),
-				BinaryOperator::BitwiseXOr => MathematicalAndBitwise::BitwiseXOr.into(),
-				BinaryOperator::BitwiseOr => MathematicalAndBitwise::BitwiseOr.into(),
-				BinaryOperator::StrictEqual => EqualityAndInequality::StrictEqual.into(),
-				BinaryOperator::StrictNotEqual => EqualityAndInequality::StrictNotEqual.into(),
-				BinaryOperator::Equal => EqualityAndInequality::Equal.into(),
-				BinaryOperator::NotEqual => EqualityAndInequality::NotEqual.into(),
-				BinaryOperator::GreaterThan => EqualityAndInequality::GreaterThan.into(),
-				BinaryOperator::LessThan => EqualityAndInequality::LessThan.into(),
-				BinaryOperator::LessThanEqual => EqualityAndInequality::LessThanOrEqual.into(),
-				BinaryOperator::GreaterThanEqual => {
-					EqualityAndInequality::GreaterThanOrEqual.into()
-				}
-				BinaryOperator::LogicalAnd
-				| BinaryOperator::LogicalOr
-				| BinaryOperator::NullCoalescing => {
-					unreachable!()
+					MathematicalOrBitwiseOperation::BitwiseShiftRightUnsigned
 				}
+				BinaryOperator::BitwiseAnd => MathematicalOrBitwiseOperation::BitwiseAnd,
+				BinaryOperator::BitwiseXOr => MathematicalOrBitwiseOperation::BitwiseXOr,
+				BinaryOperator::BitwiseOr => MathematicalOrBitwiseOperation::BitwiseOr,
 				BinaryOperator::Pipe | BinaryOperator::Compose => {
 					checking_data.raise_unimplemented_error(
 						"special operations",
@@ -317,14 +387,49 @@ pub(super) fn synthesise_expression<T: crate::ReadFromFS>(
 					);
 					return TypeId::UNIMPLEMENTED_ERROR_TYPE;
 				}
+				_ => {
+					unreachable!()
+				}
 			};
-			Instance::RValue(evaluate_pure_binary_operation_handle_errors(
-				(lhs_ty, lhs_pos),
+			let result = evaluate_mathematical_operation(
+				lhs_ty,
 				operator,
-				(rhs_ty, rhs_pos),
-				checking_data,
+				rhs_ty,
 				environment,
-			))
+				&mut checking_data.types,
+				checking_data.options.strict_casts,
+				checking_data.options.advanced_numbers,
+			);
+			if let Ok(value) = result {
+				Instance::RValue(value)
+			} else {
+				let lhs_pos = ASTNode::get_position(&**lhs).with_source(environment.get_source());
+				let rhs_pos = ASTNode::get_position(&**rhs).with_source(environment.get_source());
+				let position = lhs_pos
+					.without_source()
+					.union(rhs_pos.without_source())
+					.with_source(environment.get_source());
+
+				checking_data.diagnostics_container.add_error(
+					TypeCheckError::InvalidMathematicalOrBitwiseOperation {
+						operator,
+						lhs: TypeStringRepresentation::from_type_id(
+							lhs_ty,
+							environment,
+							&checking_data.types,
+							false,
+						),
+						rhs: TypeStringRepresentation::from_type_id(
+							rhs_ty,
+							environment,
+							&checking_data.types,
+							false,
+						),
+						position,
+					},
+				);
+				return TypeId::ERROR_TYPE;
+			}
 		}
 		Expression::UnaryOperation { operand, operator, position } => {
 			match operator {
@@ -1060,37 +1165,37 @@ fn operator_to_assignment_kind(
 		),
 		BinaryAssignmentOperator::AddAssign
 		| BinaryAssignmentOperator::BitwiseShiftRightUnsigned => {
-			AssignmentKind::PureUpdate(MathematicalAndBitwise::Add)
+			AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Add)
 		}
 		BinaryAssignmentOperator::SubtractAssign => {
-			AssignmentKind::PureUpdate(MathematicalAndBitwise::Subtract)
+			AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Subtract)
 		}
 		BinaryAssignmentOperator::MultiplyAssign => {
-			AssignmentKind::PureUpdate(MathematicalAndBitwise::Multiply)
+			AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Multiply)
 		}
 		BinaryAssignmentOperator::DivideAssign => {
-			AssignmentKind::PureUpdate(MathematicalAndBitwise::Divide)
+			AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Divide)
 		}
 		BinaryAssignmentOperator::ModuloAssign => {
-			AssignmentKind::PureUpdate(MathematicalAndBitwise::Modulo)
+			AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Modulo)
 		}
 		BinaryAssignmentOperator::ExponentAssign => {
-			AssignmentKind::PureUpdate(MathematicalAndBitwise::Exponent)
+			AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Exponent)
 		}
 		BinaryAssignmentOperator::BitwiseShiftLeftAssign => {
-			AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseShiftLeft)
+			AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseShiftLeft)
 		}
 		BinaryAssignmentOperator::BitwiseShiftRightAssign => {
-			AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseShiftRight)
+			AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseShiftRight)
 		}
 		BinaryAssignmentOperator::BitwiseAndAssign => {
-			AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseAnd)
+			AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseAnd)
 		}
 		BinaryAssignmentOperator::BitwiseXOrAssign => {
-			AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseXOr)
+			AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseXOr)
 		}
 		BinaryAssignmentOperator::BitwiseOrAssign => {
-			AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseOr)
+			AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseOr)
 		}
 	}
 }
@@ -1234,6 +1339,7 @@ pub(super) fn synthesise_object_literal<T: crate::ReadFromFS>(
 							print_type(*truthy_result, &checking_data.types, environment, true),
 							print_type(*otherwise_result, &checking_data.types, environment, true)
 						);
+
 						// Concatenate some types here if they all have the same keys
 						// && matches!(
 						// 	(lv, rv),
diff --git a/checker/src/synthesis/functions.rs b/checker/src/synthesis/functions.rs
index 9f628135..ab5dbf65 100644
--- a/checker/src/synthesis/functions.rs
+++ b/checker/src/synthesis/functions.rs
@@ -856,7 +856,7 @@ pub(super) fn build_overloaded_function(
 		let func = types.new_hoisted_function_type(as_function);
 
 		// IMPORTANT THAT RESULT IS ON THE RIGHT OF AND TYPE
-		result = types.new_and_type(func, result).unwrap();
+		result = types.new_and_type(func, result);
 	}
 
 	result
diff --git a/checker/src/synthesis/hoisting.rs b/checker/src/synthesis/hoisting.rs
index 4f9a2ea3..0781f2bf 100644
--- a/checker/src/synthesis/hoisting.rs
+++ b/checker/src/synthesis/hoisting.rs
@@ -501,8 +501,7 @@ pub(crate) fn hoist_statements<T: crate::ReadFromFS>(
 										&mut sub_environment,
 										checking_data,
 									);
-									extends =
-										checking_data.types.new_and_type(extends, new).unwrap();
+									extends = checking_data.types.new_and_type(extends, new);
 								}
 								checking_data.types.set_extends_on_interface(ty, extends);
 							}
@@ -539,8 +538,7 @@ pub(crate) fn hoist_statements<T: crate::ReadFromFS>(
 										environment,
 										checking_data,
 									);
-									extends =
-										checking_data.types.new_and_type(extends, new).unwrap();
+									extends = checking_data.types.new_and_type(extends, new);
 								}
 								checking_data.types.set_extends_on_interface(ty, extends);
 							}
diff --git a/checker/src/synthesis/interfaces.rs b/checker/src/synthesis/interfaces.rs
index fed3dc65..edd068e5 100644
--- a/checker/src/synthesis/interfaces.rs
+++ b/checker/src/synthesis/interfaces.rs
@@ -448,7 +448,7 @@ pub(super) fn synthesise_signatures<T: crate::ReadFromFS, B: SynthesiseInterface
 
 					for ta in iter {
 						let ty = synthesise_type_annotation(ta, environment, checking_data);
-						extends = checking_data.types.new_and_type(extends, ty).unwrap();
+						extends = checking_data.types.new_and_type(extends, ty);
 					}
 
 					checking_data.types.set_extends_on_interface(interface_type, extends);
diff --git a/checker/src/synthesis/mod.rs b/checker/src/synthesis/mod.rs
index f0c13d2f..e9a2ecf3 100644
--- a/checker/src/synthesis/mod.rs
+++ b/checker/src/synthesis/mod.rs
@@ -164,6 +164,8 @@ impl crate::ASTImplementation for EznoParser {
 			},
 			type_annotations: !is_js,
 			partial_syntax: lsp_mode,
+			// TODO
+			retain_blank_lines: lsp_mode,
 			is_expressions: extra_syntax,
 			..Default::default()
 		}
diff --git a/checker/src/synthesis/statements.rs b/checker/src/synthesis/statements.rs
index cd291cd1..227555b7 100644
--- a/checker/src/synthesis/statements.rs
+++ b/checker/src/synthesis/statements.rs
@@ -183,6 +183,10 @@ pub(super) fn synthesise_statement<T: crate::ReadFromFS>(
 				environment,
 				checking_data,
 				|environment, checking_data| {
+					crate::utilities::notify!(
+						"environment.info.narrowed_values={:?}",
+						environment.info.narrowed_values
+					);
 					synthesise_block_or_single_statement(&stmt.inner, environment, checking_data);
 				},
 				position.with_source(environment.get_source()),
diff --git a/checker/src/synthesis/type_annotations.rs b/checker/src/synthesis/type_annotations.rs
index bcbb40c8..304b3333 100644
--- a/checker/src/synthesis/type_annotations.rs
+++ b/checker/src/synthesis/type_annotations.rs
@@ -11,13 +11,11 @@ use crate::{
 	features::objects::ObjectBuilder,
 	types::{
 		generics::generic_type_arguments::GenericArguments,
-		properties::{PropertyKey, PropertyValue, Publicity},
-		Constant, Constructor, PartiallyAppliedGenerics, PolyNature, Type, TypeId,
-	},
-	types::{
 		generics::ExplicitTypeArguments,
+		helpers::{ArrayItem, Counter},
 		intrinsics::{self, distribute_tsc_string_intrinsic},
-		ArrayItem, Counter,
+		properties::{PropertyKey, PropertyValue, Publicity},
+		Constant, Constructor, PartiallyAppliedGenerics, PolyNature, Type, TypeId,
 	},
 	CheckingData, Map,
 };
@@ -248,10 +246,10 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>(
 							// crate::utilities::notify!(
 							// 	"{:?} and {:?}",
 							// 	inner_type_alias_id,
-							// 	inner_type_alias_id.is_some_and(intrinsics::tsc_string_intrinsic)
+							// 	inner_type_alias_id.is_some_and(intrinsics::is_tsc_string_intrinsic)
 							// );
 
-							if intrinsics::tsc_string_intrinsic(inner_type_id) {
+							if intrinsics::is_tsc_string_intrinsic(inner_type_id) {
 								distribute_tsc_string_intrinsic(
 									inner_type_id,
 									type_arguments.get(&TypeId::STRING_GENERIC).unwrap().0,
@@ -354,10 +352,15 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>(
 
 			let mut acc = iterator.next().expect("Empty intersection");
 			for right in iterator {
-				if let Ok(new_ty) = checking_data.types.new_and_type(acc, right) {
-					acc = new_ty;
-				} else {
-					checking_data.diagnostics_container.add_error(
+				let is_disjoint = crate::types::disjoint::types_are_disjoint(
+					acc,
+					right,
+					&mut Vec::new(),
+					environment,
+					&checking_data.types,
+				);
+				if is_disjoint {
+					checking_data.diagnostics_container.add_warning(
 						TypeCheckWarning::TypesDoNotIntersect {
 							left: TypeStringRepresentation::from_type_id(
 								acc,
@@ -374,8 +377,10 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>(
 							position: position.with_source(environment.get_source()),
 						},
 					);
-					return TypeId::ERROR_TYPE;
+					return TypeId::NEVER_TYPE;
 				}
+
+				acc = checking_data.types.new_and_type(acc, right);
 			}
 			acc
 		}
@@ -483,7 +488,7 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>(
 						items.push((pos, ArrayItem::Optional(annotation_ty)));
 					}
 					TupleElementKind::Spread => {
-						let slice = crate::types::as_slice(
+						let slice = crate::types::helpers::as_slice(
 							annotation_ty,
 							&checking_data.types,
 							environment,
@@ -514,15 +519,15 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>(
 			);
 
 			for (ty_position, item) in items {
+				use crate::types::helpers::ArrayItem;
+
 				let value = match item {
-					crate::types::ArrayItem::Member(item_ty) => PropertyValue::Value(item_ty),
-					crate::types::ArrayItem::Optional(item_ty) => {
-						PropertyValue::ConditionallyExists {
-							condition: TypeId::OPEN_BOOLEAN_TYPE,
-							truthy: Box::new(PropertyValue::Value(item_ty)),
-						}
-					}
-					crate::types::ArrayItem::Wildcard(on) => {
+					ArrayItem::Member(item_ty) => PropertyValue::Value(item_ty),
+					ArrayItem::Optional(item_ty) => PropertyValue::ConditionallyExists {
+						condition: TypeId::OPEN_BOOLEAN_TYPE,
+						truthy: Box::new(PropertyValue::Value(item_ty)),
+					},
+					ArrayItem::Wildcard(on) => {
 						crate::utilities::notify!("found wildcard");
 						let after = idx.into_type(&mut checking_data.types);
 
@@ -716,7 +721,8 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>(
 					checking_data.types.register_type(Type::Constructor(
 						crate::types::Constructor::BinaryOperator {
 							lhs: acc,
-							operator: crate::features::operations::MathematicalAndBitwise::Add,
+							operator:
+								crate::features::operations::MathematicalOrBitwiseOperation::Add,
 							rhs: lhs,
 							result: TypeId::STRING_TYPE,
 						},
@@ -733,7 +739,7 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>(
 				};
 				let constructor = crate::types::Constructor::BinaryOperator {
 					lhs: acc,
-					operator: crate::features::operations::MathematicalAndBitwise::Add,
+					operator: crate::features::operations::MathematicalOrBitwiseOperation::Add,
 					rhs,
 					result: TypeId::STRING_TYPE,
 				};
@@ -749,7 +755,8 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>(
 					checking_data.types.register_type(Type::Constructor(
 						crate::types::Constructor::BinaryOperator {
 							lhs: acc,
-							operator: crate::features::operations::MathematicalAndBitwise::Add,
+							operator:
+								crate::features::operations::MathematicalOrBitwiseOperation::Add,
 							rhs: lhs,
 							result: TypeId::STRING_TYPE,
 						},
diff --git a/checker/src/synthesis/variables.rs b/checker/src/synthesis/variables.rs
index 236d890f..56286456 100644
--- a/checker/src/synthesis/variables.rs
+++ b/checker/src/synthesis/variables.rs
@@ -15,7 +15,8 @@ use crate::{
 	},
 	synthesis::parser_property_key_to_checker_property_key,
 	types::{
-		get_larger_type, printing,
+		helpers::get_larger_type,
+		printing,
 		properties::{
 			get_properties_on_single_type, get_property_key_names_on_a_single_type, PropertyKey,
 			Publicity,
diff --git a/checker/src/types/calling.rs b/checker/src/types/calling.rs
index 63f46696..9ea6c083 100644
--- a/checker/src/types/calling.rs
+++ b/checker/src/types/calling.rs
@@ -22,7 +22,7 @@ use crate::{
 	types::{
 		functions::{FunctionBehavior, FunctionEffect, FunctionType},
 		generics::substitution::SubstitutionArguments,
-		get_structure_arguments_based_on_object_constraint,
+		helpers::get_structure_arguments_based_on_object_constraint,
 		logical::{Invalid, Logical, LogicalOrValid, NeedsCalculation, PossibleLogical},
 		properties::AccessMode,
 		substitute, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, Type,
@@ -474,7 +474,8 @@ fn get_logical_callable_from_type(
 	// }
 	if ty == TypeId::ANY_TYPE {
 		crate::utilities::notify!("Calling ANY");
-		return Ok(NeedsCalculation::Infer { on: from.unwrap() }.into());
+		// TODO temp
+		return Ok(NeedsCalculation::Infer { on: from.unwrap_or(TypeId::ERROR_TYPE) }.into());
 	}
 
 	let le_ty = types.get_type_by_id(ty);
@@ -613,7 +614,10 @@ fn call_logical<B: CallCheckingBehavior>(
 						|| is_independent_function
 						|| matches!(
 							const_fn_ident.as_str(),
-							"satisfies" | "is_dependent" | "bind" | "proxy:constructor"
+							"satisfies"
+								| "is_dependent" | "bind"
+								| "proxy:constructor" | "setPrototypeOf"
+								| "getPrototypeOf"
 						);
 
 					// {
@@ -862,6 +866,13 @@ fn call_logical<B: CallCheckingBehavior>(
 						call_site: input.call_site,
 						possibly_thrown,
 					});
+				} else {
+					// This fixes tree shaking of functions that are called within callbacks
+					// TODO this could be done under an option
+					let value = Event::Miscellaneous(
+						crate::events::MiscellaneousEvents::MarkFunctionAsCalled(function.function),
+					);
+					behavior.get_latest_info(top_environment).events.push(value);
 				}
 
 				Ok(result)
diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs
index a49c5f59..9e088ea6 100644
--- a/checker/src/types/disjoint.rs
+++ b/checker/src/types/disjoint.rs
@@ -1,4 +1,7 @@
-use super::{Constant, PartiallyAppliedGenerics, Type, TypeId, TypeStore};
+use super::{
+	helpers, Constant, Constructor, MathematicalOrBitwiseOperation, PartiallyAppliedGenerics, Type,
+	TypeId, TypeStore,
+};
 use crate::context::InformationChain;
 
 /// For equality + [`crate::intrinsics::Intrinsics::Not`]
@@ -30,15 +33,9 @@ pub fn types_are_disjoint(
 		if let Type::Or(lhs_lhs, lhs_rhs) = lhs_ty {
 			types_are_disjoint(*lhs_lhs, rhs, already_checked, information, types)
 				&& types_are_disjoint(*lhs_rhs, rhs, already_checked, information, types)
-		} else if let Type::And(lhs_lhs, lhs_rhs) = lhs_ty {
-			types_are_disjoint(*lhs_lhs, rhs, already_checked, information, types)
-				|| types_are_disjoint(*lhs_rhs, rhs, already_checked, information, types)
 		} else if let Type::Or(rhs_lhs, rhs_rhs) = rhs_ty {
 			types_are_disjoint(lhs, *rhs_lhs, already_checked, information, types)
 				&& types_are_disjoint(lhs, *rhs_rhs, already_checked, information, types)
-		} else if let Type::And(rhs_lhs, rhs_rhs) = rhs_ty {
-			types_are_disjoint(lhs, *rhs_lhs, already_checked, information, types)
-				|| types_are_disjoint(lhs, *rhs_rhs, already_checked, information, types)
 		} else if let Type::AliasTo { to, parameters: None, name: _ } = lhs_ty {
 			// TODO temp fix, need infer ANY
 			if matches!(*to, TypeId::ANY_TYPE) {
@@ -74,7 +71,7 @@ pub fn types_are_disjoint(
 		}) = lhs_ty
 		{
 			use super::subtyping;
-			let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap();
+			let lhs_inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap();
 			let mut state = subtyping::State {
 				// TODO
 				already_checked: already_checked.clone(),
@@ -84,16 +81,16 @@ pub fn types_are_disjoint(
 				object_constraints: None,
 			};
 
-			crate::utilities::notify!("{:?}", (lhs, inner));
+			// crate::utilities::notify!("{:?}", (lhs, lhs_inner));
 
-			subtyping::type_is_subtype(rhs, inner, &mut state, information, types).is_subtype()
+			subtyping::type_is_subtype(lhs_inner, rhs, &mut state, information, types).is_subtype()
 		} else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
 			on: TypeId::NOT_RESTRICTION,
 			arguments,
 		}) = rhs_ty
 		{
 			use super::subtyping;
-			let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap();
+			let rhs_inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap();
 			let mut state = subtyping::State {
 				// TODO
 				already_checked: already_checked.clone(),
@@ -103,75 +100,99 @@ pub fn types_are_disjoint(
 				object_constraints: None,
 			};
 
-			crate::utilities::notify!("{:?}", (lhs, inner));
+			crate::utilities::notify!("{:?}", (rhs, rhs_inner));
 
-			subtyping::type_is_subtype(lhs, inner, &mut state, information, types).is_subtype()
-		} else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
-			on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE,
-			arguments: _,
-		}) = lhs_ty
+			subtyping::type_is_subtype(rhs_inner, lhs, &mut state, information, types).is_subtype()
+		} else if let Type::And(rhs_lhs, rhs_rhs) = rhs_ty {
+			types_are_disjoint(lhs, *rhs_lhs, already_checked, information, types)
+				|| types_are_disjoint(lhs, *rhs_rhs, already_checked, information, types)
+		} else if let Type::And(lhs_lhs, lhs_rhs) = lhs_ty {
+			types_are_disjoint(*lhs_lhs, rhs, already_checked, information, types)
+				|| types_are_disjoint(*lhs_rhs, rhs, already_checked, information, types)
+		} else if let Type::PartiallyAppliedGenerics(
+			args @ PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments: _ },
+		) = lhs_ty
 		{
-			let range = super::intrinsics::get_range(lhs, types).unwrap();
-			if let Some(rhs_range) = super::intrinsics::get_range(rhs, types) {
-				let overlap = range.overlaps(rhs_range);
-				crate::utilities::notify!("{:?}", overlap);
-				!overlap
-			} else {
-				crate::utilities::notify!("Here");
-				true
-			}
-		} else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
-			on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE,
-			arguments: _,
-		}) = rhs_ty
+			// TODO also offset
+			number_modulo_disjoint(args, rhs, types)
+		} else if let Type::PartiallyAppliedGenerics(
+			args @ PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments: _ },
+		) = rhs_ty
 		{
-			let range = super::intrinsics::get_range(rhs, types).unwrap();
-			if let Some(lhs_range) = super::intrinsics::get_range(lhs, types) {
-				let overlap = range.overlaps(lhs_range);
-				crate::utilities::notify!("{:?}", overlap);
-				!overlap
-			} else {
-				crate::utilities::notify!("Here");
-				true
-			}
-		} else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
-			on: TypeId::MULTIPLE_OF,
-			arguments,
+			// TODO also offset
+			number_modulo_disjoint(args, lhs, types)
+		} else if let Type::PartiallyAppliedGenerics(
+			args @ PartiallyAppliedGenerics {
+				on: TypeId::GREATER_THAN | TypeId::LESS_THAN,
+				arguments: _,
+			},
+		) = rhs_ty
+		{
+			number_range_disjoint(args, lhs, types)
+		} else if let Type::PartiallyAppliedGenerics(
+			args @ PartiallyAppliedGenerics {
+				on: TypeId::GREATER_THAN | TypeId::LESS_THAN,
+				arguments: _,
+			},
+		) = lhs_ty
+		{
+			number_range_disjoint(args, rhs, types)
+		} else if let Type::Constructor(Constructor::BinaryOperator {
+			lhs: _lhs,
+			operator: MathematicalOrBitwiseOperation::Modulo,
+			rhs,
+			result: _,
 		}) = lhs_ty
 		{
-			// Little bit complex here because dealing with decimal types, not integers
-			if let (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) = (
-				types.get_type_by_id(
-					arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(),
-				),
-				types.get_type_by_id(rhs),
-			) {
-				let result = rhs % lhs != 0.;
-				crate::utilities::notify!("{:?} {:?}", rhs, lhs);
-				result
+			if let (
+				Type::Constant(Constant::Number(lhs_mod)),
+				Type::Constant(Constant::Number(num)),
+			) = (types.get_type_by_id(*rhs), rhs_ty)
+			{
+				crate::utilities::notify!("{:?}", (num, lhs_mod));
+				// Modulos return negative for negative number :(
+				// Checking whether out of range here
+				num.abs() > **lhs_mod
 			} else {
-				crate::utilities::notify!("Here");
 				false
 			}
-		} else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
-			on: TypeId::MULTIPLE_OF,
-			arguments,
+		} else if let Type::Constructor(Constructor::BinaryOperator {
+			lhs: _lhs,
+			operator: MathematicalOrBitwiseOperation::Modulo,
+			rhs,
+			result: _,
 		}) = rhs_ty
 		{
-			// Little bit complex here because dealing with decimal types, not integers
-			if let (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) = (
-				types.get_type_by_id(lhs),
-				types.get_type_by_id(
-					arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(),
-				),
-			) {
-				let result = lhs % rhs != 0.;
-				crate::utilities::notify!("{:?} {:?}", lhs, rhs);
-				result
+			if let (
+				Type::Constant(Constant::Number(num)),
+				Type::Constant(Constant::Number(rhs_mod)),
+			) = (types.get_type_by_id(*rhs), lhs_ty)
+			{
+				crate::utilities::notify!("{:?}", (num, rhs_mod));
+				// Modulos return negative for negative number :(
+				// Checking whether out of range here
+				num.abs() > **rhs_mod
 			} else {
-				crate::utilities::notify!("Here");
 				false
 			}
+		} else if let Type::Constructor(Constructor::BinaryOperator {
+			operator: MathematicalOrBitwiseOperation::Add,
+			result: TypeId::STRING_TYPE,
+			..
+		}) = rhs_ty
+		{
+			let lhs = helpers::TemplatelLiteralExpansion::from_type(lhs, types);
+			let rhs = helpers::TemplatelLiteralExpansion::from_type(rhs, types);
+			lhs.is_disjoint(&rhs)
+		} else if let Type::Constructor(Constructor::BinaryOperator {
+			operator: MathematicalOrBitwiseOperation::Add,
+			result: TypeId::STRING_TYPE,
+			..
+		}) = lhs_ty
+		{
+			let lhs = helpers::TemplatelLiteralExpansion::from_type(lhs, types);
+			let rhs = helpers::TemplatelLiteralExpansion::from_type(rhs, types);
+			lhs.is_disjoint(&rhs)
 		} else if let Some(lhs) = super::get_constraint(lhs, types) {
 			// TODO not sure whether these should be here?
 			types_are_disjoint(lhs, rhs, already_checked, information, types)
@@ -192,6 +213,18 @@ pub fn types_are_disjoint(
 			}
 		} else if let Type::Constant(rhs_cst) = rhs_ty {
 			types_are_disjoint(rhs_cst.get_backing_type(), lhs, already_checked, information, types)
+		} else if let Type::Object(crate::types::ObjectNature::AnonymousTypeAnnotation(
+			_properties,
+		)) = lhs_ty
+		{
+			// TODO check properties
+			false
+		} else if let Type::Object(crate::types::ObjectNature::AnonymousTypeAnnotation(
+			_properties,
+		)) = rhs_ty
+		{
+			// TODO check properties
+			false
 		} else {
 			crate::utilities::notify!(
 				"{:?} cap {:?} == empty ? cases. Might be missing, calling disjoint",
@@ -202,3 +235,100 @@ pub fn types_are_disjoint(
 		}
 	}
 }
+
+fn number_modulo_disjoint(
+	this: &PartiallyAppliedGenerics,
+	other: TypeId,
+	types: &TypeStore,
+) -> bool {
+	crate::utilities::notify!("Here number_modulo_disjoint");
+	let PartiallyAppliedGenerics { arguments, .. } = this;
+	let other_ty = types.get_type_by_id(other);
+	let argument =
+		types.get_type_by_id(arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap());
+
+	let Type::Constant(Constant::Number(argument)) = argument else {
+		crate::utilities::notify!("Gets complex here");
+		return false;
+	};
+
+	let offset = 0f64.try_into().unwrap();
+	let this = crate::utilities::modulo_class::ModuloClass::new(*argument, offset);
+
+	// Little bit complex here because dealing with decimal types, not integers
+	if let Type::Constant(Constant::Number(other)) = other_ty {
+		!this.contains(*other)
+	} else {
+		let (range, modulo_class) = super::intrinsics::get_range_and_mod_class(other, types);
+		crate::utilities::notify!("Disjoint with modulo, {:?}, {:?}", range, modulo_class);
+		if let Some(range) = range {
+			return !range.contains_multiple_of(*argument);
+		}
+		if let Some(modulo_class) = modulo_class {
+			return this.disjoint(modulo_class);
+		}
+		crate::utilities::notify!("Here {:?}", other_ty);
+		false
+	}
+}
+
+fn number_range_disjoint(
+	this: &PartiallyAppliedGenerics,
+	other: TypeId,
+	types: &TypeStore,
+) -> bool {
+	let PartiallyAppliedGenerics { on, arguments, .. } = this;
+	let greater_than = *on == TypeId::GREATER_THAN;
+	let this_ty =
+		types.get_type_by_id(arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap());
+
+	if let Type::Constant(Constant::Number(this)) = this_ty {
+		let other_ty = types.get_type_by_id(other);
+		if let Type::Constant(Constant::Number(other)) = other_ty {
+			crate::utilities::notify!("{:?} {} {}", on, other, this);
+			if greater_than {
+				other < this
+			} else {
+				other > this
+			}
+		} else {
+			crate::utilities::notify!("Unsure here {:?}", (other_ty, this_ty));
+			false
+		}
+	} else {
+		crate::utilities::notify!("Unsure here");
+		false
+	}
+}
+
+// fn todo() {
+// 	// else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
+// 	// 	on: TypeId::GREATER_THAN
+// 	// 	arguments: _,
+// 	// }) = lhs_ty
+// 	// {
+// 	// 	let range = super::intrinsics::get_range(lhs, types).unwrap();
+// 	// 	if let Some(rhs_range) = super::intrinsics::get_range(rhs, types) {
+// 	// 		let overlap = range.overlaps(rhs_range);
+// 	// 		crate::utilities::notify!("{:?}", overlap);
+// 	// 		!overlap
+// 	// 	} else {
+// 	// 		crate::utilities::notify!("Here");
+// 	// 		true
+// 	// 	}
+// 	// } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
+// 	// 	on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE,
+// 	// 	arguments: _,
+// 	// }) = rhs_ty
+// 	// {
+// 	// 	let range = super::intrinsics::get_range(rhs, types).unwrap();
+// 	// 	if let Some(lhs_range) = super::intrinsics::get_range(lhs, types) {
+// 	// 		let overlap = range.overlaps(lhs_range);
+// 	// 		crate::utilities::notify!("{:?}", overlap);
+// 	// 		!overlap
+// 	// 	} else {
+// 	// 		crate::utilities::notify!("Here");
+// 	// 		true
+// 	// 	}
+// 	// }
+// }
diff --git a/checker/src/types/generics/substitution.rs b/checker/src/types/generics/substitution.rs
index dacfe81a..1a6c6900 100644
--- a/checker/src/types/generics/substitution.rs
+++ b/checker/src/types/generics/substitution.rs
@@ -146,7 +146,7 @@ pub(crate) fn substitute(
 			let generic_arguments = structure_arguments.clone();
 
 			// Fold intrinsic type
-			if intrinsics::tsc_string_intrinsic(on) {
+			if intrinsics::is_tsc_string_intrinsic(on) {
 				let arg =
 					structure_arguments.get_structure_restriction(TypeId::STRING_GENERIC).unwrap();
 				let value = substitute(arg, arguments, environment, types);
@@ -195,7 +195,7 @@ pub(crate) fn substitute(
 			let rhs = *rhs;
 			let lhs = substitute(*lhs, arguments, environment, types);
 			let rhs = substitute(rhs, arguments, environment, types);
-			types.new_and_type(lhs, rhs).unwrap_or(TypeId::NEVER_TYPE)
+			types.new_and_type(lhs, rhs) // .unwrap_or(TypeId::NEVER_TYPE)
 		}
 		Type::Or(lhs, rhs) => {
 			let rhs = *rhs;
@@ -237,8 +237,18 @@ pub(crate) fn substitute(
 				let lhs = substitute(lhs, arguments, environment, types);
 				let rhs = substitute(rhs, arguments, environment, types);
 
-				match evaluate_mathematical_operation(lhs, operator, rhs, environment, types, false)
-				{
+				// TODO
+				let advanced_numbers = false;
+
+				match evaluate_mathematical_operation(
+					lhs,
+					operator,
+					rhs,
+					environment,
+					types,
+					false,
+					advanced_numbers,
+				) {
 					Ok(result) => result,
 					Err(()) => {
 						unreachable!(
diff --git a/checker/src/types/helpers.rs b/checker/src/types/helpers.rs
new file mode 100644
index 00000000..f8b6ab18
--- /dev/null
+++ b/checker/src/types/helpers.rs
@@ -0,0 +1,471 @@
+use super::{
+	get_constraint, properties, subtyping, Constant, Constructor, GenericArguments, GenericChain,
+	InformationChain, MathematicalOrBitwiseOperation, PartiallyAppliedGenerics, PolyNature, Type,
+	TypeId, TypeStore,
+};
+
+pub(crate) fn get_structure_arguments_based_on_object_constraint<'a, C: InformationChain>(
+	object: TypeId,
+	info_chain: &C,
+	types: &'a TypeStore,
+) -> Option<&'a GenericArguments> {
+	if let Some(object_constraint) =
+		info_chain.get_chain_of_info().find_map(|c| c.object_constraints.get(&object).copied())
+	{
+		let ty = types.get_type_by_id(object_constraint);
+		if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { arguments, .. }) = ty {
+			Some(arguments)
+		} else {
+			crate::utilities::notify!("Generics might be missed here {:?}", ty);
+			None
+		}
+	} else {
+		None
+	}
+}
+
+pub(crate) fn tuple_like(id: TypeId, types: &TypeStore, environment: &crate::Environment) -> bool {
+	// TODO should be `ObjectNature::AnonymousObjectType` or something else
+	let ty = types.get_type_by_id(id);
+	if let Type::Object(super::ObjectNature::RealDeal) = ty {
+		environment
+			.get_chain_of_info()
+			.any(|info| info.prototypes.get(&id).is_some_and(|p| *p == TypeId::ARRAY_TYPE))
+	} else if let Type::AliasTo { to, .. } = ty {
+		tuple_like(*to, types, environment)
+	} else {
+		false
+	}
+}
+
+pub(crate) fn _unfold_tuple(_ty: TypeId) -> TypeId {
+	// return Type::PropertyOf()
+	todo!()
+}
+
+pub(crate) fn _assign_to_tuple(_ty: TypeId) -> TypeId {
+	todo!()
+	// if let PropertyKey::Type(slice) =
+}
+
+pub fn get_array_length(
+	ctx: &impl InformationChain,
+	on: TypeId,
+	types: &TypeStore,
+) -> Result<ordered_float::NotNan<f64>, Option<TypeId>> {
+	let length_property = properties::PropertyKey::String(std::borrow::Cow::Borrowed("length"));
+	let id = properties::get_simple_property_value(ctx, on, &length_property, types).ok_or(None)?;
+	if let Type::Constant(Constant::Number(n)) = types.get_type_by_id(id) {
+		Ok(*n)
+	} else {
+		Err(Some(id))
+	}
+}
+
+/// TODO name?
+#[derive(Clone, Copy, Debug)]
+pub enum ArrayItem {
+	Member(TypeId),
+	Optional(TypeId),
+	Wildcard(TypeId),
+}
+
+/// WIP
+pub(crate) fn as_slice(
+	ty: TypeId,
+	types: &TypeStore,
+	environment: &crate::Environment,
+) -> Result<Vec<ArrayItem>, ()> {
+	if tuple_like(ty, types, environment) {
+		let ty = if let Type::AliasTo { to, .. } = types.get_type_by_id(ty) { *to } else { ty };
+		let properties =
+			environment.get_chain_of_info().find_map(|info| info.current_properties.get(&ty));
+		if let Some(properties) = properties {
+			Ok(properties
+				.iter()
+				.filter_map(|(_, key, value)| {
+					let not_length_value = !key.is_equal_to("length");
+					not_length_value.then(|| {
+						crate::utilities::notify!("key (should be incremental) {:?}", key);
+						if key.as_number(types).is_some() {
+							if let crate::PropertyValue::ConditionallyExists { .. } = value {
+								ArrayItem::Optional(value.as_get_type(types))
+							} else {
+								ArrayItem::Member(value.as_get_type(types))
+							}
+						} else {
+							ArrayItem::Wildcard(value.as_get_type(types))
+						}
+					})
+				})
+				.collect())
+		} else {
+			crate::utilities::notify!("BAD");
+			Err(())
+		}
+	} else {
+		Err(())
+	}
+}
+
+/// WIP for counting slice indexes
+#[derive(derive_enum_from_into::EnumFrom, Clone, Copy, Debug)]
+pub enum Counter {
+	On(usize),
+	AddTo(TypeId),
+}
+
+impl Counter {
+	/// TODO &mut or Self -> Self?
+	pub fn increment(&mut self, types: &mut TypeStore) {
+		match self {
+			Counter::On(value) => {
+				*value += 1;
+			}
+			Counter::AddTo(value) => {
+				*value = types.register_type(Type::Constructor(Constructor::BinaryOperator {
+					lhs: *value,
+					operator: MathematicalOrBitwiseOperation::Add,
+					rhs: TypeId::ONE,
+					// TODO could be greater than
+					result: TypeId::NUMBER_TYPE,
+				}));
+			}
+		}
+	}
+
+	pub fn add_type(&mut self, ty: TypeId, types: &mut TypeStore) {
+		let current = self.into_type(types);
+		let new = types.register_type(Type::Constructor(Constructor::BinaryOperator {
+			lhs: ty,
+			operator: MathematicalOrBitwiseOperation::Add,
+			rhs: current,
+			// TODO could be greater than
+			result: TypeId::NUMBER_TYPE,
+		}));
+		*self = Counter::AddTo(new);
+	}
+
+	pub(crate) fn into_property_key(self) -> properties::PropertyKey<'static> {
+		match self {
+			Counter::On(value) => properties::PropertyKey::from_usize(value),
+			Counter::AddTo(ty) => properties::PropertyKey::Type(ty),
+		}
+	}
+
+	pub(crate) fn into_type(self, types: &mut TypeStore) -> TypeId {
+		match self {
+			Counter::On(value) => {
+				types.new_constant_type(Constant::Number((value as f64).try_into().unwrap()))
+			}
+			Counter::AddTo(ty) => ty,
+		}
+	}
+}
+
+/// To fill in for TSC behavior for mapped types
+#[must_use]
+pub fn references_key_of(id: TypeId, types: &TypeStore) -> bool {
+	match types.get_type_by_id(id) {
+		Type::AliasTo { to, .. } => references_key_of(*to, types),
+		Type::Or(lhs, rhs) | Type::And(lhs, rhs) => {
+			references_key_of(*lhs, types) || references_key_of(*rhs, types)
+		}
+		Type::RootPolyType(c) => references_key_of(c.get_constraint(), types),
+		Type::Constructor(c) => {
+			if let Constructor::KeyOf(..) = c {
+				true
+			} else if let Constructor::BinaryOperator { lhs, rhs, operator: _, result: _ } = c {
+				references_key_of(*lhs, types) || references_key_of(*rhs, types)
+			} else {
+				crate::utilities::notify!("TODO might have missed keyof {:?}", c);
+				false
+			}
+		}
+		Type::PartiallyAppliedGenerics(a) => {
+			if let GenericArguments::ExplicitRestrictions(ref e) = a.arguments {
+				e.0.iter().any(|(_, (lhs, _))| references_key_of(*lhs, types))
+			} else {
+				false
+			}
+		}
+		Type::Interface { .. }
+		| Type::Class { .. }
+		| Type::Constant(_)
+		| Type::Narrowed { .. }
+		| Type::FunctionReference(_)
+		| Type::Object(_)
+		| Type::SpecialObject(_) => false,
+	}
+}
+
+#[allow(clippy::match_like_matches_macro)]
+#[must_use]
+pub fn _type_is_error(ty: TypeId, types: &TypeStore) -> bool {
+	if ty == TypeId::UNIMPLEMENTED_ERROR_TYPE {
+		true
+	} else if let Type::RootPolyType(PolyNature::Error(_)) = types.get_type_by_id(ty) {
+		true
+	} else {
+		false
+	}
+}
+
+/// TODO want to skip mapped generics because that would break subtyping
+#[must_use]
+pub fn get_type_as_conditional(ty: TypeId, types: &TypeStore) -> Option<(TypeId, TypeId, TypeId)> {
+	match types.get_type_by_id(ty) {
+		Type::Constructor(crate::types::Constructor::ConditionalResult {
+			condition,
+			truthy_result,
+			otherwise_result,
+			result_union: _,
+		}) => Some((*condition, *truthy_result, *otherwise_result)),
+		Type::Or(left, right) => Some((TypeId::OPEN_BOOLEAN_TYPE, *left, *right)),
+		// For reasons !
+		Type::RootPolyType(PolyNature::MappedGeneric { .. }) => None,
+		_ => {
+			if let Some(constraint) = get_constraint(ty, types) {
+				get_type_as_conditional(constraint, types)
+			} else {
+				None
+			}
+		}
+	}
+}
+
+/// TODO wip
+#[must_use]
+pub fn is_pseudo_continous((ty, generics): (TypeId, GenericChain), types: &TypeStore) -> bool {
+	if let TypeId::NUMBER_TYPE | TypeId::STRING_TYPE = ty {
+		true
+	} else if let Some(arg) = generics.as_ref().and_then(|args| args.get_single_argument(ty)) {
+		is_pseudo_continous((arg, generics), types)
+	} else {
+		let ty = types.get_type_by_id(ty);
+		if let Type::Or(left, right) = ty {
+			is_pseudo_continous((*left, generics), types)
+				|| is_pseudo_continous((*right, generics), types)
+		} else if let Type::And(left, right) = ty {
+			is_pseudo_continous((*left, generics), types)
+				&& is_pseudo_continous((*right, generics), types)
+		} else if let Type::RootPolyType(PolyNature::MappedGeneric { extends, .. }) = ty {
+			is_pseudo_continous((*extends, generics), types)
+		} else {
+			false
+		}
+	}
+}
+
+#[must_use]
+pub fn is_inferrable_type(ty: TypeId) -> bool {
+	matches!(ty, TypeId::ANY_TO_INFER_TYPE | TypeId::OBJECT_TYPE)
+}
+
+/// For quick checking. Wraps [`subtyping::type_is_subtype`]
+#[must_use]
+pub fn simple_subtype(
+	expr_ty: TypeId,
+	to_satisfy: TypeId,
+	information: &impl InformationChain,
+	types: &TypeStore,
+) -> bool {
+	let mut state = subtyping::State {
+		already_checked: Default::default(),
+		mode: Default::default(),
+		contributions: Default::default(),
+		others: subtyping::SubTypingOptions { allow_errors: true },
+		object_constraints: None,
+	};
+
+	subtyping::type_is_subtype(to_satisfy, expr_ty, &mut state, information, types).is_subtype()
+}
+
+// unfolds narrowing
+#[must_use]
+pub fn get_origin(ty: TypeId, types: &TypeStore) -> TypeId {
+	if let Type::Narrowed { from, .. } = types.get_type_by_id(ty) {
+		// Hopefully don't have a nested from
+		*from
+	} else {
+		ty
+	}
+}
+
+/// Temp fix for equality of narrowing stuff
+#[must_use]
+pub fn is_not_of_constant(ty: TypeId, types: &TypeStore) -> bool {
+	if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
+		on: TypeId::NOT_RESTRICTION,
+		arguments,
+	}) = types.get_type_by_id(ty)
+	{
+		let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap();
+		types.get_type_by_id(inner).is_constant()
+	} else {
+		false
+	}
+}
+
+// TODO narrowed as well
+#[must_use]
+pub fn type_equal(lhs: TypeId, rhs: TypeId, types: &TypeStore) -> bool {
+	if lhs == rhs {
+		true
+	} else if let (Type::Constant(lhs), Type::Constant(rhs)) =
+		(types.get_type_by_id(lhs), types.get_type_by_id(rhs))
+	{
+		lhs == rhs
+	} else {
+		false
+	}
+}
+
+#[derive(Debug, Copy, Clone)]
+pub struct AndCondition(pub TypeId);
+
+#[derive(Debug)]
+pub struct OrCase(pub Vec<AndCondition>);
+
+#[must_use]
+pub fn into_conditions(id: TypeId, types: &TypeStore) -> Vec<AndCondition> {
+	let ty = types.get_type_by_id(id);
+
+	// Temp fix
+	if let Type::Constructor(Constructor::BinaryOperator { result, .. }) = ty {
+		return if matches!(*result, TypeId::NUMBER_TYPE | TypeId::STRING_TYPE) {
+			vec![AndCondition(id)]
+		} else {
+			into_conditions(*result, types)
+		};
+	}
+
+	if let Type::And(lhs, rhs) = ty {
+		let mut buf = into_conditions(*lhs, types);
+		buf.append(&mut into_conditions(*rhs, types));
+		buf
+	} else if let Some(backing) = get_constraint_or_alias(id, types) {
+		into_conditions(backing, types)
+	} else {
+		vec![AndCondition(id)]
+	}
+}
+
+#[must_use]
+pub fn into_cases(id: TypeId, types: &TypeStore) -> Vec<OrCase> {
+	let ty = types.get_type_by_id(id);
+	if let Type::Or(lhs, rhs) = ty {
+		let mut buf = into_cases(*lhs, types);
+		buf.append(&mut into_cases(*rhs, types));
+		buf
+	} else if let Some(backing) = get_constraint_or_alias(id, types) {
+		into_cases(backing, types)
+	} else {
+		vec![OrCase(into_conditions(id, types))]
+	}
+}
+
+/// Returns the constraint or base of a constant for a type. Otherwise just return the type
+#[must_use]
+pub fn get_larger_type(on: TypeId, types: &TypeStore) -> TypeId {
+	if let Some(poly_base) = get_constraint(on, types) {
+		poly_base
+	} else if let Type::Constant(cst) = types.get_type_by_id(on) {
+		cst.get_backing_type()
+	} else {
+		on
+	}
+}
+
+#[must_use]
+pub fn get_constraint_or_alias(on: TypeId, types: &TypeStore) -> Option<TypeId> {
+	match types.get_type_by_id(on) {
+		Type::RootPolyType(rpt) => Some(rpt.get_constraint()),
+		Type::Constructor(constr) => Some(constr.get_constraint()),
+		Type::AliasTo { to, parameters: None, .. } => Some(*to),
+		Type::Narrowed { narrowed_to, .. } => Some(*narrowed_to),
+		_ => None,
+	}
+}
+
+#[derive(Debug)]
+pub struct TemplatelLiteralExpansion {
+	pub parts: Vec<(String, TypeId)>,
+	pub rest: String,
+}
+
+impl TemplatelLiteralExpansion {
+	// pub const EMPTY: TemplatelLiteralExpansion =
+	// TemplatelLiteralExpansion { parts: Vec::new(), rest: String::default() };
+
+	/// TODO more, maybe involving types
+	#[must_use]
+	pub fn is_disjoint(&self, other: &Self) -> bool {
+		crate::utilities::notify!("{:?}", (self, other));
+		if let (Some((lhs, _)), Some((rhs, _))) = (self.parts.first(), other.parts.first()) {
+			let prefix_length = std::cmp::min(lhs.len(), rhs.len());
+			if lhs[..prefix_length] != rhs[..prefix_length] {
+				return true;
+			}
+		}
+
+		let postfix_len = std::cmp::min(self.rest.len(), other.rest.len());
+		self.rest[..postfix_len] != other.rest[..postfix_len]
+	}
+
+	#[must_use]
+	pub fn as_single_string(&self) -> Option<&str> {
+		if self.parts.is_empty() {
+			Some(&self.rest)
+		} else {
+			None
+		}
+	}
+
+	#[must_use]
+	pub fn is_empty(&self) -> bool {
+		self.parts.is_empty() && self.rest.is_empty()
+	}
+
+	#[must_use]
+	pub fn concatenate(self, mut other: Self) -> Self {
+		let mut parts: Vec<(String, TypeId)> = self.parts;
+		let rest = if other.parts.is_empty() {
+			format!("{}{}", self.rest, other.rest)
+		} else {
+			other.parts[0].0.insert_str(0, &self.rest);
+			parts.append(&mut other.parts);
+			other.rest
+		};
+		Self { parts, rest }
+	}
+
+	/// Forms a TL no matter what
+	#[must_use]
+	pub fn from_type(id: TypeId, types: &TypeStore) -> Self {
+		let ty = types.get_type_by_id(id);
+		if let Type::Constant(constant) = ty {
+			TemplatelLiteralExpansion {
+				parts: Default::default(),
+				rest: constant.as_js_string().into_owned(),
+			}
+		} else if let Type::Constructor(Constructor::BinaryOperator {
+			lhs,
+			operator: MathematicalOrBitwiseOperation::Add,
+			rhs,
+			result: TypeId::STRING_TYPE,
+		}) = ty
+		{
+			let lhs = Self::from_type(*lhs, types);
+			let rhs = Self::from_type(*rhs, types);
+			lhs.concatenate(rhs)
+		} else if let Some(base) = get_constraint_or_alias(id, types) {
+			Self::from_type(base, types)
+		} else {
+			TemplatelLiteralExpansion {
+				parts: vec![(Default::default(), id)],
+				rest: Default::default(),
+			}
+		}
+	}
+}
diff --git a/checker/src/types/intrinsics.rs b/checker/src/types/intrinsics.rs
index 17581d27..cde00b31 100644
--- a/checker/src/types/intrinsics.rs
+++ b/checker/src/types/intrinsics.rs
@@ -2,15 +2,18 @@ use source_map::SpanWithSource;
 
 use crate::{
 	types::{
-		generics::generic_type_arguments::GenericArguments, get_constraint,
-		PartiallyAppliedGenerics, TypeStore,
+		generics::generic_type_arguments::GenericArguments, get_constraint, helpers::into_cases,
+		Constant, Constructor, MathematicalOrBitwiseOperation, PartiallyAppliedGenerics, TypeStore,
 	},
 	TypeId,
 };
 
 use super::Type;
 
-pub use crate::utilities::float_range::FloatRange;
+pub use crate::utilities::{
+	float_range::{FloatRange, InclusiveExclusive},
+	modulo_class::ModuloClass,
+};
 
 /// These are special marker types (using [`Type::Alias`])
 ///
@@ -93,7 +96,7 @@ pub(crate) fn apply_string_intrinsic(on: TypeId, s: &str) -> String {
 }
 
 #[must_use]
-pub fn tsc_string_intrinsic(id: TypeId) -> bool {
+pub fn is_tsc_string_intrinsic(id: TypeId) -> bool {
 	matches!(
 		id,
 		TypeId::STRING_UPPERCASE
@@ -105,8 +108,8 @@ pub fn tsc_string_intrinsic(id: TypeId) -> bool {
 
 #[must_use]
 pub fn is_intrinsic(id: TypeId) -> bool {
-	tsc_string_intrinsic(id)
-		|| ezno_number_intrinsic(id)
+	is_tsc_string_intrinsic(id)
+		|| is_ezno_number_intrinsic(id)
 		|| matches!(
 			id,
 			TypeId::LITERAL_RESTRICTION
@@ -119,8 +122,8 @@ pub fn is_intrinsic(id: TypeId) -> bool {
 }
 
 #[must_use]
-pub fn ezno_number_intrinsic(id: TypeId) -> bool {
-	matches!(id, TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE | TypeId::MULTIPLE_OF)
+pub fn is_ezno_number_intrinsic(id: TypeId) -> bool {
+	matches!(id, TypeId::GREATER_THAN | TypeId::LESS_THAN | TypeId::MULTIPLE_OF)
 }
 
 #[must_use]
@@ -128,15 +131,17 @@ pub fn get_greater_than(on: TypeId, types: &TypeStore) -> Option<(bool, TypeId)>
 	let on = get_constraint(on, types).unwrap_or(on);
 	let ty = types.get_type_by_id(on);
 	if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
-		on: on @ (TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE),
+		on: TypeId::GREATER_THAN,
 		arguments,
 	}) = ty
 	{
-		let inclusive = *on == TypeId::INCLUSIVE_RANGE;
-		let floor = arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap();
-		Some((inclusive, floor))
+		let floor = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap();
+		Some((false, floor))
 	} else if let Type::And(lhs, rhs) = ty {
 		get_greater_than(*lhs, types).or_else(|| get_greater_than(*rhs, types))
+	} else if let Type::Or(lhs, rhs) = ty {
+		// TODO temp
+		get_greater_than(*lhs, types).or_else(|| get_greater_than(*rhs, types))
 	} else if let Type::Constant(crate::Constant::Number(..)) = ty {
 		Some((true, on))
 	} else {
@@ -149,17 +154,17 @@ pub fn get_less_than(on: TypeId, types: &TypeStore) -> Option<(bool, TypeId)> {
 	let on = get_constraint(on, types).unwrap_or(on);
 	let ty = types.get_type_by_id(on);
 	if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
-		on: on @ (TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE),
+		on: TypeId::LESS_THAN,
 		arguments,
 	}) = ty
 	{
-		let inclusive = *on == TypeId::INCLUSIVE_RANGE;
-		Some((
-			inclusive,
-			arguments.get_structure_restriction(TypeId::NUMBER_CEILING_GENERIC).unwrap(),
-		))
+		let floor = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap();
+		Some((false, floor))
 	} else if let Type::And(lhs, rhs) = ty {
 		get_less_than(*lhs, types).or_else(|| get_less_than(*rhs, types))
+	} else if let Type::Or(lhs, rhs) = ty {
+		// TODO temp
+		get_less_than(*lhs, types).or_else(|| get_less_than(*rhs, types))
 	} else if let Type::Constant(crate::Constant::Number(..)) = ty {
 		Some((true, on))
 	} else {
@@ -168,60 +173,50 @@ pub fn get_less_than(on: TypeId, types: &TypeStore) -> Option<(bool, TypeId)> {
 }
 
 #[must_use]
-pub fn get_range(on: TypeId, types: &TypeStore) -> Option<FloatRange> {
-	let on = get_constraint(on, types).unwrap_or(on);
-	let ty = types.get_type_by_id(on);
-	if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
-		on: on @ (TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE),
-		arguments,
-	}) = ty
-	{
-		let inclusive = *on == TypeId::INCLUSIVE_RANGE;
-		crate::utilities::notify!("{:?} {:?}", on, arguments);
-		let floor = arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap();
-		let ceiling = arguments.get_structure_restriction(TypeId::NUMBER_CEILING_GENERIC).unwrap();
-		if let (
-			Type::Constant(crate::Constant::Number(floor)),
-			Type::Constant(crate::Constant::Number(ceiling)),
-		) = (types.get_type_by_id(floor), types.get_type_by_id(ceiling))
-		{
-			let (floor, ceiling) = (*floor, *ceiling);
-			Some(if inclusive {
-				FloatRange::Inclusive { floor, ceiling }
-			} else {
-				FloatRange::Exclusive { floor, ceiling }
-			})
-		} else {
-			crate::utilities::notify!("Not bottom top number");
-			None
-		}
-	} else if let Type::Constant(crate::Constant::Number(number)) = ty {
-		Some(FloatRange::single(*number))
+pub fn range_to_type(range: FloatRange, types: &mut TypeStore) -> TypeId {
+	// TODO skip if infinite
+	let floor_ty = types.new_constant_type(Constant::Number(range.floor.1));
+	let ceiling_ty = types.new_constant_type(Constant::Number(range.ceiling.1));
+	let floor = range
+		.get_greater_than()
+		.map(|_number| new_intrinsic(&Intrinsic::GreaterThan, floor_ty, types));
+
+	let ceiling =
+		range.get_less_than().map(|_number| new_intrinsic(&Intrinsic::LessThan, ceiling_ty, types));
+
+	let mut ty = if let (Some(f), Some(c)) = (floor, ceiling) {
+		types.new_and_type(f, c)
 	} else {
-		None
+		floor.or(ceiling).unwrap()
+	};
+
+	if let InclusiveExclusive::Inclusive = range.floor.0 {
+		ty = types.new_or_type(ty, floor_ty);
 	}
+	if let InclusiveExclusive::Inclusive = range.ceiling.0 {
+		ty = types.new_or_type(ty, ceiling_ty);
+	}
+	ty
 }
 
-#[must_use]
-pub fn range_to_type(range: FloatRange, types: &mut TypeStore) -> TypeId {
-	use source_map::Nullable;
-
-	let on = if let FloatRange::Inclusive { .. } = range {
-		TypeId::INCLUSIVE_RANGE
+pub fn modulo_to_type(mod_class: ModuloClass, types: &mut TypeStore) -> TypeId {
+	// TODO skip if infinite
+	let modulo = types.new_constant_type(Constant::Number(mod_class.modulo));
+	let ty = new_intrinsic(&Intrinsic::MultipleOf, modulo, types);
+	if mod_class.offset == 0. {
+		ty
 	} else {
-		TypeId::EXCLUSIVE_RANGE
-	};
-	let (FloatRange::Inclusive { floor, ceiling } | FloatRange::Exclusive { floor, ceiling }) =
-		range;
-	let floor = types.new_constant_type(crate::Constant::Number(floor));
-	let ceiling = types.new_constant_type(crate::Constant::Number(ceiling));
-	let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([
-		(TypeId::NUMBER_FLOOR_GENERIC, (floor, SpanWithSource::NULL)),
-		(TypeId::NUMBER_CEILING_GENERIC, (ceiling, SpanWithSource::NULL)),
-	]));
-	types.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments }))
+		let offset = types.new_constant_type(Constant::Number(mod_class.offset));
+		types.register_type(Type::Constructor(Constructor::BinaryOperator {
+			lhs: ty,
+			operator: MathematicalOrBitwiseOperation::Add,
+			rhs: offset,
+			result: TypeId::NUMBER_TYPE,
+		}))
+	}
 }
 
+// TODO offsets as well
 #[must_use]
 pub fn get_multiple(on: TypeId, types: &TypeStore) -> Option<TypeId> {
 	let on = get_constraint(on, types).unwrap_or(on);
@@ -231,7 +226,7 @@ pub fn get_multiple(on: TypeId, types: &TypeStore) -> Option<TypeId> {
 		arguments,
 	}) = ty
 	{
-		arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC)
+		arguments.get_structure_restriction(TypeId::NUMBER_GENERIC)
 	} else if let Type::And(lhs, rhs) = ty {
 		get_multiple(*lhs, types).or_else(|| get_multiple(*rhs, types))
 	} else if let Type::Constant(crate::Constant::Number(..)) = ty {
@@ -255,7 +250,7 @@ pub fn is_not_not_a_number(on: TypeId, types: &TypeStore) -> bool {
 		} else if let Type::Or(lhs, rhs) = ty {
 			is_not_not_a_number(*lhs, types) && is_not_not_a_number(*rhs, types)
 		} else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
-			on: TypeId::MULTIPLE_OF | TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE,
+			on: TypeId::MULTIPLE_OF | TypeId::GREATER_THAN | TypeId::LESS_THAN,
 			arguments: _,
 		}) = ty
 		{
@@ -281,26 +276,26 @@ pub fn new_intrinsic(intrinsic: &Intrinsic, argument: TypeId, types: &mut TypeSt
 			Intrinsic::NoInfer => (TypeId::NO_INFER, TypeId::T_TYPE),
 			Intrinsic::Literal => (TypeId::LITERAL_RESTRICTION, TypeId::T_TYPE),
 			Intrinsic::LessThan => {
-				let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([
-					(TypeId::NUMBER_FLOOR_GENERIC, (TypeId::NEG_INFINITY, SpanWithSource::NULL)),
-					(TypeId::NUMBER_CEILING_GENERIC, (argument, SpanWithSource::NULL)),
-				]));
+				let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([(
+					TypeId::NUMBER_GENERIC,
+					(argument, SpanWithSource::NULL),
+				)]));
 
 				return types.register_type(Type::PartiallyAppliedGenerics(
-					PartiallyAppliedGenerics { on: TypeId::EXCLUSIVE_RANGE, arguments },
+					PartiallyAppliedGenerics { on: TypeId::LESS_THAN, arguments },
 				));
 			}
 			Intrinsic::GreaterThan => {
-				let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([
-					(TypeId::NUMBER_FLOOR_GENERIC, (argument, SpanWithSource::NULL)),
-					(TypeId::NUMBER_CEILING_GENERIC, (TypeId::INFINITY, SpanWithSource::NULL)),
-				]));
+				let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([(
+					TypeId::NUMBER_GENERIC,
+					(argument, SpanWithSource::NULL),
+				)]));
 
 				return types.register_type(Type::PartiallyAppliedGenerics(
-					PartiallyAppliedGenerics { on: TypeId::EXCLUSIVE_RANGE, arguments },
+					PartiallyAppliedGenerics { on: TypeId::GREATER_THAN, arguments },
 				));
 			}
-			Intrinsic::MultipleOf => (TypeId::MULTIPLE_OF, TypeId::NUMBER_FLOOR_GENERIC),
+			Intrinsic::MultipleOf => (TypeId::MULTIPLE_OF, TypeId::NUMBER_GENERIC),
 			Intrinsic::Exclusive => (TypeId::EXCLUSIVE_RESTRICTION, TypeId::T_TYPE),
 			Intrinsic::Not => {
 				// Double negation
@@ -316,6 +311,7 @@ pub fn new_intrinsic(intrinsic: &Intrinsic, argument: TypeId, types: &mut TypeSt
 			}
 			Intrinsic::CaseInsensitive => (TypeId::CASE_INSENSITIVE, TypeId::STRING_GENERIC),
 		};
+
 	let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([(
 		to_pair,
 		(argument, SpanWithSource::NULL),
@@ -323,3 +319,168 @@ pub fn new_intrinsic(intrinsic: &Intrinsic, argument: TypeId, types: &mut TypeSt
 
 	types.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments }))
 }
+
+#[must_use]
+pub fn get_range_and_mod_class(
+	ty: TypeId,
+	types: &TypeStore,
+) -> (Option<FloatRange>, Option<ModuloClass>) {
+	let cases = into_cases(ty, types);
+	let interesting = cases.iter().enumerate().find_map(|(idx, case)| {
+		case.0
+			.iter()
+			.copied()
+			.try_fold(Vec::new(), |mut acc, ty| {
+				if let Ok(item) = PureNumberIntrinsic::try_from_type(ty.0, types) {
+					acc.push(item);
+					Some(acc)
+				} else {
+					None
+				}
+			})
+			.map(|value| (idx, value))
+	});
+
+	if let Some((idx, interesting)) = interesting {
+		let mut range: Option<FloatRange> = None;
+		let mut modulo_class: Option<ModuloClass> = None;
+		for number_condition in interesting {
+			match number_condition {
+				PureNumberIntrinsic::GreaterThan(greater_than) => {
+					range = Some(match range {
+						None => FloatRange::new_greater_than(greater_than),
+						Some(mut range) => {
+							range.floor.1 = range.floor.1.max(greater_than);
+							range
+						}
+					});
+				}
+				PureNumberIntrinsic::LessThan(less_than) => {
+					range = Some(match range {
+						None => FloatRange::new_less_than(less_than),
+						Some(mut range) => {
+							range.ceiling.1 = range.ceiling.1.min(less_than);
+							range
+						}
+					});
+				}
+				PureNumberIntrinsic::Modulo { modulo, offset } => {
+					modulo_class = Some(if modulo_class.is_none() {
+						ModuloClass::new(modulo, offset)
+					} else {
+						crate::utilities::notify!("TODO intersection");
+						return (None, None);
+					});
+				}
+			}
+		}
+
+		for (other_idx, case) in cases.into_iter().enumerate() {
+			// Skip the interesting case
+			if idx == other_idx {
+				continue;
+			}
+			// Test whether all cases fit inside this new condition. Also modify ranges for inclusion if not
+			if let [value] = &case.0[..] {
+				if let Type::Constant(Constant::Number(num)) = types.get_type_by_id(value.0) {
+					let num = *num;
+					if let Some(ref modulo_class) = modulo_class {
+						if !modulo_class.contains(num) {
+							return (None, None);
+						}
+					}
+					if let Some(ref mut range) = range {
+						if range.floor.1 == num {
+							range.floor.0 = InclusiveExclusive::Inclusive;
+						} else if range.ceiling.1 == num {
+							range.ceiling.0 = InclusiveExclusive::Inclusive;
+						} else if !range.contains(num) {
+							crate::utilities::notify!(
+								"Here, number not contained in current range"
+							);
+							return (None, None);
+						}
+					}
+				}
+			} else {
+				return (None, None);
+			}
+		}
+		(range, modulo_class)
+	} else if let Type::Constant(Constant::Number(num)) = types.get_type_by_id(ty) {
+		(Some(FloatRange::new_single(*num)), None)
+	} else {
+		// crate::utilities::notify!("Not interesting or constant {:?}", ty);
+		(None, None)
+	}
+}
+
+type BetterF64 = ordered_float::NotNan<f64>;
+
+/// Unit. No combinations at this point
+#[derive(Debug)]
+pub enum PureNumberIntrinsic {
+	GreaterThan(BetterF64),
+	LessThan(BetterF64),
+	Modulo { modulo: BetterF64, offset: BetterF64 },
+}
+
+impl PureNumberIntrinsic {
+	pub fn try_from_type(ty: TypeId, types: &TypeStore) -> Result<PureNumberIntrinsic, ()> {
+		let ty = types.get_type_by_id(ty);
+		if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
+			on: on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN | TypeId::MULTIPLE_OF),
+			arguments,
+		}) = ty
+		{
+			let arg = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap();
+			if let Type::Constant(Constant::Number(number)) = types.get_type_by_id(arg) {
+				match *on {
+					TypeId::GREATER_THAN => Ok(PureNumberIntrinsic::GreaterThan(*number)),
+					TypeId::LESS_THAN => Ok(PureNumberIntrinsic::LessThan(*number)),
+					TypeId::MULTIPLE_OF => Ok(PureNumberIntrinsic::Modulo {
+						modulo: *number,
+						offset: 0f64.try_into().unwrap(),
+					}),
+					_ => todo!(),
+				}
+			} else {
+				Err(())
+			}
+		} else if let Type::Constructor(Constructor::BinaryOperator {
+			lhs,
+			operator: MathematicalOrBitwiseOperation::Add,
+			rhs,
+			..
+		}) = ty
+		{
+			let lhs_ty = types.get_type_by_id(super::get_constraint(*lhs, types).unwrap_or(*lhs));
+			crate::utilities::notify!("{:?}", lhs_ty);
+			let lhs_modulo = if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
+				on: TypeId::MULTIPLE_OF,
+				arguments,
+			}) = lhs_ty
+			{
+				let arg = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap();
+				if let Type::Constant(Constant::Number(number)) = types.get_type_by_id(arg) {
+					Some(number)
+				} else {
+					None
+				}
+			} else {
+				None
+			};
+
+			if let (Some(modulo), Type::Constant(Constant::Number(offset))) =
+				(lhs_modulo, types.get_type_by_id(*rhs))
+			{
+				Ok(PureNumberIntrinsic::Modulo { modulo: *modulo, offset: *offset })
+			} else {
+				Err(())
+			}
+		} else {
+			// crate::utilities::notify!("err here {:?}", ty);
+			Err(())
+		}
+	}
+}
diff --git a/checker/src/types/mod.rs b/checker/src/types/mod.rs
index a4bd6d90..81dcd86b 100644
--- a/checker/src/types/mod.rs
+++ b/checker/src/types/mod.rs
@@ -4,6 +4,7 @@ pub mod classes;
 pub mod disjoint;
 pub mod functions;
 pub mod generics;
+pub mod helpers;
 pub mod intrinsics;
 pub mod logical;
 pub mod printing;
@@ -33,13 +34,12 @@ pub use crate::features::objects::SpecialObject;
 use crate::{
 	context::InformationChain,
 	events::RootReference,
-	features::operations::{CanonicalEqualityAndInequality, MathematicalAndBitwise},
+	features::operations::{CanonicalEqualityAndInequality, MathematicalOrBitwiseOperation},
 	subtyping::SliceArguments,
 	Decidable, FunctionId,
 };
 
 use derive_debug_extras::DebugExtras;
-use derive_enum_from_into::EnumFrom;
 use source_map::SpanWithSource;
 
 pub type ExplicitTypeArgument = (TypeId, SpanWithSource);
@@ -143,32 +143,29 @@ impl TypeId {
 	pub const WRITABLE_KEY_ARGUMENT: Self = Self(43);
 
 	// Ezno intrinsics
+	pub const NUMBER_GENERIC: Self = Self(44);
+	pub const GREATER_THAN: Self = Self(45);
+	pub const LESS_THAN: Self = Self(46);
+	pub const MULTIPLE_OF: Self = Self(47);
+	pub const NOT_NOT_A_NUMBER: Self = Self(48);
+	pub const NUMBER_BUT_NOT_NOT_A_NUMBER: Self = Self(49);
 
-	/// Used in [`Self::INCLUSIVE_RANGE`], [`Self::EXCLUSIVE_RANGE`] and [`Self::MULTIPLE_OF`]
-	pub const NUMBER_FLOOR_GENERIC: Self = Self(44);
-	pub const NUMBER_CEILING_GENERIC: Self = Self(45);
-	pub const INCLUSIVE_RANGE: Self = Self(46);
-	pub const EXCLUSIVE_RANGE: Self = Self(47);
-	pub const MULTIPLE_OF: Self = Self(48);
-	pub const NOT_NOT_A_NUMBER: Self = Self(49);
-	pub const NUMBER_BUT_NOT_NOT_A_NUMBER: Self = Self(50);
-
-	pub const LITERAL_RESTRICTION: Self = Self(51);
-	pub const EXCLUSIVE_RESTRICTION: Self = Self(52);
-	pub const NOT_RESTRICTION: Self = Self(53);
+	pub const LITERAL_RESTRICTION: Self = Self(50);
+	pub const EXCLUSIVE_RESTRICTION: Self = Self(51);
+	pub const NOT_RESTRICTION: Self = Self(52);
 
 	/// This is needed for the TSC string intrinsics
-	pub const CASE_INSENSITIVE: Self = Self(54);
+	pub const CASE_INSENSITIVE: Self = Self(53);
 
 	/// WIP
-	pub const OPEN_BOOLEAN_TYPE: Self = Self(55);
-	pub const OPEN_NUMBER_TYPE: Self = Self(56);
+	pub const OPEN_BOOLEAN_TYPE: Self = Self(54);
+	pub const OPEN_NUMBER_TYPE: Self = Self(55);
 
 	/// For `+` operator
-	pub const STRING_OR_NUMBER: Self = Self(57);
+	pub const STRING_OR_NUMBER: Self = Self(56);
 
 	/// Above add one (because [`TypeId`] starts at zero). Used to assert that the above is all correct
-	pub(crate) const INTERNAL_TYPE_COUNT: usize = 58;
+	pub(crate) const INTERNAL_TYPE_COUNT: usize = 57;
 }
 
 #[derive(Debug, binary_serialize_derive::BinarySerializable)]
@@ -193,13 +190,11 @@ pub enum Type {
 	/// - Can be used for subtypes (aka N aliases number then more types on top)
 	/// - **Does not imply .prototype = **
 	AliasTo {
-		to: TypeId,
 		name: String,
 		parameters: Option<Vec<TypeId>>,
+		to: TypeId,
 	},
-	/// For number and other rooted types
-	///
-	/// Although they all alias Object
+	/// Properties are in environment (for declaration merging)
 	Interface {
 		name: String,
 		parameters: Option<Vec<TypeId>>,
@@ -334,9 +329,11 @@ impl Type {
 			// Fine
 			Type::Narrowed { .. } | Type::Constructor(_) | Type::RootPolyType(_) => true,
 			// TODO what about if left or right
-			Type::And(_, _) | Type::Or(_, _) => false,
+			// TODO should not be true here
+			Type::And(_, _) | Type::Or(_, _) => true,
 			// TODO what about if it aliases dependent?
-			Type::AliasTo { .. } => false,
+			// TODO should not be true here
+			Type::AliasTo { .. } => true,
 			Type::Interface { .. } | Type::Class { .. } => false,
 			Type::Constant(_)
 			| Type::SpecialObject(_)
@@ -374,7 +371,7 @@ impl Type {
 pub enum Constructor {
 	BinaryOperator {
 		lhs: TypeId,
-		operator: MathematicalAndBitwise,
+		operator: MathematicalOrBitwiseOperation,
 		rhs: TypeId,
 		/// for add + number intrinsics
 		result: TypeId,
@@ -420,18 +417,33 @@ pub enum Constructor {
 }
 
 impl Constructor {
-	fn get_base(&self) -> Option<TypeId> {
+	#[must_use]
+	pub fn get_constraint(&self) -> TypeId {
 		match self {
-			Constructor::ConditionalResult { result_union: result, .. }
-			| Constructor::Awaited { result, .. }
-			| Constructor::Property { result, .. }
-			| Constructor::Image { result, .. } => Some(*result),
-			Constructor::BinaryOperator { .. }
-			| Constructor::CanonicalRelationOperator { .. }
-			| Constructor::TypeExtends(_)
-			| Constructor::TypeOperator(_) => None,
-			// TODO or symbol
-			Constructor::KeyOf(_) => Some(TypeId::STRING_TYPE),
+			Constructor::BinaryOperator { result, .. }
+			| Constructor::Awaited { on: _, result }
+			| Constructor::Image { on: _, with: _, result } => *result,
+			Constructor::Property { on: _, under: _, result, mode: _ } => {
+				// crate::utilities::notify!("Here, result of a property get");
+				*result
+			}
+			Constructor::ConditionalResult { result_union, .. } => {
+				// TODO dynamic and open poly
+				*result_union
+			}
+			Constructor::TypeOperator(op) => match op {
+				// TODO union of names
+				TypeOperator::TypeOf(_) => TypeId::STRING_TYPE,
+				TypeOperator::IsPrototype { .. } | TypeOperator::HasProperty(..) => {
+					TypeId::BOOLEAN_TYPE
+				}
+			},
+			Constructor::CanonicalRelationOperator { .. } => TypeId::BOOLEAN_TYPE,
+			Constructor::TypeExtends(op) => {
+				let crate::types::TypeExtends { .. } = op;
+				TypeId::BOOLEAN_TYPE
+			}
+			Constructor::KeyOf(_) => TypeId::STRING_TYPE,
 		}
 	}
 }
@@ -605,7 +617,6 @@ pub fn is_type_truthy_falsy(id: TypeId, types: &TypeStore) -> Decidable<bool> {
 			| Type::Constructor(_)
 			| Type::Interface { .. }
 			| Type::PartiallyAppliedGenerics(..)
-			| Type::Narrowed { .. }
 			| Type::Class { .. } => {
 				// TODO some of these case are known
 				Decidable::Unknown(id)
@@ -617,6 +628,7 @@ pub fn is_type_truthy_falsy(id: TypeId, types: &TypeStore) -> Decidable<bool> {
 				// TODO strict casts
 				Decidable::Known(cast_as_boolean(cst, false).unwrap())
 			}
+			Type::Narrowed { narrowed_to, .. } => is_type_truthy_falsy(*narrowed_to, types),
 		}
 	}
 }
@@ -671,32 +683,7 @@ pub(crate) fn is_explicit_generic(on: TypeId, types: &TypeStore) -> bool {
 pub(crate) fn get_constraint(on: TypeId, types: &TypeStore) -> Option<TypeId> {
 	match types.get_type_by_id(on) {
 		Type::RootPolyType(nature) => Some(nature.get_constraint()),
-		Type::Constructor(constructor) => match constructor.clone() {
-			Constructor::BinaryOperator { result, .. }
-			| Constructor::Awaited { on: _, result }
-			| Constructor::Image { on: _, with: _, result } => Some(result),
-			Constructor::Property { on: _, under: _, result, mode: _ } => {
-				// crate::utilities::notify!("Here, result of a property get");
-				Some(result)
-			}
-			Constructor::ConditionalResult { result_union, .. } => {
-				// TODO dynamic and open poly
-				Some(result_union)
-			}
-			Constructor::TypeOperator(op) => match op {
-				// TODO union of names
-				TypeOperator::TypeOf(_) => Some(TypeId::STRING_TYPE),
-				TypeOperator::IsPrototype { .. } | TypeOperator::HasProperty(..) => {
-					Some(TypeId::BOOLEAN_TYPE)
-				}
-			},
-			Constructor::CanonicalRelationOperator { .. } => Some(TypeId::BOOLEAN_TYPE),
-			Constructor::TypeExtends(op) => {
-				let crate::types::TypeExtends { .. } = op;
-				Some(TypeId::BOOLEAN_TYPE)
-			}
-			Constructor::KeyOf(_) => Some(TypeId::STRING_TYPE),
-		},
+		Type::Constructor(constructor) => Some(constructor.get_constraint()),
 		Type::Narrowed { from: _, narrowed_to } => Some(*narrowed_to),
 		Type::Object(ObjectNature::RealDeal) => {
 			// crate::utilities::notify!("Might be missing some mutations that are possible here");
@@ -706,18 +693,6 @@ pub(crate) fn get_constraint(on: TypeId, types: &TypeStore) -> Option<TypeId> {
 	}
 }
 
-/// Returns the constraint or base of a constant for a type. Otherwise just return the type
-#[must_use]
-pub fn get_larger_type(on: TypeId, types: &TypeStore) -> TypeId {
-	if let Some(poly_base) = get_constraint(on, types) {
-		poly_base
-	} else if let Type::Constant(cst) = types.get_type_by_id(on) {
-		cst.get_backing_type()
-	} else {
-		on
-	}
-}
-
 /// TODO T on `Array`, U, V on `Map` etc. Works around not having `&mut TypeStore` and mutations in-between
 #[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)]
 pub enum LookUpGeneric {
@@ -791,317 +766,3 @@ impl TypeCombinable for TypeId {
 		TypeId::UNDEFINED_TYPE
 	}
 }
-
-/// Used for **both** inference and narrowing
-pub enum Confirmation {
-	HasProperty { on: (), property: () },
-	IsType { on: (), ty: () },
-}
-
-pub(crate) fn get_structure_arguments_based_on_object_constraint<'a, C: InformationChain>(
-	object: TypeId,
-	info_chain: &C,
-	types: &'a TypeStore,
-) -> Option<&'a GenericArguments> {
-	if let Some(object_constraint) =
-		info_chain.get_chain_of_info().find_map(|c| c.object_constraints.get(&object).copied())
-	{
-		let ty = types.get_type_by_id(object_constraint);
-		if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { arguments, .. }) = ty {
-			Some(arguments)
-		} else {
-			crate::utilities::notify!("Generics might be missed here {:?}", ty);
-			None
-		}
-	} else {
-		None
-	}
-}
-
-pub(crate) fn tuple_like(id: TypeId, types: &TypeStore, environment: &crate::Environment) -> bool {
-	// TODO should be `ObjectNature::AnonymousObjectType` or something else
-	let ty = types.get_type_by_id(id);
-	if let Type::Object(ObjectNature::RealDeal) = ty {
-		environment
-			.get_chain_of_info()
-			.any(|info| info.prototypes.get(&id).is_some_and(|p| *p == TypeId::ARRAY_TYPE))
-	} else if let Type::AliasTo { to, .. } = ty {
-		tuple_like(*to, types, environment)
-	} else {
-		false
-	}
-}
-
-pub(crate) fn _unfold_tuple(_ty: TypeId) -> TypeId {
-	// return Type::PropertyOf()
-	todo!()
-}
-
-pub(crate) fn _assign_to_tuple(_ty: TypeId) -> TypeId {
-	todo!()
-	// if let PropertyKey::Type(slice) =
-}
-
-fn get_array_length(
-	ctx: &impl InformationChain,
-	on: TypeId,
-	types: &TypeStore,
-) -> Result<ordered_float::NotNan<f64>, Option<TypeId>> {
-	let length_property = properties::PropertyKey::String(std::borrow::Cow::Borrowed("length"));
-	let id = get_simple_value(ctx, on, &length_property, types).ok_or(None)?;
-	if let Type::Constant(Constant::Number(n)) = types.get_type_by_id(id) {
-		Ok(*n)
-	} else {
-		Err(Some(id))
-	}
-}
-
-/// TODO name?
-#[derive(Clone, Copy, Debug)]
-pub enum ArrayItem {
-	Member(TypeId),
-	Optional(TypeId),
-	Wildcard(TypeId),
-}
-
-/// WIP
-pub(crate) fn as_slice(
-	ty: TypeId,
-	types: &TypeStore,
-	environment: &crate::Environment,
-) -> Result<Vec<ArrayItem>, ()> {
-	if tuple_like(ty, types, environment) {
-		let ty = if let Type::AliasTo { to, .. } = types.get_type_by_id(ty) { *to } else { ty };
-		let properties =
-			environment.get_chain_of_info().find_map(|info| info.current_properties.get(&ty));
-		if let Some(properties) = properties {
-			Ok(properties
-				.iter()
-				.filter_map(|(_, key, value)| {
-					let not_length_value = !key.is_equal_to("length");
-					not_length_value.then(|| {
-						crate::utilities::notify!("key (should be incremental) {:?}", key);
-						if key.as_number(types).is_some() {
-							if let crate::PropertyValue::ConditionallyExists { .. } = value {
-								ArrayItem::Optional(value.as_get_type(types))
-							} else {
-								ArrayItem::Member(value.as_get_type(types))
-							}
-						} else {
-							ArrayItem::Wildcard(value.as_get_type(types))
-						}
-					})
-				})
-				.collect())
-		} else {
-			crate::utilities::notify!("BAD");
-			Err(())
-		}
-	} else {
-		Err(())
-	}
-}
-
-/// WIP for counting slice indexes
-#[derive(EnumFrom, Clone, Copy, Debug)]
-pub enum Counter {
-	On(usize),
-	AddTo(TypeId),
-}
-
-impl Counter {
-	/// TODO &mut or Self -> Self?
-	pub fn increment(&mut self, types: &mut TypeStore) {
-		match self {
-			Counter::On(value) => {
-				*value += 1;
-			}
-			Counter::AddTo(value) => {
-				*value = types.register_type(Type::Constructor(Constructor::BinaryOperator {
-					lhs: *value,
-					operator: MathematicalAndBitwise::Add,
-					rhs: TypeId::ONE,
-					// TODO could be greater than
-					result: TypeId::NUMBER_TYPE,
-				}));
-			}
-		}
-	}
-
-	pub fn add_type(&mut self, ty: TypeId, types: &mut TypeStore) {
-		let current = self.into_type(types);
-		let new = types.register_type(Type::Constructor(Constructor::BinaryOperator {
-			lhs: ty,
-			operator: MathematicalAndBitwise::Add,
-			rhs: current,
-			// TODO could be greater than
-			result: TypeId::NUMBER_TYPE,
-		}));
-		*self = Counter::AddTo(new);
-	}
-
-	pub(crate) fn into_property_key(self) -> properties::PropertyKey<'static> {
-		match self {
-			Counter::On(value) => properties::PropertyKey::from_usize(value),
-			Counter::AddTo(ty) => properties::PropertyKey::Type(ty),
-		}
-	}
-
-	pub(crate) fn into_type(self, types: &mut TypeStore) -> TypeId {
-		match self {
-			Counter::On(value) => {
-				types.new_constant_type(Constant::Number((value as f64).try_into().unwrap()))
-			}
-			Counter::AddTo(ty) => ty,
-		}
-	}
-}
-
-pub(crate) mod helpers {
-	use super::{
-		get_constraint, subtyping, Constructor, GenericArguments, GenericChain, InformationChain,
-		PartiallyAppliedGenerics, PolyNature, Type, TypeId, TypeStore,
-	};
-
-	/// To fill in for TSC behavior for mapped types
-	#[must_use]
-	pub fn references_key_of(id: TypeId, types: &TypeStore) -> bool {
-		match types.get_type_by_id(id) {
-			Type::AliasTo { to, .. } => references_key_of(*to, types),
-			Type::Or(lhs, rhs) | Type::And(lhs, rhs) => {
-				references_key_of(*lhs, types) || references_key_of(*rhs, types)
-			}
-			Type::RootPolyType(c) => references_key_of(c.get_constraint(), types),
-			Type::Constructor(c) => {
-				if let Constructor::KeyOf(..) = c {
-					true
-				} else if let Constructor::BinaryOperator { lhs, rhs, operator: _, result: _ } = c {
-					references_key_of(*lhs, types) || references_key_of(*rhs, types)
-				} else {
-					crate::utilities::notify!("TODO might have missed keyof {:?}", c);
-					false
-				}
-			}
-			Type::PartiallyAppliedGenerics(a) => {
-				if let GenericArguments::ExplicitRestrictions(ref e) = a.arguments {
-					e.0.iter().any(|(_, (lhs, _))| references_key_of(*lhs, types))
-				} else {
-					false
-				}
-			}
-			Type::Interface { .. }
-			| Type::Class { .. }
-			| Type::Constant(_)
-			| Type::Narrowed { .. }
-			| Type::FunctionReference(_)
-			| Type::Object(_)
-			| Type::SpecialObject(_) => false,
-		}
-	}
-
-	#[allow(clippy::match_like_matches_macro)]
-	#[must_use]
-	pub fn _type_is_error(ty: TypeId, types: &TypeStore) -> bool {
-		if ty == TypeId::UNIMPLEMENTED_ERROR_TYPE {
-			true
-		} else if let Type::RootPolyType(PolyNature::Error(_)) = types.get_type_by_id(ty) {
-			true
-		} else {
-			false
-		}
-	}
-
-	/// TODO want to skip mapped generics because that would break subtyping
-	#[must_use]
-	pub fn get_conditional(ty: TypeId, types: &TypeStore) -> Option<(TypeId, TypeId, TypeId)> {
-		match types.get_type_by_id(ty) {
-			Type::Constructor(crate::types::Constructor::ConditionalResult {
-				condition,
-				truthy_result,
-				otherwise_result,
-				result_union: _,
-			}) => Some((*condition, *truthy_result, *otherwise_result)),
-			Type::Or(left, right) => Some((TypeId::OPEN_BOOLEAN_TYPE, *left, *right)),
-			// For reasons !
-			Type::RootPolyType(PolyNature::MappedGeneric { .. }) => None,
-			_ => {
-				if let Some(constraint) = get_constraint(ty, types) {
-					get_conditional(constraint, types)
-				} else {
-					None
-				}
-			}
-		}
-	}
-
-	/// TODO wip
-	#[must_use]
-	pub fn is_pseudo_continous((ty, generics): (TypeId, GenericChain), types: &TypeStore) -> bool {
-		if let TypeId::NUMBER_TYPE | TypeId::STRING_TYPE = ty {
-			true
-		} else if let Some(arg) = generics.as_ref().and_then(|args| args.get_single_argument(ty)) {
-			is_pseudo_continous((arg, generics), types)
-		} else {
-			let ty = types.get_type_by_id(ty);
-			if let Type::Or(left, right) = ty {
-				is_pseudo_continous((*left, generics), types)
-					|| is_pseudo_continous((*right, generics), types)
-			} else if let Type::And(left, right) = ty {
-				is_pseudo_continous((*left, generics), types)
-					&& is_pseudo_continous((*right, generics), types)
-			} else if let Type::RootPolyType(PolyNature::MappedGeneric { extends, .. }) = ty {
-				is_pseudo_continous((*extends, generics), types)
-			} else {
-				false
-			}
-		}
-	}
-
-	#[must_use]
-	pub fn is_inferrable_type(ty: TypeId) -> bool {
-		matches!(ty, TypeId::ANY_TO_INFER_TYPE | TypeId::OBJECT_TYPE)
-	}
-
-	/// For quick checking
-	#[must_use]
-	pub fn simple_subtype(
-		expr_ty: TypeId,
-		to_satisfy: TypeId,
-		information: &impl InformationChain,
-		types: &TypeStore,
-	) -> bool {
-		let mut state = subtyping::State {
-			already_checked: Default::default(),
-			mode: Default::default(),
-			contributions: Default::default(),
-			others: subtyping::SubTypingOptions { allow_errors: true },
-			object_constraints: None,
-		};
-
-		subtyping::type_is_subtype(to_satisfy, expr_ty, &mut state, information, types).is_subtype()
-	}
-
-	// unfolds narrowing
-	pub fn get_origin(ty: TypeId, types: &TypeStore) -> TypeId {
-		if let Type::Narrowed { from, .. } = types.get_type_by_id(ty) {
-			// Hopefully don't have a nested from
-			*from
-		} else {
-			ty
-		}
-	}
-
-	/// Temp fix for equality of narrowing stuff
-	pub fn is_not_of_constant(ty: TypeId, types: &TypeStore) -> bool {
-		if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
-			on: TypeId::NOT_RESTRICTION,
-			arguments,
-		}) = types.get_type_by_id(ty)
-		{
-			let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap();
-			types.get_type_by_id(inner).is_constant()
-		} else {
-			false
-		}
-	}
-}
diff --git a/checker/src/types/printing.rs b/checker/src/types/printing.rs
index 63cf693a..81a2f1ca 100644
--- a/checker/src/types/printing.rs
+++ b/checker/src/types/printing.rs
@@ -9,7 +9,8 @@ use crate::{
 		calling::ThisValue,
 		functions::{FunctionBehavior, FunctionEffect},
 		generics::generic_type_arguments::GenericArguments,
-		get_array_length, get_constraint, get_simple_value,
+		get_constraint, get_simple_property_value,
+		helpers::get_array_length,
 		properties::{get_properties_on_single_type, AccessMode, PropertyKey, Publicity},
 		Constructor, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, TypeExtends,
 	},
@@ -57,24 +58,6 @@ pub fn print_type_with_type_arguments(
 	buf
 }
 
-pub fn print_inner_template_literal_type_into_buf<C: InformationChain>(
-	ty: TypeId,
-	buf: &mut String,
-	cycles: &mut HashSet<TypeId>,
-	args: GenericChain,
-	types: &TypeStore,
-	info: &C,
-	debug: bool,
-) {
-	if let Type::Constant(cst) = types.get_type_by_id(ty) {
-		buf.push_str(&cst.as_js_string());
-	} else {
-		buf.push_str("${");
-		print_type_into_buf(ty, buf, cycles, args, types, info, debug);
-		buf.push('}');
-	}
-}
-
 /// Recursion safe + reuses buffer
 pub fn print_type_into_buf<C: InformationChain>(
 	ty: TypeId,
@@ -96,27 +79,35 @@ pub fn print_type_into_buf<C: InformationChain>(
 	let r#type = types.get_type_by_id(ty);
 	match r#type {
 		Type::And(a, b) => {
+			// let value = crate::types::intrinsics::get_range_and_mod_class(ty, types);
+			// if value.0.is_some() || value.1.is_some() {
+			// 	crate::utilities::notify!("{:?}", value);
+			// }
+
 			print_type_into_buf(*a, buf, cycles, args, types, info, debug);
 			buf.push_str(" & ");
 			print_type_into_buf(*b, buf, cycles, args, types, info, debug);
 		}
 		Type::Or(a, b) => {
+			// let value = crate::types::intrinsics::get_range_and_mod_class(ty, types);
+			// if value.0.is_some() || value.1.is_some() {
+			// 	crate::utilities::notify!("{:?}", value);
+			// }
+
 			print_type_into_buf(*a, buf, cycles, args, types, info, debug);
 			buf.push_str(" | ");
 			print_type_into_buf(*b, buf, cycles, args, types, info, debug);
 		}
 		Type::Narrowed { narrowed_to, from } => {
 			if debug {
-				buf.push_str("(narrowed from ");
-				print_type_into_buf(*from, buf, cycles, args, types, info, debug);
-				buf.push_str(") ");
+				write!(buf, "(narrowed from {from:?}) ").unwrap();
 			}
 			print_type_into_buf(*narrowed_to, buf, cycles, args, types, info, debug);
 		}
 		Type::RootPolyType(nature) => match nature {
 			PolyNature::MappedGeneric { name, extends } => {
 				if debug {
-					write!(buf, "[mg {} {}] ", ty.0, name).unwrap();
+					write!(buf, "(mg {} {}) ", ty.0, name).unwrap();
 				}
 				crate::utilities::notify!("args={:?}", args);
 				if let Some(crate::types::CovariantContribution::String(property)) =
@@ -131,13 +122,13 @@ pub fn print_type_into_buf<C: InformationChain>(
 			| PolyNature::StructureGeneric { name, extends: _ } => {
 				if debug {
 					if let PolyNature::FunctionGeneric { .. } = nature {
-						write!(buf, "[fg {} {}] ", ty.0, name).unwrap();
+						write!(buf, "(fg {} {}) ", ty.0, name).unwrap();
 					}
 				}
 				if let Some(arg) = args.and_then(|args| args.get_argument_covariant(ty)) {
 					use crate::types::CovariantContribution;
 					if debug {
-						buf.push_str(" (specialised with) ");
+						buf.push_str("(specialised with) ");
 					}
 					match arg {
 						CovariantContribution::TypeId(id) => {
@@ -159,7 +150,7 @@ pub fn print_type_into_buf<C: InformationChain>(
 						if let PolyNature::FunctionGeneric { extends, .. } = nature {
 							print_type_into_buf(*extends, buf, cycles, args, types, info, debug);
 						} else {
-							write!(buf, "[sg {}] ", ty.0).unwrap();
+							write!(buf, "(sg {})", ty.0).unwrap();
 						}
 					}
 					buf.push_str(name);
@@ -167,7 +158,7 @@ pub fn print_type_into_buf<C: InformationChain>(
 			}
 			PolyNature::InferGeneric { name, extends } => {
 				if debug {
-					write!(buf, "[IG {}] @ ", ty.0).unwrap();
+					write!(buf, "(IG {}) @ ", ty.0).unwrap();
 				}
 				buf.push_str("infer ");
 				buf.push_str(name);
@@ -179,19 +170,19 @@ pub fn print_type_into_buf<C: InformationChain>(
 			PolyNature::FreeVariable { based_on: to, .. } => {
 				if debug {
 					// FV = free variable
-					write!(buf, "[FV {}] @ ", ty.0).unwrap();
+					write!(buf, "(FV {}) @ ", ty.0).unwrap();
 				}
 				print_type_into_buf(*to, buf, cycles, args, types, info, debug);
 			}
 			PolyNature::Parameter { fixed_to: to } => {
 				if debug {
-					write!(buf, "[param {}] @ ", ty.0).unwrap();
+					write!(buf, "(param {}) @ ", ty.0).unwrap();
 				}
 				print_type_into_buf(*to, buf, cycles, args, types, info, debug);
 			}
 			PolyNature::Open(to) => {
 				if debug {
-					write!(buf, "[open {}] ", ty.0).unwrap();
+					write!(buf, "(open {}) ", ty.0).unwrap();
 				}
 				print_type_into_buf(*to, buf, cycles, args, types, info, debug);
 			}
@@ -212,31 +203,30 @@ pub fn print_type_into_buf<C: InformationChain>(
 			}
 			PolyNature::CatchVariable(constraint) => {
 				if debug {
-					write!(buf, "[catch variable {ty:?}] ").unwrap();
+					write!(buf, "(CV {ty:?}) ").unwrap();
 				}
 				print_type_into_buf(*constraint, buf, cycles, args, types, info, debug);
 			}
 		},
 		Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments }) => {
-			// TypeId::INCLUSIVE_RANGE |
-			if let TypeId::EXCLUSIVE_RANGE = *on {
-				// let inclusive = *on == TypeId::INCLUSIVE_RANGE;
-				let floor =
-					arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap();
-				let ceiling =
-					arguments.get_structure_restriction(TypeId::NUMBER_CEILING_GENERIC).unwrap();
-				if let TypeId::NEG_INFINITY = floor {
-					buf.push_str("LessThan<");
-					print_type_into_buf(ceiling, buf, cycles, args, types, info, debug);
-					buf.push('>');
-					return;
-				} else if let TypeId::INFINITY = ceiling {
-					buf.push_str("GreaterThan<");
-					print_type_into_buf(floor, buf, cycles, args, types, info, debug);
-					buf.push('>');
-					return;
-				}
-			}
+			// if let TypeId::Great = *on {
+			// 	// let inclusive = *on == TypeId::INCLUSIVE_RANGE;
+			// 	let floor =
+			// 		arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap();
+			// 	let ceiling =
+			// 		arguments.get_structure_restriction(TypeId::NUMBER_CEILING_GENERIC).unwrap();
+			// 	if let TypeId::NEG_INFINITY = floor {
+			// 		buf.push_str("LessThan<");
+			// 		print_type_into_buf(ceiling, buf, cycles, args, types, info, debug);
+			// 		buf.push('>');
+			// 		return;
+			// 	} else if let TypeId::INFINITY = ceiling {
+			// 		buf.push_str("GreaterThan<");
+			// 		print_type_into_buf(floor, buf, cycles, args, types, info, debug);
+			// 		buf.push('>');
+			// 		return;
+			// 	}
+			// }
 
 			if debug {
 				write!(buf, "SG({:?})(", ty.0).unwrap();
@@ -428,6 +418,18 @@ pub fn print_type_into_buf<C: InformationChain>(
 					print_type_into_buf(*result, buf, cycles, args, types, info, debug);
 				}
 			}
+			Constructor::BinaryOperator {
+				lhs: _,
+				operator: crate::features::operations::MathematicalOrBitwiseOperation::Modulo,
+				rhs,
+				result: _,
+			} if matches!(types.get_type_by_id(*rhs), Type::Constant(_)) => {
+				if let Type::Constant(crate::Constant::Number(num)) = types.get_type_by_id(*rhs) {
+					write!(buf, "ExclusiveRange<-{num}, {num}>").unwrap();
+				} else {
+					unreachable!()
+				}
+			}
 			constructor if debug => match constructor {
 				Constructor::BinaryOperator { lhs, operator, rhs, result: _ } => {
 					print_type_into_buf(*lhs, buf, cycles, args, types, info, debug);
@@ -456,7 +458,7 @@ pub fn print_type_into_buf<C: InformationChain>(
 				}
 				Constructor::Image { on: _, with: _, result } => {
 					// TODO arguments
-					write!(buf, "[func result {}] (*args*)", ty.0).unwrap();
+					write!(buf, "(func result {}) (*args*)", ty.0).unwrap();
 					buf.push_str(" -> ");
 					print_type_into_buf(*result, buf, cycles, args, types, info, debug);
 				}
@@ -488,23 +490,26 @@ pub fn print_type_into_buf<C: InformationChain>(
 				}
 			},
 			constructor => {
-				if let Constructor::BinaryOperator { result: result_ty, lhs, rhs, .. } = constructor
+				if let Constructor::BinaryOperator { result: TypeId::STRING_TYPE, .. } = constructor
 				{
-					if *result_ty != TypeId::NUMBER_TYPE
-						&& !matches!(
-							types.get_type_by_id(*result_ty),
-							Type::PartiallyAppliedGenerics(_) | Type::RootPolyType(_)
-						) {
+					let slice =
+						crate::types::helpers::TemplatelLiteralExpansion::from_type(ty, types);
+					if let Some(single) = slice.as_single_string() {
+						buf.push('"');
+						buf.push_str(single);
+						buf.push('"');
+					} else {
 						buf.push('`');
-						print_inner_template_literal_type_into_buf(
-							*lhs, buf, cycles, args, types, info, debug,
-						);
-						print_inner_template_literal_type_into_buf(
-							*rhs, buf, cycles, args, types, info, debug,
-						);
+						for (s, ty) in &slice.parts {
+							buf.push_str(s);
+							buf.push_str("${");
+							print_type_into_buf(*ty, buf, cycles, args, types, info, debug);
+							buf.push('}');
+						}
+						buf.push_str(&slice.rest);
 						buf.push('`');
-						return;
 					}
+					return;
 				}
 				let base = get_constraint(ty, types).unwrap();
 				print_type_into_buf(base, buf, cycles, args, types, info, debug);
@@ -514,12 +519,12 @@ pub fn print_type_into_buf<C: InformationChain>(
 		| Type::Interface { name, parameters: _, .. }
 		| Type::AliasTo { to: _, name, parameters: _ }) => {
 			if debug {
-				write!(buf, "{name}#{}", ty.0).unwrap();
+				write!(buf, "{name}#{} ", ty.0).unwrap();
 				if let Type::AliasTo { to, .. } = t {
-					buf.push_str(" = ");
+					buf.push_str("= ");
 					print_type_into_buf(*to, buf, cycles, args, types, info, debug);
 				} else if let Type::Class { .. } = t {
-					buf.push_str(" (class)");
+					buf.push_str("(class)");
 				}
 			} else {
 				buf.push_str(name);
@@ -567,7 +572,7 @@ pub fn print_type_into_buf<C: InformationChain>(
 
 			if debug {
 				let kind = if matches!(r#type, Type::FunctionReference(_)) { "ref" } else { "" };
-				write!(buf, "[func{kind} #{}, kind {:?}, effect ", ty.0, func.behavior).unwrap();
+				write!(buf, "(func{kind} #{}, kind {:?}, effect ", ty.0, func.behavior).unwrap();
 				if let FunctionEffect::SideEffects {
 					events: _,
 					free_variables,
@@ -585,7 +590,7 @@ pub fn print_type_into_buf<C: InformationChain>(
 					buf.push_str(", this ");
 					print_type_into_buf(*p, buf, cycles, args, types, info, debug);
 				}
-				buf.push_str("] = ");
+				buf.push_str(") = ");
 			}
 			if let Some(ref parameters) = func.type_parameters {
 				buf.push('<');
@@ -639,9 +644,9 @@ pub fn print_type_into_buf<C: InformationChain>(
 		Type::Object(kind) => {
 			if debug {
 				if let ObjectNature::RealDeal = kind {
-					write!(buf, "[obj {}] ", ty.0).unwrap();
+					write!(buf, "(obj {}) ", ty.0).unwrap();
 				} else {
-					write!(buf, "[aol {}] ", ty.0).unwrap();
+					write!(buf, "(anom {}) ", ty.0).unwrap();
 				}
 			}
 			let prototype =
@@ -655,8 +660,12 @@ pub fn print_type_into_buf<C: InformationChain>(
 							if i != 0 {
 								buf.push_str(", ");
 							}
-							let value =
-								get_simple_value(info, ty, &PropertyKey::from_usize(i), types);
+							let value = get_simple_property_value(
+								info,
+								ty,
+								&PropertyKey::from_usize(i),
+								types,
+							);
 
 							if let Some(value) = value {
 								print_type_into_buf(value, buf, cycles, args, types, info, debug);
diff --git a/checker/src/types/properties/access.rs b/checker/src/types/properties/access.rs
index 84d52df1..6b17a5c7 100644
--- a/checker/src/types/properties/access.rs
+++ b/checker/src/types/properties/access.rs
@@ -10,7 +10,7 @@ use crate::{
 			contributions::CovariantContribution, generic_type_arguments::GenericArguments,
 		},
 		get_constraint,
-		helpers::{get_conditional, is_inferrable_type, is_pseudo_continous},
+		helpers::{get_type_as_conditional, is_inferrable_type, is_pseudo_continous},
 		logical::{
 			BasedOnKey, Invalid, Logical, LogicalOrValid, NeedsCalculation, PossibleLogical,
 			PropertyOn,
@@ -336,7 +336,8 @@ pub(crate) fn get_property_unbound(
 			Type::Constructor(crate::types::Constructor::ConditionalResult { .. })
 			| Type::Or(..) => {
 				let (condition, truthy_result, otherwise_result) =
-					get_conditional(on, types).expect("case above != get_conditional");
+					get_type_as_conditional(on, types)
+						.expect("case above != get_type_as_conditional");
 
 				if require_both_logical {
 					let left = get_property_on_type_unbound(
@@ -372,14 +373,16 @@ pub(crate) fn get_property_unbound(
 						types,
 					);
 					if left.is_err() && right.is_err() {
+						crate::utilities::notify!(
+							"One side invalid {:?}",
+							(left.is_err(), right.is_err())
+						);
 						Err(Invalid(on))
 					} else {
-						let left = left.unwrap_or(LogicalOrValid::Logical(Logical::Pure(
-							PropertyValue::Deleted,
-						)));
-						let right = right.unwrap_or(LogicalOrValid::Logical(Logical::Pure(
-							PropertyValue::Deleted,
-						)));
+						const DELETED_PROPERTY: LogicalOrValid<PropertyValue> =
+							LogicalOrValid::Logical(Logical::Pure(PropertyValue::Deleted));
+						let left = left.unwrap_or(DELETED_PROPERTY);
+						let right = right.unwrap_or(DELETED_PROPERTY);
 						Ok(Logical::Or { condition, left: Box::new(left), right: Box::new(right) }
 							.into())
 					}
@@ -421,7 +424,7 @@ pub(crate) fn get_property_unbound(
 			}
 			Type::Object(ObjectNature::RealDeal) => {
 				let object_constraint_structure_generics =
-					crate::types::get_structure_arguments_based_on_object_constraint(
+					crate::types::helpers::get_structure_arguments_based_on_object_constraint(
 						on, info_chain, types,
 					);
 
@@ -430,17 +433,6 @@ pub(crate) fn get_property_unbound(
 					.find_map(|facts| facts.prototypes.get(&on))
 					.copied();
 
-				let generics = if let Some(generics) = object_constraint_structure_generics {
-					// TODO clone
-					Some(generics.clone())
-				} else if prototype
-					.is_some_and(|prototype| types.lookup_generic_map.contains_key(&prototype))
-				{
-					Some(GenericArguments::LookUp { on })
-				} else {
-					None
-				};
-
 				let on_self = resolver(
 					(on, on_type_arguments),
 					(publicity, under, under_type_arguments),
@@ -448,31 +440,39 @@ pub(crate) fn get_property_unbound(
 					types,
 				);
 
-				let result = if let (Some(prototype), None) = (prototype, &on_self) {
-					resolver(
+				if let Some(property) = on_self {
+					let generics = if let Some(generics) = object_constraint_structure_generics {
+						// TODO clone
+						Some(generics.clone())
+					} else if prototype
+						.is_some_and(|prototype| types.lookup_generic_map.contains_key(&prototype))
+					{
+						Some(GenericArguments::LookUp { on })
+					} else {
+						None
+					};
+
+					let property = wrap(property);
+					let property = if let Some(ref generics) = generics {
+						// TODO clone
+						Logical::Implies { on: Box::new(property), antecedent: generics.clone() }
+					} else {
+						property
+					};
+					Ok(LogicalOrValid::Logical(property))
+				} else if let Some(prototype) = prototype {
+					crate::utilities::notify!("{:?}", types.get_type_by_id(prototype));
+
+					get_property_on_type_unbound(
 						(prototype, on_type_arguments),
 						(publicity, under, under_type_arguments),
+						require_both_logical,
 						info_chain,
 						types,
 					)
 				} else {
-					on_self
-				};
-
-				// crate::utilities::notify!("result={:?}", result);
-
-				result
-					.map(wrap)
-					.map(|result| {
-						if let Some(ref generics) = generics {
-							// TODO clone
-							Logical::Implies { on: Box::new(result), antecedent: generics.clone() }
-						} else {
-							result
-						}
-					})
-					.map(LogicalOrValid::Logical)
-					.ok_or(Invalid(on))
+					Err(Invalid(on))
+				}
 			}
 			Type::Interface { extends, .. } => resolver(
 				(on, on_type_arguments),
@@ -543,14 +543,12 @@ pub(crate) fn get_property_unbound(
 				}
 			}
 			Type::Constant(Constant::String(s)) if under.is_equal_to("length") => {
-				// TODO temp TypeId::NUMBER_FLOOR_GENERIC for slice member
+				// TODO temp TypeId::NUMBER_GENERIC for slice member
 				let count = s.chars().count();
 				Ok(Logical::BasedOnKey(BasedOnKey::Left {
-					value: Box::new(Logical::Pure(PropertyValue::Value(
-						TypeId::NUMBER_FLOOR_GENERIC,
-					))),
+					value: Box::new(Logical::Pure(PropertyValue::Value(TypeId::NUMBER_GENERIC))),
 					key_arguments: crate::Map::from_iter([(
-						TypeId::NUMBER_FLOOR_GENERIC,
+						TypeId::NUMBER_GENERIC,
 						(CovariantContribution::Number(count as f64), 0),
 					)]),
 				})
@@ -604,7 +602,9 @@ pub(crate) fn get_property_unbound(
 		// if *key == TypeId::ERROR_TYPE {
 		// 	return Err(MissingOrToCalculate::Error);
 		// } else
-		if let Some((condition, truthy_result, otherwise_result)) = get_conditional(key, types) {
+		if let Some((condition, truthy_result, otherwise_result)) =
+			get_type_as_conditional(key, types)
+		{
 			let left = get_property_unbound(
 				(on, on_type_arguments),
 				(publicity, &PropertyKey::Type(truthy_result), under_type_arguments),
@@ -736,9 +736,9 @@ pub(crate) fn get_property<B: CallCheckingBehavior>(
 		types,
 	);
 
-	{
-		crate::utilities::notify!("Access {:?} result {:?}", under, result);
-	}
+	// {
+	// 	crate::utilities::notify!("Access {:?} result {:?}", under, result);
+	// }
 
 	match result {
 		Ok(LogicalOrValid::Logical(logical)) => {
@@ -1096,7 +1096,7 @@ fn resolve_property_on_logical<B: CallCheckingBehavior>(
 				)
 			}
 			BasedOnKey::Right(property_on) => {
-				crate::utilities::notify!("{:?}", generics);
+				crate::utilities::notify!("generics are {:?}", generics);
 				let result = property_on.get_on(generics, environment, types)?;
 
 				// {
diff --git a/checker/src/types/properties/assignment.rs b/checker/src/types/properties/assignment.rs
index 31538e68..14eb510a 100644
--- a/checker/src/types/properties/assignment.rs
+++ b/checker/src/types/properties/assignment.rs
@@ -9,8 +9,9 @@ use crate::{
 	types::{
 		calling::{CallingDiagnostics, CallingOutput, SynthesisedArgument},
 		get_constraint,
+		helpers::tuple_like,
 		logical::{BasedOnKey, Logical, LogicalOrValid, NeedsCalculation},
-		tuple_like, Constructor, GenericChain, PartiallyAppliedGenerics, TypeStore,
+		Constructor, GenericChain, PartiallyAppliedGenerics, TypeStore,
 	},
 	Environment, Type, TypeId,
 };
@@ -129,7 +130,10 @@ pub fn set_property<B: CallCheckingBehavior>(
 					environment,
 					types,
 				);
+
 				if let SubTypeResult::IsNotSubType(reason) = result {
+					crate::utilities::notify!("Here {:?} {:?}", property_constraint, new);
+
 					let is_modifying_tuple_length = under.is_equal_to("length")
 						&& tuple_like(object_constraint, types, environment);
 
@@ -203,7 +207,7 @@ pub fn set_property<B: CallCheckingBehavior>(
 		result_union: _,
 	}) = types.get_type_by_id(on)
 	{
-		crate::utilities::notify!("Here");
+		crate::utilities::notify!("Cascading assigment bc of conditional result");
 		let truthy = *truthy_result;
 		let otherwise_result = *otherwise_result;
 
@@ -226,6 +230,13 @@ pub fn set_property<B: CallCheckingBehavior>(
 		);
 	}
 
+	// Important that this goes below the condition above
+	let on = if let Type::Narrowed { narrowed_to, .. } = types.get_type_by_id(on) {
+		*narrowed_to
+	} else {
+		on
+	};
+
 	// IMPORTANT: THIS ALSO CAPTURES POLY CONSTRAINTS
 	let current_property =
 		get_property_unbound((on, None), (publicity, under, None), false, environment, types);
@@ -263,12 +274,13 @@ pub fn set_property<B: CallCheckingBehavior>(
 				types,
 			),
 		}
-	} else if get_constraint(on, types).is_some() {
-		Err(SetPropertyError::AssigningToNonExistent {
-			property: PropertyKeyRepresentation::new(under, environment, types),
-			position,
-		})
 	} else {
+		if get_constraint(on, types).is_some() {
+			return Err(SetPropertyError::AssigningToNonExistent {
+				property: PropertyKeyRepresentation::new(under, environment, types),
+				position,
+			});
+		}
 		// Sealed & no extensions check for NEW property (frozen case covered above)
 		{
 			if object_protection.is_some() {
diff --git a/checker/src/types/properties/mod.rs b/checker/src/types/properties/mod.rs
index dd608419..804c740b 100644
--- a/checker/src/types/properties/mod.rs
+++ b/checker/src/types/properties/mod.rs
@@ -186,7 +186,7 @@ impl<'a> PropertyKey<'a> {
 }
 
 /// For getting `length` and stuff
-pub(crate) fn get_simple_value(
+pub(crate) fn get_simple_property_value(
 	ctx: &impl InformationChain,
 	on: TypeId,
 	property: &PropertyKey,
@@ -508,10 +508,10 @@ pub(crate) fn key_matches(
 			);
 
 			let contributions = state.contributions.unwrap();
-			crate::utilities::notify!(
-				"Here contributions {:?}",
-				&contributions.staging_contravariant
-			);
+			// crate::utilities::notify!(
+			// 	"Here contributions {:?}",
+			// 	&contributions.staging_contravariant
+			// );
 
 			(result.is_subtype(), contributions.staging_contravariant)
 		}
diff --git a/checker/src/types/store.rs b/checker/src/types/store.rs
index f6bcb3bb..263ced34 100644
--- a/checker/src/types/store.rs
+++ b/checker/src/types/store.rs
@@ -148,30 +148,20 @@ impl Default for TypeStore {
 				name: "T".into(),
 				extends: TypeId::NUMBER_TYPE,
 			}),
-			Type::RootPolyType(PolyNature::StructureGeneric {
-				name: "U".into(),
-				extends: TypeId::NUMBER_TYPE,
-			}),
 			Type::AliasTo {
+				name: "GreaterThan".into(),
+				parameters: Some(vec![TypeId::NUMBER_GENERIC]),
 				to: TypeId::NUMBER_TYPE,
-				name: "InclusiveRange".into(),
-				parameters: Some(vec![
-					TypeId::NUMBER_FLOOR_GENERIC,
-					TypeId::NUMBER_CEILING_GENERIC,
-				]),
 			},
 			Type::AliasTo {
+				name: "LessThan".into(),
+				parameters: Some(vec![TypeId::NUMBER_GENERIC]),
 				to: TypeId::NUMBER_TYPE,
-				name: "ExclusiveRange".into(),
-				parameters: Some(vec![
-					TypeId::NUMBER_FLOOR_GENERIC,
-					TypeId::NUMBER_CEILING_GENERIC,
-				]),
 			},
 			Type::AliasTo {
-				to: TypeId::NUMBER_TYPE,
 				name: "MultipleOf".into(),
-				parameters: Some(vec![TypeId::NUMBER_FLOOR_GENERIC]),
+				parameters: Some(vec![TypeId::NUMBER_GENERIC]),
+				to: TypeId::NUMBER_TYPE,
 			},
 			// Intermediate for the below
 			Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
@@ -182,26 +172,25 @@ impl Default for TypeStore {
 				)])),
 			}),
 			Type::And(TypeId::NUMBER_TYPE, TypeId::NOT_NOT_A_NUMBER),
-			// TODO WIP
 			Type::AliasTo {
 				name: "Literal".into(),
-				to: TypeId::T_TYPE,
 				parameters: Some(vec![TypeId::T_TYPE]),
+				to: TypeId::T_TYPE,
 			},
 			Type::AliasTo {
 				name: "Exclusive".into(),
-				to: TypeId::T_TYPE,
 				parameters: Some(vec![TypeId::T_TYPE]),
+				to: TypeId::T_TYPE,
 			},
 			Type::AliasTo {
 				name: "Not".into(),
-				to: TypeId::ANY_TYPE,
 				parameters: Some(vec![TypeId::T_TYPE]),
+				to: TypeId::ANY_TYPE,
 			},
 			Type::AliasTo {
 				name: "CaseInsensitive".into(),
-				to: TypeId::STRING_TYPE,
 				parameters: Some(vec![TypeId::STRING_GENERIC]),
+				to: TypeId::STRING_TYPE,
 			},
 			Type::RootPolyType(PolyNature::Open(TypeId::BOOLEAN_TYPE)),
 			Type::RootPolyType(PolyNature::Open(TypeId::NUMBER_TYPE)),
@@ -302,46 +291,39 @@ impl TypeStore {
 		iter.into_iter().reduce(|acc, n| self.new_or_type(acc, n)).unwrap_or(TypeId::NEVER_TYPE)
 	}
 
-	pub fn new_and_type(&mut self, lhs: TypeId, rhs: TypeId) -> Result<TypeId, ()> {
+	// intersection. Does not calculate disjoint
+	pub fn new_and_type(&mut self, lhs: TypeId, rhs: TypeId) -> TypeId {
+		// string & string = string
 		if lhs == rhs {
-			return Ok(lhs);
-		}
-
-		let left_ty = self.get_type_by_id(lhs);
-		let right_ty = self.get_type_by_id(rhs);
-
-		// TODO more cases
-		if let (Type::Constant(l), Type::Constant(r)) = (left_ty, right_ty) {
-			if l != r {
-				return Err(());
-			}
-		} else if left_ty.is_nominal() && right_ty.is_nominal() {
-			return Err(());
+			return lhs;
 		}
 
-		// (left and right) distributivity.
-		let result = if let Type::Or(or_lhs, or_rhs) = left_ty {
+		// (left and right) distributivity & some other reductions on singleton types bc why not
+		// TODO sort intrinsics?
+		let lhs_type = self.get_type_by_id(lhs);
+		let rhs_type = self.get_type_by_id(rhs);
+		if let Type::Or(or_lhs, or_rhs) = lhs_type {
 			let (or_lhs, or_rhs) = (*or_lhs, *or_rhs);
-			let new_lhs = self.new_and_type(or_lhs, rhs)?;
-			let new_rhs = self.new_and_type(or_rhs, rhs)?;
+			let new_lhs = self.new_and_type(or_lhs, rhs);
+			let new_rhs = self.new_and_type(or_rhs, rhs);
 			self.new_or_type(new_lhs, new_rhs)
-		} else if let Type::Or(or_lhs, or_rhs) = right_ty {
+		} else if let Type::Or(or_lhs, or_rhs) = rhs_type {
 			let (or_lhs, or_rhs) = (*or_lhs, *or_rhs);
-			let new_lhs = self.new_and_type(lhs, or_lhs)?;
-			let new_rhs = self.new_and_type(lhs, or_rhs)?;
+			let new_lhs = self.new_and_type(lhs, or_lhs);
+			let new_rhs = self.new_and_type(lhs, or_rhs);
 			self.new_or_type(new_lhs, new_rhs)
+		} else if let Type::Constant(_) = lhs_type {
+			lhs
+		} else if let Type::Constant(_) = rhs_type {
+			rhs
+		} else if let Type::And(rhs_lhs, rhs_rhs) = rhs_type {
+			let (rhs_lhs, rhs_rhs) = (*rhs_lhs, *rhs_rhs);
+			let lhs = self.new_and_type(lhs, rhs_lhs);
+			self.new_and_type(lhs, rhs_rhs)
 		} else {
 			let ty = Type::And(lhs, rhs);
 			self.register_type(ty)
-		};
-
-		Ok(result)
-	}
-
-	/// TODO temp
-	#[must_use]
-	pub fn into_vec_temp(self) -> Vec<(TypeId, Type)> {
-		self.types.into_iter().enumerate().map(|(idx, ty)| (TypeId(idx as u16), ty)).collect()
+		}
 	}
 
 	/// From something like: let a: number => string. Rather than a actual function
@@ -554,7 +536,34 @@ impl TypeStore {
 		self.register_type(Type::RootPolyType(PolyNature::Open(base)))
 	}
 
+	/// Will provide origin rewriting as well
 	pub fn new_narrowed(&mut self, from: TypeId, narrowed_to: TypeId) -> TypeId {
+		let from_ty = self.get_type_by_id(from);
+		let new_constraint = self.get_type_by_id(narrowed_to);
+		let (from, existing) = if let Type::Narrowed { from, narrowed_to } = from_ty {
+			(*from, Some(*narrowed_to))
+		} else {
+			(from, None)
+		};
+		// temp fix for adding things.
+		let narrowed_to = if let (
+			Some(existing),
+			Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
+				on:
+					TypeId::GREATER_THAN
+					| TypeId::LESS_THAN
+					| TypeId::MULTIPLE_OF
+					| TypeId::NOT_RESTRICTION,
+				arguments: _,
+			}),
+		) = (existing, new_constraint)
+		{
+			self.new_and_type(existing, narrowed_to)
+		} else {
+			// crate::utilities::notify!("{:?}", from_ty);
+			narrowed_to
+		};
+
 		self.register_type(Type::Narrowed { from, narrowed_to })
 	}
 
@@ -633,4 +642,13 @@ impl TypeStore {
 	pub(crate) fn new_key_of(&mut self, of: TypeId) -> TypeId {
 		self.register_type(Type::Constructor(Constructor::KeyOf(of)))
 	}
+
+	/// TODO temp for debugging
+	pub fn user_types(&self) -> impl Iterator<Item = (TypeId, &Type)> + '_ {
+		self.types
+			.iter()
+			.enumerate()
+			.skip(TypeId::INTERNAL_TYPE_COUNT)
+			.map(|(idx, ty)| (TypeId(idx as u16), ty))
+	}
 }
diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs
index b1ae45e0..1fe9fa0c 100644
--- a/checker/src/types/subtyping.rs
+++ b/checker/src/types/subtyping.rs
@@ -6,7 +6,7 @@ use crate::{
 	context::{GeneralContext, InformationChain},
 	features::{
 		objects::{self, SpecialObject},
-		operations::MathematicalAndBitwise,
+		operations::MathematicalOrBitwiseOperation,
 	},
 	Constant, Environment, PropertyValue, TypeId,
 };
@@ -267,12 +267,11 @@ pub(crate) fn type_is_subtype_with_generics(
 				types,
 			);
 			// Temp fix for narrowing constants
-			crate::utilities::notify!("{:?}", super::helpers::is_not_of_constant(*right, types));
+			// crate::utilities::notify!("{:?}", super::helpers::is_not_of_constant(*right, types));
 			// SubTypeResult::IsNotSubType(_)
 			return if let (Type::Narrowed { from, .. }, _, true) =
 				(subtype, &result, super::helpers::is_not_of_constant(*right, types))
 			{
-				crate::utilities::notify!("Here");
 				type_is_subtype_with_generics(
 					(base_type, base_type_arguments),
 					(*from, ty_structure_arguments),
@@ -308,61 +307,15 @@ pub(crate) fn type_is_subtype_with_generics(
 				left_result
 			};
 		}
-		// Type::And(left, right) => {
-		// 	let left_is_operator_right_is_not =
-		// 		supertype.is_operator();
-
-		// 	// edge cases on edge cases
-		// 	// If any of these are true. Then do not perform constraint argument lookup
-		// 	let edge_case = left_is_operator_right_is_not;
-
-		// 	if !edge_case {
-
-		// 	let right = *right;
-		// 	let left_result = type_is_subtype_with_generics(
-		// 		(base_type, base_type_arguments),
-		// 		(*left, ty_structure_arguments),
-		// 		state,
-		// 		information,
-		// 		types,
-		// 	);
-
-		// 	return if let SubTypeResult::IsSubType = left_result {
-		// 		left_result
-		// 	} else {
-		// 		type_is_subtype_with_generics(
-		// 			(base_type, base_type_arguments),
-		// 			(right, ty_structure_arguments),
-		// 			state,
-		// 			information,
-		// 			types,
-		// 		)
-		// 	};
-		// }
-		// }
-		// TODO others
 		Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
 			on: on @ TypeId::NOT_RESTRICTION,
-			arguments,
+			arguments: _,
 		}) => {
 			match *on {
 				TypeId::NOT_RESTRICTION => {
-					let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap();
-					// https://leanprover-community.github.io/mathlib4_docs/Mathlib/Data/Set/Basic.html#Set.subset_compl_comm -> https://leanprover-community.github.io/mathlib4_docs/Mathlib/Data/Set/Basic.html#Set.subset_compl_iff_disjoint_left
-
-					let result = super::disjoint::types_are_disjoint(
-						base_type,
-						inner,
-						&mut state.already_checked,
-						information,
-						types,
-					);
-					// crate::utilities::notify!("Here {:?}", (&result, inner));
-					return if result {
-						SubTypeResult::IsSubType
-					} else {
-						SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
-					};
+					// This only happens when subtype ∪ supertype = `any`. This is only true when
+					// one is `any`. `Not<any>` is already `never` and `supertype = any` is handled above
+					return SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch);
 				}
 				_ => unreachable!(),
 			}
@@ -378,7 +331,7 @@ pub(crate) fn type_is_subtype_with_generics(
 				};
 			}
 
-			crate::utilities::notify!("Looking for {:?} with {:?}", ty, ty_structure_arguments);
+			// crate::utilities::notify!("Looking for {:?} with {:?}", ty, ty_structure_arguments);
 
 			if let Some(arg) = ty_structure_arguments.and_then(|tas| tas.get_argument_covariant(ty))
 			{
@@ -438,20 +391,20 @@ pub(crate) fn type_is_subtype_with_generics(
 
 			// If lhs is not operator unless argument is operator
 			// if !T::INFER_GENERICS && ty_structure_arguments.is_none() {
-			let right_arg = get_constraint(ty, types).unwrap();
+			let right_constraint = get_constraint(ty, types).unwrap();
 
-			crate::utilities::notify!(
-				"edge case {:?}",
-				(
-					types.get_type_by_id(ty),
-					types.get_type_by_id(right_arg),
-					types.get_type_by_id(right_arg).is_operator()
-				)
-			);
+			// crate::utilities::notify!(
+			// 	"RHS is parameter, edge case results to {:?}",
+			// 	(
+			// 		types.get_type_by_id(ty),
+			// 		types.get_type_by_id(right_constraint),
+			// 		types.get_type_by_id(right_constraint).is_operator()
+			// 	)
+			// );
 
 			// This is important that LHS is not operator
 			let left_is_operator_right_is_not =
-				supertype.is_operator() && !types.get_type_by_id(right_arg).is_operator();
+				supertype.is_operator() && !types.get_type_by_id(right_constraint).is_operator();
 
 			// edge cases on edge cases
 			// If any of these are true. Then do not perform constraint argument lookup
@@ -460,12 +413,14 @@ pub(crate) fn type_is_subtype_with_generics(
 					supertype,
 					Type::RootPolyType(rpt)
 					if rpt.is_substitutable()
-				) || matches!(supertype, Type::Constructor(..));
-
+				) || matches!(supertype, Type::Constructor(..))
+				|| base_type_arguments
+					.and_then(|args| args.get_argument_covariant(base_type))
+					.is_some();
 			if !edge_case {
 				let result = type_is_subtype_with_generics(
 					(base_type, base_type_arguments),
-					(right_arg, ty_structure_arguments),
+					(right_constraint, ty_structure_arguments),
 					state,
 					information,
 					types,
@@ -505,7 +460,7 @@ pub(crate) fn type_is_subtype_with_generics(
 				}
 			} else {
 				// TODO what about if LHS has inferred constraint
-				crate::utilities::notify!("Constant {:?} against RHS {:#?}", lhs, subtype);
+				// crate::utilities::notify!("Constant {:?} against RHS {:#?}", lhs, subtype);
 				SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
 			}
 		}
@@ -692,10 +647,8 @@ pub(crate) fn type_is_subtype_with_generics(
 				// TODO temp fix
 				if let Type::Constructor(c) = subtype {
 					crate::utilities::notify!("TODO right hand side maybe okay");
-					if let Some(to) = c.get_base() {
-						if to == base_type {
-							return SubTypeResult::IsSubType;
-						}
+					if c.get_constraint() == base_type {
+						return SubTypeResult::IsSubType;
 					}
 				}
 				if let PolyNature::FunctionGeneric { .. } = nature {
@@ -845,10 +798,13 @@ pub(crate) fn type_is_subtype_with_generics(
 				// Ezno intrinsic
 				TypeId::LITERAL_RESTRICTION => {
 					let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap();
-					return if let Type::Constant(rhs_constant) = subtype {
+					return if let Type::Constant(_)
+					| Type::Object(ObjectNature::RealDeal)
+					| Type::SpecialObject(..) = subtype
+					{
 						type_is_subtype_with_generics(
 							(inner, base_type_arguments),
-							(rhs_constant.get_backing_type(), ty_structure_arguments),
+							(ty, ty_structure_arguments),
 							state,
 							information,
 							types,
@@ -881,7 +837,7 @@ pub(crate) fn type_is_subtype_with_generics(
 				}
 				TypeId::MULTIPLE_OF => {
 					let argument =
-						arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap();
+						arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap();
 
 					let right_multiple = crate::types::intrinsics::get_multiple(ty, types);
 					return if let (
@@ -901,18 +857,93 @@ pub(crate) fn type_is_subtype_with_generics(
 						SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
 					};
 				}
-				TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE => {
-					return if let (super_range, Some(sub_range)) = (
-						intrinsics::get_range(base_type, types).unwrap(),
-						intrinsics::get_range(ty, types),
-					) {
-						if sub_range.contained_in(super_range) {
+				TypeId::GREATER_THAN => {
+					let argument =
+						arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap();
+					let argument_type = types.get_type_by_id(argument);
+					return if let (
+						Type::Constant(Constant::Number(value)),
+						Type::Constant(Constant::Number(subtype_number)),
+					) = (argument_type, subtype)
+					{
+						if subtype_number > value {
+							SubTypeResult::IsSubType
+						} else {
+							SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
+						}
+					} else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
+						on: TypeId::GREATER_THAN,
+						arguments,
+					}) = subtype
+					{
+						let subtype_argument =
+							arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap();
+						// Transitivity
+						if argument == subtype_argument {
+							SubTypeResult::IsSubType
+						} else {
+							let subtype_argument = types.get_type_by_id(subtype_argument);
+							if let (
+								Type::Constant(Constant::Number(subtype_number)),
+								Type::Constant(Constant::Number(value)),
+							) = (argument_type, subtype_argument)
+							{
+								if subtype_number > value {
+									SubTypeResult::IsSubType
+								} else {
+									SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
+								}
+							} else {
+								crate::utilities::notify!("Here");
+								SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
+							}
+						}
+					} else {
+						SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
+					};
+				}
+				TypeId::LESS_THAN => {
+					let argument =
+						arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap();
+					let argument_type = types.get_type_by_id(argument);
+					return if let (
+						Type::Constant(Constant::Number(value)),
+						Type::Constant(Constant::Number(subtype_number)),
+					) = (argument_type, subtype)
+					{
+						if subtype_number < value {
 							SubTypeResult::IsSubType
 						} else {
 							SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
 						}
+					} else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
+						on: TypeId::GREATER_THAN,
+						arguments,
+					}) = subtype
+					{
+						let subtype_argument =
+							arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap();
+						// Transitivity
+						if argument == subtype_argument {
+							SubTypeResult::IsSubType
+						} else {
+							let subtype_argument = types.get_type_by_id(subtype_argument);
+							if let (
+								Type::Constant(Constant::Number(subtype_number)),
+								Type::Constant(Constant::Number(value)),
+							) = (argument_type, subtype_argument)
+							{
+								if subtype_number < value {
+									SubTypeResult::IsSubType
+								} else {
+									SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
+								}
+							} else {
+								crate::utilities::notify!("Here");
+								SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
+							}
+						}
 					} else {
-						crate::utilities::notify!("TODO");
 						SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
 					};
 				}
@@ -1113,7 +1144,8 @@ pub(crate) fn type_is_subtype_with_generics(
 		Type::Constructor(cst) => match cst {
 			// For template literal types
 			Constructor::BinaryOperator {
-				operator: crate::types::MathematicalAndBitwise::Add,
+				operator: crate::types::MathematicalOrBitwiseOperation::Add,
+				result: TypeId::STRING_TYPE,
 				..
 			} => {
 				if let Type::Constant(Constant::String(rs)) = subtype {
@@ -1131,17 +1163,26 @@ pub(crate) fn type_is_subtype_with_generics(
 						// TODO clear contributions
 						SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
 					}
+				} else if let Type::Constructor(Constructor::BinaryOperator {
+					operator: crate::types::MathematicalOrBitwiseOperation::Add,
+					result: TypeId::STRING_TYPE,
+					..
+				}) = subtype
+				{
+					crate::utilities::notify!("TODO test prefixes");
+					SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
 				} else {
-					crate::utilities::notify!("RHS not string");
-					type_is_subtype_with_generics(
-						(TypeId::NUMBER_TYPE, base_type_arguments),
-						(ty, ty_structure_arguments),
-						state,
-						information,
-						types,
-					)
+					SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
 				}
 			}
+			Constructor::BinaryOperator {
+				operator: crate::types::MathematicalOrBitwiseOperation::Add,
+				result: TypeId::NUMBER_TYPE,
+				..
+			} => {
+				crate::utilities::notify!("TODO here!");
+				SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
+			}
 			Constructor::BinaryOperator { .. } | Constructor::CanonicalRelationOperator { .. } => {
 				unreachable!("invalid constructor on LHS")
 			}
@@ -1415,9 +1456,15 @@ pub(crate) fn type_is_subtype_with_generics(
 					if *prototype == base_type {
 						SubTypeResult::IsSubType
 					} else {
+						crate::utilities::notify!(
+							"Mismatched prototype {:?} != {:?}",
+							prototype,
+							base_type
+						);
 						SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
 					}
 				} else {
+					crate::utilities::notify!("No prototype");
 					SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
 				}
 			}
@@ -1433,13 +1480,26 @@ pub(crate) fn type_is_subtype_with_generics(
 					types,
 				)
 			}
-			Type::And(a, b) => {
-				// TODO more
-				crate::utilities::notify!("Here LHS class, RHS and");
-				if *a == base_type || *b == base_type {
-					SubTypeResult::IsSubType
+			Type::And(left, right) => {
+				// This only happens in predicate edge cases (with numbers)
+				let left_result = type_is_subtype_with_generics(
+					(base_type, base_type_arguments),
+					(*left, ty_structure_arguments),
+					state,
+					information,
+					types,
+				);
+
+				if let SubTypeResult::IsSubType = left_result {
+					left_result
 				} else {
-					SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
+					type_is_subtype_with_generics(
+						(base_type, base_type_arguments),
+						(*right, ty_structure_arguments),
+						state,
+						information,
+						types,
+					)
 				}
 			}
 			Type::SpecialObject(SpecialObject::Function(..)) | Type::FunctionReference(..)
@@ -1447,8 +1507,8 @@ pub(crate) fn type_is_subtype_with_generics(
 			{
 				SubTypeResult::IsSubType
 			}
-			ty => {
-				crate::utilities::notify!("Does {:?} not match class", ty);
+			_ty => {
+				// crate::utilities::notify!("{:?} does not match class", base_type);
 				SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch)
 			}
 		},
@@ -2588,7 +2648,7 @@ pub(crate) fn slice_matches_type(
 					allow_casts,
 				)
 			} else if let Some(contributions) = contributions {
-				assert!(rpt.is_substitutable());
+				// assert!(rpt.is_substitutable(), "{:?}", rpt);
 				let constraint = rpt.get_constraint();
 				let res = slice_matches_type(
 					(constraint, base_type_arguments),
@@ -2735,23 +2795,6 @@ pub(crate) fn slice_matches_type(
 				allow_casts,
 			)
 		}
-		Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
-			on: TypeId::MULTIPLE_OF | TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE,
-			arguments: _,
-		}) if allow_casts => {
-			// Special behavior here to treat numerical property keys (which are strings) as numbers
-			if let Ok(value) = slice.parse::<f64>() {
-				number_matches_type(
-					(base, base_type_arguments),
-					value,
-					contributions,
-					information,
-					types,
-				)
-			} else {
-				false
-			}
-		}
 		Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
 			on: TypeId::NOT_RESTRICTION,
 			arguments,
@@ -2770,6 +2813,22 @@ pub(crate) fn slice_matches_type(
 			// crate::utilities::notify!("negated slice arguments={:?}", _k);
 			!matches
 		}
+		Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments: _ })
+			if allow_casts && intrinsics::is_ezno_number_intrinsic(*on) =>
+		{
+			// Special behavior here to treat numerical property keys (which are strings) as numbers
+			if let Ok(value) = slice.parse::<f64>() {
+				number_matches_type(
+					(base, base_type_arguments),
+					value,
+					contributions,
+					information,
+					types,
+				)
+			} else {
+				false
+			}
+		}
 		Type::Constructor(super::Constructor::KeyOf(on)) => {
 			let argument =
 				(Publicity::Public, &PropertyKey::String(std::borrow::Cow::Borrowed(slice)), None);
@@ -2836,10 +2895,18 @@ pub(crate) fn slice_matches_type(
 				false
 			}
 		}
+		Type::Constant(Constant::Number(base)) => {
+			crate::utilities::notify!("Here");
+			if let Ok(slice_as_float) = slice.parse::<f64>() {
+				*base == slice_as_float
+			} else {
+				false
+			}
+		}
 		Type::Constructor(super::Constructor::BinaryOperator {
 			lhs,
 			rhs,
-			operator: MathematicalAndBitwise::Add,
+			operator: MathematicalOrBitwiseOperation::Add,
 			result: _,
 		}) => {
 			let lhs = base_type_arguments
@@ -2922,8 +2989,7 @@ pub(crate) fn number_matches_type(
 			on: TypeId::MULTIPLE_OF,
 			arguments,
 		}) => {
-			let argument =
-				arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap();
+			let argument = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap();
 			if let Type::Constant(Constant::Number(argument)) = types.get_type_by_id(argument) {
 				let number: ordered_float::NotNan<f64> = number.try_into().unwrap();
 				(number % argument) == 0.
@@ -2933,11 +2999,20 @@ pub(crate) fn number_matches_type(
 			}
 		}
 		Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
-			on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE,
+			on: TypeId::LESS_THAN,
+			arguments: _,
+		}) => {
+			todo!()
+			// let lhs_range = intrinsics::get_range(base, types).unwrap();
+			// intrinsics::FloatRange::new_single(number.try_into().unwrap()).contained_in(lhs_range)
+		}
+		Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
+			on: TypeId::GREATER_THAN,
 			arguments: _,
 		}) => {
-			let lhs_range = intrinsics::get_range(base, types).unwrap();
-			intrinsics::FloatRange::single(number.try_into().unwrap()).contained_in(lhs_range)
+			todo!()
+			// let lhs_range = intrinsics::get_range(base, types).unwrap();
+			// intrinsics::FloatRange::new_single(number.try_into().unwrap()).contained_in(lhs_range)
 		}
 		Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics {
 			on: TypeId::NOT_RESTRICTION,
diff --git a/checker/src/utilities/debugging.rs b/checker/src/utilities/debugging.rs
index 6ef42907..2a19cee2 100644
--- a/checker/src/utilities/debugging.rs
+++ b/checker/src/utilities/debugging.rs
@@ -17,7 +17,7 @@ pub(crate) fn is_debug_mode() -> bool {
 			value
 		} else {
 			let new_value = std::env::var("EZNO_DEBUG")
-				.map(|value| !value.is_empty() || value == "0")
+				.map(|value| !(value.is_empty() || value == "0"))
 				.unwrap_or_default();
 			IS_DEBUG_MODE.set(Some(new_value));
 			new_value
diff --git a/checker/src/utilities/float_range.rs b/checker/src/utilities/float_range.rs
index 133e226e..c0870212 100644
--- a/checker/src/utilities/float_range.rs
+++ b/checker/src/utilities/float_range.rs
@@ -1,18 +1,97 @@
 type BetterF64 = ordered_float::NotNan<f64>;
 
-// TODO
 #[derive(Debug, Clone, Copy, PartialEq)]
-pub enum FloatRange {
-	/// yes or `===`
-	Inclusive { floor: BetterF64, ceiling: BetterF64 },
-	/// but not necessarily `===`
-	Exclusive { floor: BetterF64, ceiling: BetterF64 },
+pub enum InclusiveExclusive {
+	Inclusive,
+	Exclusive,
 }
 
+use InclusiveExclusive::{Exclusive, Inclusive};
+
+impl InclusiveExclusive {
+	#[must_use]
+	pub fn mix(self, other: Self) -> Self {
+		if let (Inclusive, Inclusive) = (self, other) {
+			Inclusive
+		} else {
+			Exclusive
+		}
+	}
+
+	#[must_use]
+	pub fn is_inclusive(self) -> bool {
+		matches!(self, Inclusive)
+	}
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct FloatRange {
+	pub floor: (InclusiveExclusive, BetterF64),
+	pub ceiling: (InclusiveExclusive, BetterF64),
+}
+
+impl Default for FloatRange {
+	fn default() -> Self {
+		Self {
+			floor: (Exclusive, f64::NEG_INFINITY.try_into().unwrap()),
+			ceiling: (Exclusive, f64::INFINITY.try_into().unwrap()),
+		}
+	}
+}
+
+// TODO try_from (assert ceiling > floor etc)
 impl FloatRange {
 	#[must_use]
-	pub fn single(on: BetterF64) -> Self {
-		Self::Inclusive { floor: on, ceiling: on }
+	pub fn new_single(on: BetterF64) -> Self {
+		Self { floor: (Inclusive, on), ceiling: (Inclusive, on) }
+	}
+
+	#[must_use]
+	pub fn as_single(self) -> Option<BetterF64> {
+		if let FloatRange { floor: (Inclusive, floor), ceiling: (Inclusive, ceiling) } = self {
+			(floor == ceiling).then_some(floor)
+		} else {
+			None
+		}
+	}
+
+	#[must_use]
+	pub fn new_greater_than(greater_than: BetterF64) -> Self {
+		FloatRange {
+			floor: (Exclusive, greater_than),
+			ceiling: (Exclusive, f64::INFINITY.try_into().unwrap()),
+		}
+	}
+
+	#[must_use]
+	pub fn get_greater_than(self) -> Option<BetterF64> {
+		(self.floor.1 != f64::NEG_INFINITY).then_some(self.floor.1)
+	}
+
+	#[must_use]
+	pub fn new_less_than(less_than: BetterF64) -> Self {
+		FloatRange {
+			floor: (Exclusive, f64::NEG_INFINITY.try_into().unwrap()),
+			ceiling: (Exclusive, less_than),
+		}
+	}
+
+	#[must_use]
+	pub fn get_less_than(self) -> Option<BetterF64> {
+		(self.ceiling.1 != f64::INFINITY).then_some(self.ceiling.1)
+	}
+
+	#[must_use]
+	pub fn contains(self, value: BetterF64) -> bool {
+		if self.floor.1 < value && value < self.ceiling.1 {
+			true
+		} else if self.floor.1 == value {
+			self.floor.0.is_inclusive()
+		} else if self.ceiling.1 == value {
+			self.ceiling.0.is_inclusive()
+		} else {
+			false
+		}
 	}
 
 	/// For disjointness. TODO Think this is correct
@@ -20,30 +99,23 @@ impl FloatRange {
 	pub fn overlaps(self, other: Self) -> bool {
 		crate::utilities::notify!("{:?} ∩ {:?} != ∅", self, other);
 
-		if let (
-			Self::Inclusive { floor: l_floor, ceiling: l_ceiling },
-			Self::Inclusive { floor: r_floor, ceiling: r_ceiling },
-		) = (self, other)
-		{
-			if l_floor <= r_floor {
-				l_ceiling >= r_floor
-			} else if l_ceiling >= r_ceiling {
-				l_floor <= r_ceiling
-			} else {
-				false
-			}
+		// TODO more with inclusivity etc
+		other.floor.1 <= self.ceiling.1 || other.ceiling.1 <= self.floor.1
+	}
+
+	pub fn intersection(self, other: Self) -> Result<Self, ()> {
+		crate::utilities::notify!("{:?} ∩ {:?}", self, other);
+
+		let max_floor = self.floor.1.max(other.floor.1);
+		let min_ceiling = self.ceiling.1.min(other.ceiling.1);
+
+		if max_floor <= min_ceiling {
+			Ok(Self {
+				floor: (self.floor.0.mix(other.floor.0), max_floor),
+				ceiling: (self.ceiling.0.mix(other.ceiling.0), min_ceiling),
+			})
 		} else {
-			let (Self::Inclusive { floor: l_floor, ceiling: l_ceiling }
-			| Self::Exclusive { floor: l_floor, ceiling: l_ceiling }) = self;
-			let (Self::Inclusive { floor: r_floor, ceiling: r_ceiling }
-			| Self::Exclusive { floor: r_floor, ceiling: r_ceiling }) = other;
-			if l_floor < r_floor {
-				l_ceiling > r_floor
-			} else if l_ceiling > r_ceiling {
-				l_floor < r_ceiling
-			} else {
-				false
-			}
+			Err(())
 		}
 	}
 
@@ -52,37 +124,27 @@ impl FloatRange {
 	pub fn contained_in(self, other: Self) -> bool {
 		crate::utilities::notify!("{:?} ⊆ {:?}", self, other);
 		// Edge case
-		if let (
-			Self::Inclusive { floor: l_floor, ceiling: l_ceiling },
-			Self::Exclusive { floor: r_floor, ceiling: r_ceiling },
-		) = (self, other)
-		{
-			l_floor > r_floor && l_ceiling < r_ceiling
+		let lhs = if let (Inclusive, Exclusive) = (self.floor.0, other.floor.0) {
+			self.floor.1 > other.floor.1
 		} else {
-			let (Self::Inclusive { floor: l_floor, ceiling: l_ceiling }
-			| Self::Exclusive { floor: l_floor, ceiling: l_ceiling }) = self;
-			let (Self::Inclusive { floor: r_floor, ceiling: r_ceiling }
-			| Self::Exclusive { floor: r_floor, ceiling: r_ceiling }) = other;
-			l_floor >= r_floor && l_ceiling <= r_ceiling
-		}
+			self.floor.1 >= other.floor.1
+		};
+		let rhs = if let (Inclusive, Exclusive) = (self.ceiling.0, other.ceiling.0) {
+			self.ceiling.1 < other.ceiling.1
+		} else {
+			self.ceiling.1 <= other.ceiling.1
+		};
+		lhs && rhs
 	}
 
 	/// ∀ a in self, ∀ b in other: a > b
 	#[must_use]
 	pub fn above(self, other: Self) -> bool {
 		crate::utilities::notify!("{:?} > {:?}", self, other);
-		if let (
-			Self::Inclusive { floor: l_floor, ceiling: _ },
-			Self::Inclusive { floor: _, ceiling: r_ceiling },
-		) = (self, other)
-		{
-			l_floor > r_ceiling
+		if let (Inclusive, Inclusive) = (self.ceiling.0, other.floor.0) {
+			self.floor.1 > other.ceiling.1
 		} else {
-			let (Self::Inclusive { floor: l_floor, ceiling: _ }
-			| Self::Exclusive { floor: l_floor, ceiling: _ }) = self;
-			let (Self::Inclusive { floor: _, ceiling: r_ceiling }
-			| Self::Exclusive { floor: _, ceiling: r_ceiling }) = other;
-			l_floor >= r_ceiling
+			self.floor.1 >= other.ceiling.1
 		}
 	}
 
@@ -90,118 +152,147 @@ impl FloatRange {
 	#[must_use]
 	pub fn below(self, other: Self) -> bool {
 		crate::utilities::notify!("{:?} < {:?}", self, other);
-		if let (
-			Self::Inclusive { floor: _, ceiling: l_ceiling },
-			Self::Inclusive { floor: r_floor, ceiling: _ },
-		) = (self, other)
-		{
-			l_ceiling < r_floor
+		if let (Inclusive, Inclusive) = (self.ceiling.0, other.floor.0) {
+			self.ceiling.1 < other.floor.1
 		} else {
-			let (Self::Inclusive { floor: _, ceiling: l_ceiling }
-			| Self::Exclusive { floor: _, ceiling: l_ceiling }) = self;
-			let (Self::Inclusive { floor: r_floor, ceiling: _ }
-			| Self::Exclusive { floor: r_floor, ceiling: _ }) = other;
-			l_ceiling <= r_floor
+			self.ceiling.1 <= other.floor.1
 		}
 	}
 
 	#[must_use]
 	pub fn space_addition(self, other: Self) -> Self {
-		if let (
-			Self::Inclusive { floor: l_floor, ceiling: l_ceiling },
-			Self::Inclusive { floor: r_floor, ceiling: r_ceiling },
-		) = (self, other)
-		{
-			Self::Inclusive { floor: l_floor + r_floor, ceiling: l_ceiling + r_ceiling }
-		} else {
-			let (Self::Inclusive { floor: l_floor, ceiling: l_ceiling }
-			| Self::Exclusive { floor: l_floor, ceiling: l_ceiling }) = self;
-			let (Self::Inclusive { floor: r_floor, ceiling: r_ceiling }
-			| Self::Exclusive { floor: r_floor, ceiling: r_ceiling }) = other;
-			Self::Exclusive { floor: l_floor + r_floor, ceiling: l_ceiling + r_ceiling }
+		let floor_bound = self.floor.0.mix(other.floor.0);
+		let ceiling_bound = self.ceiling.0.mix(other.ceiling.0);
+		Self {
+			floor: (floor_bound, self.floor.1 + other.floor.1),
+			ceiling: (ceiling_bound, self.ceiling.1 + other.ceiling.1),
 		}
 	}
 
 	#[must_use]
 	pub fn space_multiplication(self, other: Self) -> Self {
-		let inclusive = matches!((self, other), (Self::Inclusive { .. }, Self::Inclusive { .. }));
-		let (Self::Inclusive { floor: l_floor, ceiling: l_ceiling }
-		| Self::Exclusive { floor: l_floor, ceiling: l_ceiling }) = self;
-		let (Self::Inclusive { floor: r_floor, ceiling: r_ceiling }
-		| Self::Exclusive { floor: r_floor, ceiling: r_ceiling }) = other;
-		// being lazy
+		let (l_floor, l_ceiling, r_floor, r_ceiling) =
+			(self.floor.1, self.ceiling.1, other.floor.1, other.ceiling.1);
+		// there may be a faster way but being lazy
 		let corners =
 			[l_floor * r_floor, l_floor * r_ceiling, r_floor * l_ceiling, l_ceiling * r_ceiling];
 		let floor = *corners.iter().min().unwrap();
 		let ceiling = *corners.iter().max().unwrap();
-		if inclusive {
-			Self::Inclusive { floor, ceiling }
+
+		let floor_bound = self.floor.0.mix(other.floor.0);
+		let ceiling_bound = self.ceiling.0.mix(other.ceiling.0);
+		Self { floor: (floor_bound, floor), ceiling: (ceiling_bound, ceiling) }
+	}
+
+	#[must_use]
+	pub fn contains_multiple_of(self, multiple_of: BetterF64) -> bool {
+		let (floor, ceiling) = (self.floor.1, self.ceiling.1);
+
+		let floor = floor / multiple_of;
+		let ceiling = ceiling / multiple_of;
+
+		// TODO >= ?
+		ceiling.floor() > *floor
+	}
+
+	// This will try to get cover
+	// A union like above might create gaps. aka if try_get_cover (0, 1) (3, 4) = (0, 4) then it implies 2
+	// exists is in one of the ranges. Thus in this case it returns None
+	#[must_use]
+	pub fn try_get_cover(self, other: Self) -> Option<Self> {
+		if self.contained_in(other) {
+			Some(other)
+		} else if other.contained_in(self) {
+			Some(self)
 		} else {
-			Self::Exclusive { floor, ceiling }
+			None
 		}
 	}
 
 	// TODO more :)
 }
 
+impl From<std::ops::Range<BetterF64>> for FloatRange {
+	fn from(range: std::ops::Range<BetterF64>) -> FloatRange {
+		FloatRange { floor: (Exclusive, range.start), ceiling: (Exclusive, range.end) }
+	}
+}
+impl TryFrom<std::ops::Range<f64>> for FloatRange {
+	type Error = ordered_float::FloatIsNan;
+
+	fn try_from(range: std::ops::Range<f64>) -> Result<Self, Self::Error> {
+		let floor = ordered_float::NotNan::new(range.start)?;
+		let ceiling = ordered_float::NotNan::new(range.end)?;
+		Ok(FloatRange { floor: (Exclusive, floor), ceiling: (Exclusive, ceiling) })
+	}
+}
+
 // TODO more
 #[cfg(test)]
 mod tests {
-	use super::{BetterF64, FloatRange};
+	use super::{BetterF64, FloatRange, InclusiveExclusive};
+
+	fn e(a: f64) -> (InclusiveExclusive, BetterF64) {
+		(InclusiveExclusive::Exclusive, a.try_into().unwrap())
+	}
+
+	fn i(a: f64) -> (InclusiveExclusive, BetterF64) {
+		(InclusiveExclusive::Inclusive, a.try_into().unwrap())
+	}
 
 	#[test]
 	fn contained_in() {
-		assert!(FloatRange::single(2.into())
-			.contained_in(FloatRange::Exclusive { floor: 0.into(), ceiling: 5.into() }));
+		let zero_to_four: FloatRange = FloatRange::try_from(0f64..4f64).unwrap();
+		assert!(FloatRange::new_single(2.into()).contained_in(zero_to_four));
 	}
 
 	#[test]
 	fn overlaps() {
-		assert!(FloatRange::Exclusive { floor: 0.into(), ceiling: 4.into() }
-			.overlaps(FloatRange::Exclusive { floor: 2.into(), ceiling: 5.into() }));
-		assert!(!FloatRange::Exclusive { floor: 0.into(), ceiling: 1.into() }
-			.overlaps(FloatRange::Exclusive { floor: 2.into(), ceiling: 5.into() }));
+		assert!(FloatRange { floor: e(0.), ceiling: e(4.) }
+			.overlaps(FloatRange { floor: e(2.), ceiling: e(5.) }));
+
+		assert!(!FloatRange { floor: e(0.), ceiling: e(1.) }
+			.overlaps(FloatRange { floor: e(2.), ceiling: e(5.) }));
 	}
 
 	#[test]
 	fn above() {
-		assert!(FloatRange::Exclusive { floor: 8.into(), ceiling: 10.into() }
-			.above(FloatRange::Exclusive { floor: 6.into(), ceiling: 7.into() }));
-		assert!(!FloatRange::Exclusive { floor: 0.into(), ceiling: 1.into() }
-			.above(FloatRange::Exclusive { floor: 0.into(), ceiling: 5.into() }));
+		assert!(FloatRange { floor: e(8.), ceiling: e(10.) }
+			.above(FloatRange { floor: e(6.), ceiling: e(7.) }));
+		assert!(!FloatRange { floor: e(0.), ceiling: e(1.) }
+			.above(FloatRange { floor: e(0.), ceiling: e(5.) }));
 	}
 
 	#[test]
 	fn below() {
-		assert!(FloatRange::Exclusive { floor: 0.into(), ceiling: 4.into() }
-			.below(FloatRange::Exclusive { floor: 6.into(), ceiling: 7.into() }));
-		assert!(!FloatRange::Exclusive { floor: 0.into(), ceiling: 1.into() }
-			.below(FloatRange::Exclusive { floor: 0.into(), ceiling: 5.into() }));
+		assert!(FloatRange { floor: e(0.), ceiling: e(4.) }
+			.below(FloatRange { floor: e(6.), ceiling: e(7.) }));
+		assert!(!FloatRange { floor: e(0.), ceiling: e(1.) }
+			.below(FloatRange { floor: e(0.), ceiling: e(5.) }));
 	}
 
 	#[test]
 	fn space_addition() {
-		assert_eq!(
-			FloatRange::Exclusive { floor: 0.into(), ceiling: 4.into() }
-				.space_addition(FloatRange::Exclusive { floor: 6.into(), ceiling: 7.into() }),
-			FloatRange::Exclusive { floor: 6.into(), ceiling: 11.into() }
-		);
+		let lhs = FloatRange { floor: e(0.), ceiling: e(4.) }
+			.space_addition(FloatRange { floor: e(6.), ceiling: e(7.) });
+		assert_eq!(lhs, FloatRange { floor: e(6.), ceiling: e(11.) });
 	}
 
 	#[test]
 	fn space_multiplication() {
-		assert_eq!(
-			FloatRange::Exclusive { floor: 0.into(), ceiling: 4.into() }
-				.space_multiplication(FloatRange::Exclusive { floor: 6.into(), ceiling: 7.into() }),
-			FloatRange::Exclusive { floor: 0.into(), ceiling: 28.into() }
-		);
-		assert_eq!(
-			FloatRange::Exclusive { floor: BetterF64::from(-2i32), ceiling: 4.into() }
-				.space_multiplication(FloatRange::Exclusive {
-					floor: BetterF64::from(-10i32),
-					ceiling: 1.into()
-				}),
-			FloatRange::Exclusive { floor: BetterF64::from(-40i32), ceiling: 20.into() }
-		);
+		let lhs = FloatRange { floor: e(0.), ceiling: e(4.) }
+			.space_multiplication(FloatRange { floor: e(6.), ceiling: e(7.) });
+		assert_eq!(lhs, FloatRange { floor: e(0.), ceiling: e(28.) });
+
+		let lhs = FloatRange { floor: e(-2.), ceiling: e(4.) }
+			.space_multiplication(FloatRange { floor: e(-10.), ceiling: e(1.) });
+		assert_eq!(lhs, FloatRange { floor: e(-40.), ceiling: e(20.) });
+	}
+
+	#[test]
+	fn multiple_of() {
+		let lhs = FloatRange { floor: e(30.), ceiling: e(34.) };
+		assert!(lhs.contains_multiple_of(4.into())); // 32
+		assert!(!lhs.contains_multiple_of(7.into())); // 28 -- 35
 	}
 }
diff --git a/checker/src/utilities/mod.rs b/checker/src/utilities/mod.rs
index 222c487a..4e59012d 100644
--- a/checker/src/utilities/mod.rs
+++ b/checker/src/utilities/mod.rs
@@ -3,6 +3,7 @@
 mod debugging;
 pub mod float_range;
 pub mod map;
+pub mod modulo_class;
 pub mod range_map;
 pub mod serialization;
 
diff --git a/checker/src/utilities/modulo_class.rs b/checker/src/utilities/modulo_class.rs
new file mode 100644
index 00000000..e5753d50
--- /dev/null
+++ b/checker/src/utilities/modulo_class.rs
@@ -0,0 +1,162 @@
+type BetterF64 = ordered_float::NotNan<f64>;
+
+/// x ≡ *class* [mod *modulo*]
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct ModuloClass {
+	pub modulo: BetterF64,
+	pub offset: BetterF64,
+}
+
+// TODO more operations
+impl ModuloClass {
+	#[must_use]
+	pub fn new(modulo: BetterF64, offset: BetterF64) -> Self {
+		debug_assert!(modulo != 0.);
+		if modulo > 0f64.try_into().unwrap() {
+			Self { offset: offset % modulo, modulo }
+		} else {
+			// TODO think this is correct. [1]_{-3} = [2]_{3}
+			let modulo = -modulo;
+			Self { offset: modulo - (offset % modulo), modulo }
+		}
+	}
+
+	#[must_use]
+	pub fn contains(self, value: BetterF64) -> bool {
+		// Note -0. = 0.
+		(value - self.offset) % self.modulo == 0.
+	}
+
+	/// WIP
+	#[must_use]
+	pub fn disjoint(self, other: Self) -> bool {
+		if let Ok(gcd) = gcd_of_float(self.modulo, other.modulo) {
+			crate::utilities::notify!("{:?}", gcd);
+			(self.offset - other.offset) % gcd != 0.
+		} else {
+			crate::utilities::notify!("Here");
+			true
+		}
+	}
+
+	#[must_use]
+	pub fn intersection(self, _other: Self) -> Option<Self> {
+		todo!()
+	}
+
+	#[must_use]
+	pub fn get_cover(self, _other: Self) -> Option<Self> {
+		todo!()
+	}
+
+	#[must_use]
+	pub fn offset(self, offset: BetterF64) -> Self {
+		// TODO temp fix
+		if self.is_default() {
+			self
+		} else {
+			Self::new(self.modulo, self.offset + offset)
+		}
+	}
+
+	#[must_use]
+	pub fn multiply(self, multiple: BetterF64) -> Self {
+		// TODO temp fix
+		if self.is_default() {
+			self
+		} else {
+			Self::new(self.modulo * multiple, self.offset)
+		}
+	}
+
+	#[must_use]
+	pub fn negate(self) -> Self {
+		// TODO temp fix
+		if self.is_default() {
+			self
+		} else {
+			Self::new(self.modulo, self.modulo - self.offset)
+		}
+	}
+
+	#[must_use]
+	pub fn is_default(self) -> bool {
+		self.modulo == f64::EPSILON
+	}
+}
+
+/// Using Farey algoirthm
+/// TODO is there a faster way implemntation
+/// Note that numerator and denominator are coprime
+fn try_get_numerator_denominator(input: BetterF64) -> Result<(i32, i32), ()> {
+	const STEPS: usize = 50;
+	const MARGIN: f64 = 1e-4;
+
+	let integer_part = input.trunc() as i32;
+	let fractional_part = input.fract();
+	if fractional_part == 0. {
+		return Ok((integer_part, 1));
+	}
+
+	let (mut a, mut b, mut c, mut d) = (0, 1, 1, 1);
+
+	for _ in 0..STEPS {
+		let mediant_float = (f64::from(a) + f64::from(b)) / (f64::from(c) + f64::from(d));
+		if (fractional_part - mediant_float).abs() < MARGIN {
+			let numerator = a + b + integer_part * (c + d);
+			let denominator = c + d;
+			return Ok((numerator, denominator));
+		} else if fractional_part > mediant_float {
+			a += b;
+			c += d;
+		} else {
+			b += a;
+			d += c;
+		}
+	}
+
+	Err(())
+}
+
+fn gcd_of_float(n1: BetterF64, n2: BetterF64) -> Result<BetterF64, ()> {
+	fn gcd(mut n1: i32, mut n2: i32) -> i32 {
+		while n2 != 0 {
+			let t = n2;
+			n2 = n1 % n2;
+			n1 = t;
+		}
+		n1
+	}
+
+	/// n1*n2 = gcd(n1, n2)*lcm(n1, n2)
+	fn lcm(n1: i32, n2: i32) -> i32 {
+		(n1 * n2) / gcd(n1, n2)
+	}
+
+	let (a, b) = try_get_numerator_denominator(n1)?;
+	let (c, d) = try_get_numerator_denominator(n2)?;
+
+	// gcd(a / b, c / d) = gcd(a, c) / lcm(b, d)
+	Ok(BetterF64::new(f64::from(gcd(a, c)) / f64::from(lcm(b, d))).unwrap())
+}
+
+// hmmm
+impl Default for ModuloClass {
+	fn default() -> Self {
+		Self { modulo: f64::EPSILON.try_into().unwrap(), offset: BetterF64::new(0.).unwrap() }
+	}
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+
+	// TODO test negatives etc
+	#[test]
+	fn gcd() {
+		assert_eq!(
+			gcd_of_float(BetterF64::new(1. / 3.).unwrap(), BetterF64::new(3. / 2.).unwrap()),
+			Ok(BetterF64::new(0.16666666666666666).unwrap())
+		);
+	}
+}
diff --git a/parser/examples/code_blocks_to_script.rs b/parser/examples/code_blocks_to_script.rs
index 4f0a4fea..4abf44f4 100644
--- a/parser/examples/code_blocks_to_script.rs
+++ b/parser/examples/code_blocks_to_script.rs
@@ -13,6 +13,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 
 	let replace_satisfies_with_as = args.iter().any(|item| item == "--satisfies-with-as");
 	let add_headers_as_comments = args.iter().any(|item| item == "--comment-headers");
+	// let declare_to_function = args.iter().any(|item| item == "--declare-to-function");
 
 	let into_files_directory_and_extension = args.windows(3).find_map(|item| {
 		matches!(item[0].as_str(), "--into-files").then_some((item[1].clone(), item[2].clone()))
@@ -35,7 +36,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 
 	let content = std::fs::read_to_string(&path)?;
 
-	let filters: Vec<&str> = vec!["import", "export", "declare"];
+	let filters: Vec<&str> = vec!["import", "export"];
 
 	let blocks = if path.ends_with(".md") {
 		let mut blocks = Vec::new();
@@ -98,104 +99,193 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 				}
 			}
 		}
-		return Ok(());
-	}
-
-	// Else bundle into one, bound in arrow functions to prevent namespace collision
-	let mut final_blocks: Vec<(HashSet<String>, String)> = Vec::new();
-	for (header, code) in blocks {
-		let module = Module::from_string(code.clone(), Default::default()).map_err(Box::new)?;
-
-		let mut names = HashSet::new();
-
-		let mut visitors = Visitors {
-			expression_visitors: Default::default(),
-			statement_visitors: Default::default(),
-			variable_visitors: vec![Box::new(NameFinder)],
-			block_visitors: Default::default(),
-		};
-
-		module.visit::<HashSet<String>>(
-			&mut visitors,
-			&mut names,
-			&VisitOptions { visit_nested_blocks: false, reverse_statements: false },
-			source_map::Nullable::NULL,
-		);
-
-		// TODO quick fix to also register interface and type alias names to prevent conflicts
-		for item in module.items {
-			match item {
-				StatementOrDeclaration::Declaration(Declaration::TypeAlias(TypeAlias {
-					name: StatementPosition { identifier: VariableIdentifier::Standard(s, _), .. },
-					..
-				})) => {
-					names.insert(s.clone());
+	} else {
+		// Else bundle into one, bound in arrow functions to prevent namespace collision
+		let mut final_blocks: Vec<(HashSet<String>, String)> = Vec::new();
+		for (header, mut code) in blocks {
+			// TODO clone
+			let module = match Module::from_string(code.clone(), Default::default()) {
+				Ok(module) => module,
+				Err(err) => {
+					return Err(From::from(format!("Parse error on {code}: {err:?}")));
 				}
-				StatementOrDeclaration::Declaration(Declaration::Interface(Decorated {
-					on:
-						InterfaceDeclaration {
-							name:
-								StatementPosition {
-									identifier: VariableIdentifier::Standard(s, _), ..
-								},
-							..
-						},
-					..
-				})) => {
-					names.insert(s.clone());
+			};
+
+			let mut names = HashSet::new();
+
+			let mut visitors = Visitors {
+				expression_visitors: Default::default(),
+				statement_visitors: Default::default(),
+				variable_visitors: vec![Box::new(NameFinder)],
+				block_visitors: Default::default(),
+			};
+
+			module.visit::<HashSet<String>>(
+				&mut visitors,
+				&mut names,
+				&VisitOptions { visit_nested_blocks: false, reverse_statements: false },
+				source_map::Nullable::NULL,
+			);
+
+			let mut declare_lets = Vec::new();
+
+			// TODO quick fix to also register interface and type alias names to prevent conflicts
+			for item in &module.items {
+				match item {
+					StatementOrDeclaration::Declaration(Declaration::TypeAlias(TypeAlias {
+						name:
+							StatementPosition { identifier: VariableIdentifier::Standard(s, _), .. },
+						..
+					})) => {
+						names.insert(s.clone());
+					}
+					StatementOrDeclaration::Declaration(Declaration::Interface(Decorated {
+						on:
+							InterfaceDeclaration {
+								name:
+									StatementPosition {
+										identifier: VariableIdentifier::Standard(s, _),
+										..
+									},
+								..
+							},
+						..
+					})) => {
+						names.insert(s.clone());
+					}
+					StatementOrDeclaration::Declaration(Declaration::DeclareVariable(
+						declare_variable,
+					)) => {
+						for declaration in &declare_variable.declarations {
+							declare_lets.push((
+								declaration.name.clone(),
+								declaration.type_annotation.clone(),
+							));
+						}
+					}
+					_ => {}
 				}
-				_ => {}
 			}
-		}
 
-		// If available block add to that, otherwise create a new one
-		if let Some((items, block)) =
-			final_blocks.iter_mut().find(|(uses, _)| uses.is_disjoint(&names))
-		{
-			items.extend(names.into_iter());
-			if add_headers_as_comments {
-				block.push_str("\n\t// ");
-				block.push_str(&header);
-			}
-			for line in code.lines() {
-				block.push_str("\n\t");
-				block.push_str(&line);
-			}
-			// If the block is not terminated, it can change the parsing of the next one
-			if block.ends_with(')') {
-				block.push(';');
-			}
-			block.push('\n');
-		} else {
-			let mut block = String::new();
-			if add_headers_as_comments {
-				block.push_str("\t// ");
-				block.push_str(&header);
+			if !declare_lets.is_empty() {
+				use source_map::{Nullable, Span};
+				let (mut top_level, mut inside) = (Vec::new(), Vec::new());
+				for item in module.items {
+					match item {
+						StatementOrDeclaration::Declaration(Declaration::TypeAlias(
+							TypeAlias { .. },
+						)) => {
+							top_level.push(item);
+						}
+						StatementOrDeclaration::Declaration(Declaration::Interface(
+							Decorated { .. },
+						)) => {
+							top_level.push(item);
+						}
+						StatementOrDeclaration::Declaration(Declaration::DeclareVariable(..)) => {}
+						item => {
+							inside.push(item);
+						}
+					}
+				}
+
+				use ezno_parser::{ast, expressions::operators, functions, Expression, Statement};
+
+				let parameters = declare_lets
+					.into_iter()
+					.map(|(name, type_annotation)| functions::Parameter {
+						visibility: (),
+						name,
+						type_annotation,
+						additionally: None,
+						position: Span::NULL,
+					})
+					.collect();
+
+				let function = Expression::ArrowFunction(ast::ArrowFunction {
+					// TODO maybe async
+					header: false,
+					name: (),
+					parameters: functions::FunctionParameters {
+						parameters,
+						rest_parameter: Default::default(),
+						position: Span::NULL,
+						leading: (),
+					},
+					return_type: None,
+					type_parameters: None,
+					position: Span::NULL,
+					body: ast::ExpressionOrBlock::Block(ast::Block(inside, Span::NULL)),
+				});
+
+				// void is temp fix
+				top_level.push(
+					Statement::Expression(
+						Expression::UnaryOperation {
+							operator: operators::UnaryOperator::Void,
+							operand: Box::new(function),
+							position: Span::NULL,
+						}
+						.into(),
+					)
+					.into(),
+				);
+
+				let module = Module { hashbang_comment: None, items: top_level, span: Span::NULL };
+
+				code = module.to_string(&ezno_parser::ToStringOptions::typescript());
 			}
-			for line in code.lines() {
-				block.push_str("\n\t");
-				block.push_str(&line);
+
+			// If available block add to that, otherwise create a new one
+			if let Some((items, block)) =
+				final_blocks.iter_mut().find(|(uses, _)| uses.is_disjoint(&names))
+			{
+				items.extend(names.into_iter());
+				if add_headers_as_comments {
+					block.push_str("\n\t// ");
+					block.push_str(&header);
+				}
+				for line in code.lines() {
+					block.push_str("\n\t");
+					block.push_str(&line);
+				}
+				// If the block is not terminated, it can change the parsing of the next one
+				if block.ends_with(')') {
+					block.push(';');
+				}
+				block.push('\n');
+			} else {
+				let mut block = String::new();
+				if add_headers_as_comments {
+					block.push_str("\t// ");
+					block.push_str(&header);
+				}
+				for line in code.lines() {
+					block.push_str("\n\t");
+					block.push_str(&line);
+				}
+				block.push('\n');
+				final_blocks.push((names, block));
 			}
-			block.push('\n');
-			final_blocks.push((names, block));
 		}
-	}
 
-	// eprintln!("Generated {:?} blocks", final_blocks.len());
+		// eprintln!("Generated {:?} blocks", final_blocks.len());
 
-	if let Some(out) = out_file {
-		let mut out = std::fs::File::create(out)?;
-		for (_items, block) in final_blocks {
-			writeln!(out, "() => {{\n{block}}};\n")?;
-		}
-	} else {
-		let mut out = std::io::stdout();
-		for (_items, block) in final_blocks {
-			// eprintln!("block includes: {items:?}\n{block}\n---");
-			writeln!(out, "() => {{\n{block}}};\n")?;
+		eprintln!("Bundled into {} functions", final_blocks.len());
+
+		if let Some(out) = out_file {
+			let mut out = std::fs::File::create(out)?;
+			for (_items, block) in final_blocks {
+				writeln!(out, "() => {{\n{block}}};\n")?;
+			}
+		} else {
+			let mut out = std::io::stdout();
+			for (_items, block) in final_blocks {
+				// eprintln!("block includes: {items:?}\n{block}\n---");
+				writeln!(out, "() => {{\n{block}}};\n")?;
+			}
 		}
 	}
-
 	Ok(())
 }
 
diff --git a/parser/src/expressions/arrow_function.rs b/parser/src/expressions/arrow_function.rs
index 043ebc85..e30a80b1 100644
--- a/parser/src/expressions/arrow_function.rs
+++ b/parser/src/expressions/arrow_function.rs
@@ -91,17 +91,19 @@ impl FunctionBased for ArrowFunctionBase {
 		local: crate::LocalToStringInformation,
 	) {
 		// Use shorthand if one parameter with no declared type
-		if let (true, [Parameter { name, .. }]) =
-			(parameters.rest_parameter.is_none(), parameters.parameters.as_slice())
+		if let ([Parameter { name, type_annotation, .. }], None) =
+			(parameters.parameters.as_slice(), &parameters.rest_parameter)
 		{
-			if let VariableField::Name(name, ..) = name.get_ast_ref() {
-				name.to_string_from_buffer(buf, options, local);
-			} else {
-				parameters.to_string_from_buffer(buf, options, local);
+			let is_printing_type_annotation =
+				options.include_type_annotations && type_annotation.is_some();
+			if !is_printing_type_annotation {
+				if let VariableField::Name(name, ..) = name.get_ast_ref() {
+					name.to_string_from_buffer(buf, options, local);
+					return;
+				}
 			}
-		} else {
-			parameters.to_string_from_buffer(buf, options, local);
 		}
+		parameters.to_string_from_buffer(buf, options, local);
 	}
 
 	fn parameter_body_boundary_token_to_string_from_buffer<T: source_map::ToString>(
diff --git a/src/cli.rs b/src/cli.rs
index a62dab08..fbdd1de4 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -121,6 +121,9 @@ pub(crate) struct CheckArguments {
 	/// compact diagnostics
 	#[argh(switch)]
 	pub compact_diagnostics: bool,
+	/// more behavior for numbers
+	#[argh(switch)]
+	pub advanced_numbers: bool,
 	/// maximum diagnostics to print (defaults to 30, pass `all` for all and `0` to count)
 	#[argh(option, default = "MaxDiagnostics::default()")]
 	pub max_diagnostics: MaxDiagnostics,
@@ -262,12 +265,17 @@ pub fn run_cli<T: crate::ReadFromFS, U: crate::WriteToFS>(
 				timings,
 				compact_diagnostics,
 				max_diagnostics,
+				advanced_numbers,
 			} = check_arguments;
 
 			let type_check_options: TypeCheckOptions = if cfg!(target_family = "wasm") {
 				Default::default()
 			} else {
-				TypeCheckOptions { measure_time: timings, ..TypeCheckOptions::default() }
+				TypeCheckOptions {
+					measure_time: timings,
+					advanced_numbers,
+					..TypeCheckOptions::default()
+				}
 			};
 
 			let entry_points = match get_entry_points(input) {
@@ -293,7 +301,7 @@ pub fn run_cli<T: crate::ReadFromFS, U: crate::WriteToFS>(
 						new_debouncer(Duration::from_millis(200), None, tx).unwrap();
 
 					for e in &entry_points {
-						_ = debouncer.watcher().watch(e, notify::RecursiveMode::Recursive).unwrap();
+						debouncer.watcher().watch(e, notify::RecursiveMode::Recursive).unwrap();
 					}
 
 					let _ = run_checker(
@@ -359,7 +367,7 @@ pub fn run_cli<T: crate::ReadFromFS, U: crate::WriteToFS>(
 				source_maps: build_config.source_maps,
 				type_definition_module: build_config.definition_file,
 				// TODO not sure
-				output_path: PathBuf::from(output_path),
+				output_path,
 				other_transformers: None,
 				lsp_mode: false,
 			};
diff --git a/src/playground/main.js b/src/playground/main.js
index 9a7065b5..0aa7f379 100644
--- a/src/playground/main.js
+++ b/src/playground/main.js
@@ -5,7 +5,7 @@ import { defaultKeymap, indentWithTab, toggleLineComment } from "@codemirror/com
 import { parser as jsParser } from "@lezer/javascript";
 import { tags } from "@lezer/highlight";
 import { HighlightStyle, syntaxHighlighting, LanguageSupport, LRLanguage } from "@codemirror/language";
-import { init as init_ezno, check_with_options, get_version } from "ezno";
+import { init as init_ezno, check, get_version } from "ezno";
 
 const diagnosticsEntry = document.querySelector(".diagnostics");
 const editorParent = document.querySelector("#editor");
@@ -65,6 +65,14 @@ if (id) {
 let currentState = null;
 
 const ROOT_PATH = "index.tsx";
+const options = {
+  // Allow partial syntax
+  lsp_mode: true,
+  // For hover
+  store_type_mappings: true,
+  // For showing off
+  number_intrinsics: true
+};
 
 async function setup() {
   await init_ezno();
@@ -74,7 +82,7 @@ async function setup() {
       text = args.state.doc.text.join("\n");
       try {
         const start = performance.now();
-        currentState = check_with_options(ROOT_PATH, (_) => text, { lsp_mode: true, store_type_mappings: true });
+        currentState = check(ROOT_PATH, (_) => text, options);
         const elapsed = performance.now() - start;
         timeOutput.innerText = `Parsed & checked in ${Math.trunc(elapsed)}ms`;