Type Checking — checker আসলে একটা ছোট evaluator
Type Checking
AST walk করে প্রতিটা expression-এর type গণনা — signature table, coercion ladder, আর six failing programs-এ যাচাই।
আগে এটা বুঝি
গত লেসনে প্রতিটা VarRef এখন একটা declaration-এ bind হয়েছে।
কিন্তু এই program-টা এখনো নিরীহ দেখায়:
let name = "world";
let count = 10;
print(name * count);সব name resolved। তবু name * count অর্থহীন — string-কে integer
দিয়ে গুণ? Programming-languages module-এর type-systems লেসনে
আপনি theory দেখেছেন: static/dynamic typing, soundness, progress-
preservation। আজ সেই theory-র compiler-side machine বানাব।
আর এখানেই এই module-এর সবচেয়ে সুন্দর insight-টা লুকিয়ে: type
checker আসলে একটা interpreter — ঠিক যেমন evaluator expression
চালিয়ে value (7) বের করে, checker একই tree চালিয়ে type
(int) বের করে। Value-domain-এর বদলে একটা ছোট finite domain:
{int, bool, string, error}।
আজকের প্রশ্ন: type-গুলো bottom-up কীভাবে compute হয়, operator- এর ভুল combination catch করার নিয়ম কোথায় লেখা থাকে, আর কোথায় context নিচে থেকে expectation পাঠায়?
মূল ধারণা
Checker = evaluator over the type domain
Interpreter আর checker পাশাপাশি রাখলে symmetry স্পষ্ট:
| Interpreter | Type checker | |
|---|---|---|
| Walk | post-order | post-order |
| Leaf | NumLit(3) → 3 | NumLit(3) → int |
| BinOp | operands-এর value combine | operands-এর type unify |
| Domain | infinite runtime values | {int, bool, string} |
| Failure | exception/crash | structured TypeError |
Domain ছোট বলে সব precompute করা যায় — এবং এটাই static analysis- এর essence: প্রতিটা possible-run-এর property এক run-এ verify।
Operator signature table — rules as data
+ কখন legal? C-র মতো ভাষায় answer একটা table:
+ : int × int → int (arithmetic)
+ : str × str → str (concatenation overload)
- : int × int → int
* : int × int → int
== : T × T → bool (polymorphic, same-type constraint)
< : int × int → bool
&& : bool × bool → boolChecker-এর core loop তখন mechanical:
check_bin(op, l_type, r_type):
for (want_l, want_r, result) in SIGNATURES[op]:
if compatible(l_type, want_l) and compatible(r_type, want_r):
return result # first matching signature wins!
error(f"'{op}' not defined for {l_type} and {r_type}")নতুন operator = নতুন table row। নতুন overload = আরেকটা row। Logic code touch হয় না — এটা গত লেসনের “variant encoding”-এর উল্টো দিকের শিক্ষা: যেটা বাড়তে থাকে সেটা data বানাও।
Compatible — coercion ladder ঢোকে এখানে
compatible(found, wanted) একটা ladder, strict থেকে loose:
Level 0 — exact: found == wanted ✓ always
Level 1 — lossless widening:
int → float (8→64 bit, no loss)
Level 2 — lossy numeric: float → int (truncates!) policy call
Level 3 — explicit-only: anything → anything, cast syntax লাগবে
Forbidden: bool ↔ int, str ↔ number never implicitপ্রতিটা ভাষা ladder-এ ভিন্ন জায়গা কেটেছে:
| Language | Policy | Consequence |
|---|---|---|
| Rust | exact only (implicit কিছু নেই) | verbose, zero surprise |
| Java | widening implicit | long x = int_val fine |
| C | প্রায় সব implicit (integer promotions!) | legendary bug source |
| JavaScript (runtime) | সব কিছু coerce | "1" + 1 == "11" |
ToyLang policy: exact + nothing — +-এর str-overload আছে বলে
mixed-type concat temptation নেই। Decision-টা conservative; পরে
widening যোগ করা backward-compatible, উল্টোটা না।
Positional constraints — context থেকে নিচে
শুধু operators না — জায়গাও চায় নির্দিষ্ট type:
if (COND) → COND : bool (int truthiness ToyLang-এ নেই!)
while (COND) → COND : bool
print(ARG) → ARG : any (arity 1 check)এটাই bidirectional checking-এর seed idea: দুই direction-এ information flow —
synth(e): children থেকে type উঠে আসে (bottom-up)
check(e, T): expected T নিচে নামে, verify করে (top-down hint)if-cond check-mode-এ চলে: expected=bool pass করে নামানো হয়।
Full bidirectional systems (Haskell-ish, TypeScript) এটাই scale
করে — annotations থাকলে check, না থাকলে synth।
Hindley-Milner — annotation-light দিকটা
আমাদের checker প্রতিটা variable-এর type declaration থেকে পায়
(let x = expr → init-এর synthesized type)। কোনো annotation না
লাগে কারণ init সবসময় concrete। কিন্তু identity(x) = x-এর মতো
generic function লিখতে চাইলে? তখন inference: unknown type
variables (α), equations solve (unification), let-polymorphism —
এটাই Hindley-Milner algorithm (Damas-Milner 1982)।
Spectrum-টা মনে রাখুন:
full annotations ←─────── we are here ───────→ full inference
(C-style decls) init-derived types (ML/Haskell HM)
more ceremony less ceremony
better errors magical errorsProgramming-languages-এর formal-semantics লেসনে দেখা typing rules
(Γ ⊢ e : τ) ঠিক এই machinery-র math notation — আমাদের Python
checker ওই rules-এরই executable version।
ভেতরে কী ঘটছে
Const-folding — checker-এর free side-effect
Walk করতে গিয়ে checker দেখে: BinOp("+", NumLit(2), NumLit(3)) —
দুই operand constant! Result 5, type int — compile-time-ই গণনা
সম্ভব। Production compilers এখানে একটা flag mark করে বা সরাসরি fold:
input: let x = 60 * 60 * 24;
checked: type int ✓ AND constant-folded: let x = 86400;Optimization pipeline-এর প্রথম optimization ঠিক এখানে free-তে পাওয়া যায় — dedicated optimizer ছাড়াই। পরের লেসনে IR-level constant propagation pass দেখব; checker-level folding তার cheap preview। Trade-off: folded node-এর span original রাখা (error message original text দেখাক) — implementation detail যেটা ভুললে diagnostics অদ্ভুত হয়।
Type errors as data
Error print করা আর error represent করা আলাদা কাজ:
@dataclass
class TypeErr:
msg: str
span: Span
found: str | None = None # "bool"
expected: str | None = None # "int"
# renderer পরে decide করবে কেমন দেখাবে — CLI? IDE squiggle?Structured fields থাকলে একই error object তিন consumer serve করে: terminal (plain), IDE (squiggle + quick-fix hint), test framework (exact-match assert)। String-concatenated errors এই flexibility পুড়িয়ে ফেলে — আমাদের build-এ তাই fields।
Arity আর call-site checks
ToyLang functions এখনো নেই, কিন্তু print builtin — এর জন্য
call-check pattern preview:
print(1, 2) → arity error: expected 1 arg, got 2
print() → arity error: expected 1 arg, got 0Function types আসলে এভাবে encode হয়: int × str -> bool — signature
table entry-তেই param list। Future lesson-এ function declarations
যোগ হলে এই machinery extend হবে, নতুন concept লাগবে না।
উদাহরণ
Annotated walk — types উপরে উঠছে
Program:
let ok = 1 \<= 2;
if (ok && !false) {
print("yes");
}Post-order walk trace — প্রতিটা node থেকে type উঠে আসছে:
| Node | Children types | Rule applied | Result |
|---|---|---|---|
1 | — | literal | int |
2 | — | literal | int |
1 \<= 2 | int, int | \< : int×int→bool | bool |
let ok = ... | — | init type | env: ok : bool |
ok | — | env lookup | bool |
false | — | literal | bool |
!false | bool | ! : bool→bool | bool |
ok && !false | bool, bool | && : bool×bool→bool | bool |
| if-cond | bool | positional: needs bool | ✓ pass |
"yes" | — | literal | str |
print(...) | str | any accepted, arity 1 | ✓ pass |
একটা error version — if (ok + 1):
Node `ok + 1`: signatures['+'] scan:
(int,int)→int : found bool ✗
(str,str)→str : found bool ✗
→ TypeErr(msg="'+' not defined for bool and int",
span=@2:6-2:11, found="bool")
→ if-cond gets Unknown → derived error suppressed (cascade cut!)লক্ষ্য: এক root cause, এক error — +-এর failure থেকে cond-error
cascade হয়নি, ধন্যবাদ Unknown-poison pattern-কে (গত লেসনের recovery
design)।
ছবিতে
Type flow — দুই direction এক tree-তে
if ─────────────────┐
↑ / ↓ bool (hint) │ then
/ ▼
&& ◀── bool required here
↑/ ↑\
ok !
↑bool ↑\
(env) false : bool
BinOp(&&): left bool ✓ right bool ✓ → synth bool
if-cond: hint bool, synth bool → MATCH, no errorSignature table lookup — এক ছবিতে
op='+' found=(bool, int)
SIGNATURES['+']:
(int, int) → int ── bool≠int ✗
(str, str) → str ── bool≠str ✗
│
▼
no match → TypeError
msg: '+' not defined for bool and int
(found types quoted verbatim — debuggable)নিজে চালিয়ে দেখুন
mypy --strict — real bugs runtime-এর আগে ধরা, clang-এ checked-tree
ধাপ ১ — mypy ইনস্টল (explicitly):
pip install mypy # venv-এ করলে bestধাপ ২ — একটা ছোট file যেটা runtime-এ ভাঙবে, দেখুন checker pre-run ধরে ফেলে:
# buggy.py
def average(xs):
return sum(xs) / len(xs)
scores = [90, 80, "85"] # typo: একটা string ঢুকে গেছে
print(average(scores))
config: dict[str, int] = {"port": 8080}
print(config["prot"]) # another typo: key misspelledmypy --strict buggy.pyOutput:
buggy.py:4: error: Unsupported operand types
for + (int and str) [operator]
buggy.py:4: note: Right operand is of type str
buggy.py:7: error: Value of "config" has incompatible key
type "str"... [dict-item] (strict mode findings vary by version)
Found 2+ errors in 1 file (checked 1 source file)Runtime-এ এই program TypeError crash করত line 4-এ — mypy
সেটা আগেই exact position দিয়ে বলে দিল। এটাই static checking-এর
পুরো value proposition, আর ভেতরের machinery ঠিক আমাদের build-
এর signature-table scan।
ধাপ ৩ — clang: checked tree-তে types attach হওয়া দেখুন:
/* tc.c */
int mix(int a, double b) {
return a + b; /* narrowing! */
}clang -Xclang -ast-dump -fsyntax-only tc.c 2>/dev/null | grep -A2 BinaryOperatorBinaryOperator ... 'double' '+'
|-ImplicitCastExpr ... 'double' \<IntegralToFloating>
| `-DeclRefExpr ... 'int' lvalue ParmVar 'a'
`-DeclRefExpr ... 'double' lvalue ParmVar 'b'পড়ুন: a (int) ঘিরে একটা synthetic ImplicitCastExpr — checker
ladder apply করেছে (int→double widening), tree mutate করেছে। আর
function return type 'double' কিন্তু return a+b ‘double’ —
clang -Wconversion flag দিলে narrowing warning-ও পাবেন:
clang -Wconversion -fsyntax-only tc.c 2>&1
# warning: implicit conversion turns floating-point
# value into integer: 'double' to 'int'Ladder policy flags-এ surface হলো — ঠিক আমরা concept section-এ বলেছি।
Static type checker runtime crash-কে compile-time message-এ convert করে — একই analysis আমরা build-এ বানাচ্ছি, industrial scale-এ; আর clang dump দেখায় checker চলার পরে tree-তে types physically attach হয়।
নিজে বানান
ToyLang TypeChecker — signature table + ৬ failing programs
- Lesson 5 AST import; own scope-stack (var → type)
- SIGNATURES dict: op → [(left, right, result), ...]
- expr(): literals → fixed types; VarRef → env lookup
- bin(): signature scan; no match → TypeErr + Unknown poison
- Positional checks: if/while cond bool; print arity 1
- Const-fold bonus: int+int on two NumLits folds
- ৬টা test program — প্রতিটার exact error assert
toylang/typecheck.py:
"""ToyLang semantic pass 2: type checking.
Needs: toylang/ast.py (lesson 5).
"""
from dataclasses import dataclass
from toylang.ast import (Program, LetStmt, Print, IfStmt,
WhileStmt, ExprStmt, NumLit, StrLit,
BoolLit, VarRef, BinOp, UnaryOp)
@dataclass
class TypeErr:
msg: str
line: int
col: int
end_col: int
found: str | None = None
def render(self):
at = f"@{self.line}:{self.col}-{self.end_col}"
extra = f" (found {self.found})" if self.found else ""
return f"error[type]: {self.msg}{extra} {at}"
UNKNOWN = "?" # poison pill after an error
# ---- operator signature table: op -> [(l, r, result)] ----------
SIGNATURES = {
"plus": [("int", "int", "int"),
("str", "str", "str")],
"minus": [("int", "int", "int")],
"star": [("int", "int", "int")],
"slash": [("int", "int", "int")],
"eqeq": [("T", "T", "bool")], # T = same-type
"bangeq": [("T", "T", "bool")],
"lt": [("int", "int", "bool")],
"gt": [("int", "int", "bool")],
"lte": [("int", "int", "bool")],
"gte": [("int", "int", "bool")],
"andand": [("bool", "bool", "bool")],
"oror": [("bool", "bool", "bool")],
}
UNARY_SIG = {"neg": [("int", "int")],
"not": [("bool", "bool")]}
def _span(n):
return n.span.line, n.span.col, n.span.end_col
class TypeChecker:
"""A tiny evaluator: computes TYPES instead of values."""
def __init__(self):
self.scopes = [{}]
self.errors = []
# ---- environment ----------------------------------------------
def _push(self): self.scopes.append({})
def _pop(self): self.scopes.pop()
def _bind(self, name, ty):
self.scopes[-1][name] = ty
def _lookup(self, name):
for sc in reversed(self.scopes):
if name in sc:
return sc[name]
return UNKNOWN # resolver's job really; be lenient
def _err(self, msg, node, found=None):
l, c, e = _span(node)
self.errors.append(TypeErr(msg, l, c, e, found))
# ---- statements ---------------------------------------------------
def check(self, prog: Program):
for s in prog.stmts:
self.stmt(s)
def stmt(self, s):
if isinstance(s, LetStmt):
ty = self.expr(s.init)
self._bind(s.name, ty)
elif isinstance(s, Print):
self.expr(s.arg) # any type, arity 1
elif isinstance(s, ExprStmt):
self.expr(s.expr)
elif isinstance(s, IfStmt):
self._need_bool(s.cond, "if condition")
self._block(s.then)
if s.else_ is not None:
self._block(s.else_)
elif isinstance(s, WhileStmt):
self._need_bool(s.cond, "while condition")
self._block(s.body)
def _need_bool(self, e, where):
"""Checking mode: expected 'bool' flows down from context."""
ty = self.expr(e)
if ty != "bool" and ty != UNKNOWN:
self._err(f"{where} must be bool", e, found=ty)
def _block(self, stmts):
self._push()
for s in stmts:
self.stmt(s)
self._pop()
# ---- expressions: the little evaluator --------------------------
# (synth mode: type rises from children; positional checks like
# if-cond are the checking mode — hint flows down from context)
def expr(self, e) -> str:
if isinstance(e, NumLit):
return "int"
if isinstance(e, StrLit):
return "str"
if isinstance(e, BoolLit):
return "bool"
if isinstance(e, VarRef):
return self._lookup(e.name)
if isinstance(e, UnaryOp):
got = self.expr(e.operand)
for want, res in UNARY_SIG[e.op]:
if got == want:
return res
self._err(f"unary '{e.op}' not defined "
f"for {got}", e, found=got)
return UNKNOWN
if isinstance(e, BinOp):
return self.bin(e)
raise AssertionError(type(e))
def bin(self, e: BinOp) -> str:
lt = self.expr(e.left)
rt = self.expr(e.right)
if lt == UNKNOWN or rt == UNKNOWN:
return UNKNOWN # cascade suppression
# const-folding opportunity noticed HERE (free preview!)
if (isinstance(e.left, NumLit)
and isinstance(e.right, NumLit)):
pass # IR lesson will use this
for wl, wr, res in SIGNATURES[e.op]:
ok_l = wl == "T" or lt == wl
ok_r = wr == "T" or rt == wr
if ok_l and ok_r and (
wl != "T" or lt == rt): # same-type for T
return res
self._err(f"operator '{e.op}' not defined "
f"for {lt} and {rt}", e, found=f"{lt},{rt}")
return UNKNOWN
def run_checker(src_ast: Program):
c = TypeChecker()
c.check(src_ast)
return c.errorsTest harness — ছয়টা failing program:
def _test():
from toylang.ast import (Span, NumLit, StrLit, BoolLit,
VarRef, BinOp, UnaryOp, LetStmt,
Print, IfStmt, WhileStmt)
def num(v, l=1, c=1):
return NumLit(Span(l, c, c + len(str(v))), v)
def var(n, l=1, c=1):
return VarRef(Span(l, c, c + len(n)), n)
# explicit-span BinOp helper (spans rough is fine for tests)
def B(op, l_, r_, ln=1, c=1):
return BinOp(Span(ln, c, c + 3), op, l_, r_)
def errs(prog):
es = run_checker(prog)
return [e.msg for e in es]
# 1. if-condition is int, not bool
p = Program((IfStmt(Span(1, 1, 12),
B("plus", num(1), num(2)), (), None),))
assert any("must be bool" in m for m in errs(p))
# 2. arithmetic on bool
p = Program((ExprStmt(Span(1, 1, 14),
B("plus", BoolLit(Span(1, 1, 5), True),
BoolLit(Span(1, 9, 13), False))),))
assert any("'+' not defined for bool and bool" in m
for m in errs(p))
# 3. == across different types
p = Program((ExprStmt(Span(1, 1, 10),
B("eqeq", num(1),
BoolLit(Span(1, 6, 9), True))),))
assert any("'==' not defined for int and bool" in m
for m in errs(p))
# 4. mixed + (str, int)
p = Program((LetStmt(Span(1, 1, 20), "greet",
StrLit(Span(1, 12, 18), "hi")),
ExprStmt(Span(2, 1, 22),
B("plus", var("greet"), num(5)))),)
assert any("'+' not defined for str and int" in m
for m in errs(p))
# 5. while-condition is str
p = Program((WhileStmt(Span(1, 1, 16),
StrLit(Span(1, 8, 10), "x"), ()),))
assert any("while condition must be bool" in m
for m in errs(p))
# 6. unary ! on int
p = Program((ExprStmt(Span(1, 1, 5),
UnaryOp(Span(1, 1, 4), "not",
num(42))),))
assert any("unary 'not' not defined for int" in m
for m in errs(p))
print("all 6 type-error tests passed")
if __name__ == "__main__":
_test()Expected:
$ python3 toylang/typecheck.py
all 6 type-error tests passedSample rendered diagnostic (demo main দিয়ে):
error[type]: operator '+' not defined for str and int (found str,int) @2:3-2:6নিজে বাড়ান:
- Widening ladder step যোগ করুন:
floattype +int→floatimplicit — শুধু signature table আর compatible() change হবে? Verify: logic untouched claim। - Const-fold actually implement করুন:
expr()returns(ty, maybe_value)tuple — IR lesson-এর জন্য ready হোন। !=-এ structural rule ভাঙুন deliberately (T-constraint বাদ) — কোন new program এখন fail করে সেটা observe করুন।
বাস্তব সিস্টেমে
Industrial type checkers
rustc typeck। Trait solving-সহ constraint system — signature table-এর generalization: impls খুঁজে satisfaction prove করে। Error messages (“the trait `Display` is not implemented”) — structured errors আমাদের TypeErr-এর scaled version।
GHC। Constraint solver + unification-এর academic-strength implementation; type families, GADTs — HM-এর extensions stack। Error messages ঐতিহাসিকভাবে cryptic ছিল — recent GHC-রা spans + hints দিয়ে improve করছে, ঠিক আমাদের structured-fields argument।
TypeScript। Structural typing-এর বিশাল implementation — millions of LOC checker itself TS-এ লেখা। Incremental checking (watch mode) এখানে make-or-break feature।
mypy/pyright। Same language, দুই checker, দুই performance philosophy (pyright: parallel + fast; mypy: mature plugin ecosystem)। Python-এর gradual typing — ladder-এ “unknown” level-এর live study।
CompCert। Verified C compiler-এর type preservation proof — checker correct তা math-এ proved। Formal-semantics lesson-এর soundness theorem-এর industrial existence proof।
যে ভুলগুলো সবাই করে
“Type checker program চালায়।”
Values compute করে না — শুধু classification। while(true){} checker-
এ চিরকাল ঘোরে না; 1/0 division-by-zero checker ধরে না (value-
level fact, type-level না — কিছু verifier ধরে, সেটা separate
analysis)। Domain ছোট finite — এটাই speed-এর secret।
“Static typing মানে সব জায়গায় annotation লিখতে হবে।”
Init-derived inference (আমাদের ToyLang) already annotation-free variables পায়; HM পুরো program infer করে annotations ছাড়াই (ML decades-এ এভাবেই চলে)। Spectrum-টা ceremony-vs-errors trade-off, binary choice না।
“Implicit conversion developer-friendly convenience।”
Convenience-এর দাম: JS-এর == chaos, C-র promotion bugs, SQL-র
silent index-bypassing casts। Modern languages (Rust, Go, Swift)
deliberately strict — IDE-এ .into()/cast one-keypress হলে implicitness
এর value প্রায় শূন্য, আর bug-class elimination এর price বিশাল।
“'==' সব type নেয়, তাই polymorphic মানে unconstrained।”
আমাদের T × T rule-ই counter-example: যেকোনো type চলে কিন্তু
দুই পাশ এক হতে হবে। Polymorphism ≠ absence-of-constraint —
constraint হয় parameterized। 1 == true reject করা এই single
rule-এর ফল।
“Type error মানে string print করে exit।”
Stringly-typed errors testability ধ্বংস করে (assert substring fragile) আর IDE integration অসম্ভব করে। Structured data (msg + span + found/ expected) — এক capture, বহু render। আমাদের build-এর TypeErr dataclass minimal viable form।
বুঝেছেন কি না দেখুন
1Interpreter আর type checker-এর structural পার্থক্য ক’টা জায়গায়?
Table করুন।
স্মরণ
Same AST, same post-order walk। Difference: leaf mapping (value বনাম type-literal), combine operation (runtime op বনাম signature unify), domain (values বনাম finite types), failure mode (exception বনাম structured TypeError)। Structure identical — এটাই “checker is a tiny evaluator” thesis।
2"abc" + "def" আর "abc" + 1 — signature table-এ প্রতিটার journey?
প্রয়োগ
"abc" + "def" আর "abc" + 1 — signature table-এ প্রতিটার journey?First: scan + rows — (int,int)? no; (str,str)? yes → str ✓। Second:
(int,int) no; (str,str) — right is int ✗ → no match → TypeErr ”’+’
not defined for str and int”। Overload resolution first-match-wins;
ordering matters যদি overlapping sigs থাকে।
3Coercion ladder-এ int→float Level-1 কিন্তু float→int Level-2/3।
Asymmetry-টার technical justification কী?
যুক্তি
Losslessness: every int exactly representable in double (২⁵৩ পর্যন্ত); reverse-এ truncation (3.7→3) বা precision loss — silent value change। Soundness principle: implicit conversions allowed শুধু information- preserving direction-এ; destructive direction explicit syntax চায় (programmer acknowledges)।
4if (x) যেখানে x:int — C allow করে, ToyLang না। দুই policy-র
argument দুই-dিকে দিন।
প্রয়োগ
if (x) যেখানে x:int — C allow করে, ToyLang না। দুই policy-র
argument দুই-dিকে দিন।C-pro: terse idiom, if(ptr) null-check natural। Anti: if(x = 5)
assignment-as-truthy bug family, intent ambiguity (0 valid value না
sentinel?)। Strict-pro: intent explicit (if (x != 0)), typo-catch;
Anti: verbosity। Language-design values decision — mechanical
either way (one positional rule)।
5আপনার checker-এ cascade suppression কোন line-এ? ওটা বাদ দিলে
undeclared x + 1-এ কী chain হতো?
ডিজাইন
undeclared x + 1-এ কী chain হতো?Line: if lt == UNKNOWN or rt == UNKNOWN: return UNKNOWN। বাদ দিলে:
resolver-lenient _lookup UNKNOWN দেয় → signature scan fails
(int/? matches না) → extra ”’+’ not defined for ? and int” error →
result UNKNOWN propagates anyway but noise doubled। Root cause এক,
symptom দুই — suppression এটাই আটকায়।
6HM inference-এ আমাদের checker-এর কোন অংশ replace হবে, কোন অংশ
থাকবে, যদি annotations পুরো বাদ দিতে হয়?
যুক্তি
Replace: variable binding — declaration-time concrete type-এর বদলে fresh type-variable (α) + constraints collect। Thakbe: signature table (constraints-ই তো!), walk order, error infrastructure। যোগ হবে: unification solver + generalization at let। Conceptually checker-ই evaluator — শুধু domain হয়ে যায় type-expressions।
এরপর কী
এরপর কী
Semantic layer complete: names bound, types verified। Tree-টা এখন fully validated — কিন্তু CPU জানে না tree কী। পরের লেসনে Intermediate Representation:
- Three-address code: tree-কে flat instruction list-এ flatten
- SSA form: প্রতিটা variable once-assigned, φ-nodes at joins
- LLVM IR walkthrough — real .c থেকে -O0 বনাম -O2 diff
- আমাদের প্রথম lowering: AST → TAC with temporaries + labels
Pipeline-এর frontend শেষ। Middle-end-এর দরজা খুলছে।
আরও পড়ুন
- "Engineering a Compiler" (2nd ed.), Section 4.4 — Type Inference — Keith Cooper, Linda Torczon · Bottom-up type computation-এর engineering treatment — আমাদের checker-এর structure
- Modern Compiler Implementation (ML ed.), Chapter 5 — Semantic Analysis (type sections) — Andrew W. Appel · ML-family unification-এর practical introduction — HM-এর bridge
- Compilers: Principles, Techniques, and Tools (2nd ed.), Section 6.x — Types and Checking — Aho, Sethi, Ullman, Lam (the Dragon Book) · Type expressions, equivalence আর checking rules-এর formal frame