packages feed

canontra-0.1.0.0: src/Canontra/Fingerprint/TypeContract.hs

{- |
Module      : Canontra.Fingerprint.TypeContract
Description : Deterministic structural type contract fingerprint (F_T) computation.

Computes the 9th orthogonal fingerprint tier (F_T) capturing structural interface contracts,
method signatures, and subtyping relationships. Invariant under interface nominal renaming,
method declaration reordering, and union/intersection permutations.
-}
{-# LANGUAGE OverloadedStrings #-}
module Canontra.Fingerprint.TypeContract
  ( computeFT
  , serializeTypeContracts
  ) where

import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import Data.List (sortBy)
import Data.Ord (comparing)
import qualified Data.Text.Encoding as TE

import Canontra.Analysis.TypeContract
  ( InterfaceContract (..)
  , MethodContract (..)
  , StructuralType (..)
  , extractTypeContracts
  )
import Canontra.Fingerprint.Source (hashBytes)
import Canontra.IR.Program (Program)
import Canontra.Types (Fingerprint)

-- | Compute the structural type contract fingerprint (F_T) for a Program.
computeFT :: Program -> Fingerprint
computeFT prog =
  let contracts = extractTypeContracts prog
      bytes = serializeTypeContracts contracts
  in hashBytes bytes

-- | Deterministically serialize a list of InterfaceContracts into a canonical byte sequence.
serializeTypeContracts :: [InterfaceContract] -> BS.ByteString
serializeTypeContracts rawContracts =
  let sortedContracts = sortBy (comparing (\c -> (icFields c, icMethods c))) rawContracts
      builder = BB.word8 0x0A <> BB.word32BE (fromIntegral (length sortedContracts)) <> foldMap serializeContract sortedContracts
  in BL.toStrict (BB.toLazyByteString builder)
  where
    serializeContract c =
      BB.word32BE (fromIntegral (length (icMethods c)))
        <> foldMap serializeMethod (icMethods c)
        <> BB.word32BE (fromIntegral (length (icFields c)))
        <> foldMap serializeField (icFields c)

    serializeMethod m =
      let nameBytes = TE.encodeUtf8 (mcName m)
      in BB.word32BE (fromIntegral (BS.length nameBytes))
          <> BB.byteString nameBytes
          <> BB.word8 (if mcIsAsync m then 1 else 0)
          <> BB.word32BE (fromIntegral (length (mcParams m)))
          <> foldMap serializeType (mcParams m)
          <> serializeType (mcReturn m)

    serializeField (name, ty) =
      let nameBytes = TE.encodeUtf8 name
      in BB.word32BE (fromIntegral (BS.length nameBytes))
          <> BB.byteString nameBytes
          <> serializeType ty

    serializeType = \case
      TypePrimitive p ->
        let b = TE.encodeUtf8 p
        in BB.word8 0x01 <> BB.word32BE (fromIntegral (BS.length b)) <> BB.byteString b
      TypeRecord fields ->
        BB.word8 0x02 <> BB.word32BE (fromIntegral (length fields)) <> foldMap serializeField fields
      TypeFunction params ret ->
        BB.word8 0x03 <> BB.word32BE (fromIntegral (length params)) <> foldMap serializeType params <> serializeType ret
      TypeArray elemTy ->
        BB.word8 0x04 <> serializeType elemTy
      TypeUnion members ->
        BB.word8 0x05 <> BB.word32BE (fromIntegral (length members)) <> foldMap serializeType members
      TypeIntersection members ->
        BB.word8 0x06 <> BB.word32BE (fromIntegral (length members)) <> foldMap serializeType members
      TypeOptional inner ->
        BB.word8 0x07 <> serializeType inner
      TypeGeneric name args ->
        let b = TE.encodeUtf8 name
        in BB.word8 0x08 <> BB.word32BE (fromIntegral (BS.length b)) <> BB.byteString b <> BB.word32BE (fromIntegral (length args)) <> foldMap serializeType args