Foundationপ্রথম নীতি থেকে
LEVEL 5লেসন ৭/১৬অ্যাডভান্সড৫৫ মিনিট

Type Checking — checker আসলে একটা ছোট evaluator

Type Checking

AST walk করে প্রতিটা expression-এর type গণনা — signature table, coercion ladder, আর six failing programs-এ যাচাই।

এই লেসন শেষে আপনি পারবেন

  • Type checker-কে একটা abstract evaluator হিসেবে model করে ব্যাখ্যা করতে পারবেন — value-র জায়গায় type compute করা হয়
  • Operator signature table data-ভিত্তিক design করে নতুন operator যোগ করা code-change না table-row বানাতে পারবেন
  • Coercion ladder-এর স্তরগুলো (exact, lossless widening, explicit-only, forbidden) আলাদা করে language policy হিসেবে position নিতে পারবেন
  • Bidirectional checking-এর synth/check দুই mode দিয়ে context-এর expected type propagate করতে পারবেন
  • Hindley-Milner inference আর annotated checking-এর মধ্যে spectrum টানতে পারবেন — annotation-light ভাষা কীভাবे infer করে

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

গত লেসনে প্রতিটা 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 স্পষ্ট:

InterpreterType checker
Walkpost-orderpost-order
LeafNumLit(3) → 3NumLit(3) → int
BinOpoperands-এর value combineoperands-এর type unify
Domaininfinite runtime values{int, bool, string}
Failureexception/crashstructured 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 → bool

Checker-এর 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-এ ভিন্ন জায়গা কেটেছে:

LanguagePolicyConsequence
Rustexact only (implicit কিছু নেই)verbose, zero surprise
Javawidening implicitlong 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 errors

Programming-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 0

Function 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 উঠে আসছে:

NodeChildren typesRule appliedResult
1—literalint
2—literalint
1 \<= 2int, int\< : int×int→boolbool
let ok = ...—init typeenv: ok : bool
ok—env lookupbool
false—literalbool
!falsebool! : bool→boolbool
ok && !falsebool, bool&& : bool×bool→boolbool
if-condboolpositional: needs bool✓ pass
"yes"—literalstr
print(...)strany 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 error
সলিড তীর (↑): synthesized types bottom-up। ড্যাশড তীর (↓): expected-type hints top-down (if-cond bool চায়)। Unification point-এ মিল না হলে structured error।

Signature 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)

নিজে চালিয়ে দেখুন

EXPERIMENT

mypy --strict — real bugs runtime-এর আগে ধরা, clang-এ checked-tree

Linux / macOS — python3; mypy needs pip install· ২৫ মিনিট

ধাপ ১ — 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 misspelled
mypy --strict buggy.py

Output:

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 BinaryOperator
BinaryOperator ... '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 হয়।

নিজে বানান

BUILD IT

ToyLang TypeChecker — signature table + ৬ failing programs

Python · ●●●○○
  1. Lesson 5 AST import; own scope-stack (var → type)
  2. SIGNATURES dict: op → [(left, right, result), ...]
  3. expr(): literals → fixed types; VarRef → env lookup
  4. bin(): signature scan; no match → TypeErr + Unknown poison
  5. Positional checks: if/while cond bool; print arity 1
  6. Const-fold bonus: int+int on two NumLits folds
  7. ৬টা 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.errors

Test 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 passed

Sample rendered diagnostic (demo main দিয়ে):

error[type]: operator '+' not defined for str and int (found str,int) @2:3-2:6

নিজে বাড়ান:

  1. Widening ladder step যোগ করুন: float type + int→float implicit — শুধু signature table আর compatible() change হবে? Verify: logic untouched claim।
  2. Const-fold actually implement করুন: expr() returns (ty, maybe_value) tuple — IR lesson-এর জন্য ready হোন।
  3. !=-এ 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।

বুঝেছেন কি না দেখুন

1

Interpreter আর 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?

প্রয়োগ

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 থাকে।

3

Coercion 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)।

4

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 হতো?

ডিজাইন

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 এটাই আটকায়।

6

HM 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