# (c) 2021 Ashok P. Nadkarni
# See LICENSE for license terms.
#
# Tests for the cffi::type command

source [file join [file dirname [info script]] common.tcl]

namespace eval cffi::test {
    variable typeInfo
    # Size and alignment
    array set typeInfo \
        [list \
             void {0 0} \
             schar {1 1} \
             uchar {1 1} \
             short {2 2} \
             ushort {2 2} \
             int {4 4} \
             uint {4 4} \
             long [list $::tcl_platform(wordSize) $::tcl_platform(wordSize)] \
             ulong [list $::tcl_platform(wordSize) $::tcl_platform(wordSize)] \
             longlong {8 8} \
             ulonglong {8 8} \
             float {4 4} \
             double {8 8} \
             pointer [list $::tcl_platform(pointerSize) $::tcl_platform(pointerSize)] \
             string [list $::tcl_platform(pointerSize) $::tcl_platform(pointerSize)] \
             unistring [list $::tcl_platform(pointerSize) $::tcl_platform(pointerSize)] \
             binary [list $::tcl_platform(pointerSize) $::tcl_platform(pointerSize)] \
             chars {1 1} \
             unichars {2 2} \
             bytes {1 1} \
            ]

    testnumargs type-size "cffi::type size" "TYPE"
    testnumargs type-count "cffi::type count" "TYPE"
    testnumargs type-info "cffi::type info" "TYPE" "?PARSEMODE?"

    proc basictypetest {typedecl} {
        variable typeInfo
        variable errorMessages
        set testlabel [join $typedecl -]
        set typeattrs [lassign $typedecl type]
        lassign $typeInfo($type) size alignment
        # Scalar tests
        if {$type ni {chars unichars bytes}} {
            test type-info-$testlabel-0 "type info $typedecl" -body {
                cffi::type info $typedecl
            } -result [list Size $size Count -1 Alignment $alignment Definition $typedecl BaseSize $size] -match dict

            test type-size-$testlabel-0 "type size $typedecl" -body {
                cffi::type size $typedecl
            } -result $size

            test type-count-$testlabel-0 "type count $typedecl" -body {
                cffi::type count $typedecl
            } -result -1

        } else {
            # chars, unichars, bytes
            test type-info-$testlabel-1 "type info $typedecl" -body {
                cffi::type info $typedecl
            } -result "Invalid value \"$type\". $errorMessages(scalarchars)" -returnCodes error

            test type-size-$testlabel-0 "type size $typedecl" -body {
                cffi::type size $typedecl
            } -result "Invalid value \"$type\". $errorMessages(scalarchars)" -returnCodes error

            test type-count-$testlabel-0 "type count $typedecl" -body {
                cffi::type count $typedecl
            } -result "Invalid value \"$type\". $errorMessages(scalarchars)" -returnCodes error

        }

        # Array tests
        if {$type ni {struct.TypeTestStruct  binary void}} {

            test type-info-$testlabel-array-1 "type info $typedecl array" -body {
                cffi::type info "$type\[10\] $typeattrs"
            } -result [list Size [expr {10 * $size}] Count 10 Alignment $alignment Definition [list "$type\[10\]" {*}$typeattrs] BaseSize $size] -match dict

            test type-size-$testlabel-array-1 "type size $typedecl array" -body {
                cffi::type size "$type\[10\] $typeattrs"
            } -result [expr {10 * $size}]

            test type-count-$testlabel-array-1 "type count $typedecl array" -body {
                cffi::type count "$type\[10\] $typeattrs"
            } -result 10

        } else {

            test type-info-$testlabel-array-1 "type info $typedecl array" -body {
                cffi::type info "$type\[10\] $typeattrs"
            } -result "Invalid value \"$type\[10\]\". $errorMessages(unsupportedarraytype)" -returnCodes error

            test type-size-$testlabel-array-1 "type size $typedecl array" -body {
                cffi::type size "$type\[10\] $typeattrs"
            } -result "Invalid value \"$type\[10\]\". $errorMessages(unsupportedarraytype)" -returnCodes error

            test type-count-$testlabel-array-1 "type count $typedecl array" -body {
                cffi::type count "$type\[10\] $typeattrs"
            } -result "Invalid value \"$type\[10\]\". $errorMessages(unsupportedarraytype)" -returnCodes error

        }

    }

    testsubcmd ::cffi::type

    # Tests for basic types - no parse mode specified
    basictypetest void
    basictypetest schar
    basictypetest uchar
    basictypetest short
    basictypetest ushort
    basictypetest int
    basictypetest uint
    basictypetest long
    basictypetest ulong
    basictypetest longlong
    basictypetest ulonglong
    basictypetest float
    basictypetest double
    basictypetest pointer
    basictypetest string
    basictypetest unistring
    basictypetest binary
    basictypetest chars
    basictypetest unichars
    basictypetest bytes


    proc testinvalidattr {typedecl parse_mode expected_message args} {
        variable typeInfo

        if {"lasterror" in $typedecl || "winerror" in $typedecl} {
            set constraints win
        } else {
            set constraints {}
        }
        set testlabel type-info-invalidattr-[join $typedecl -]-$parse_mode

        test $testlabel-0 {
            type info param, $typedecl invalid attr
        } -constraints $constraints -body {
            cffi::type info $typedecl $parse_mode
        } -result "Invalid value \"$typedecl\". $expected_message" -returnCodes error {*}$args
    }

    # Tests for numeric types with parse mode specified

    proc testnumeric {typedecl parse_mode expected_typeattrs args} {
        variable typeInfo
        set typeattrs [lassign $typedecl type]
        lassign $typeInfo($type) size alignment
        set testlabel type-info-[join $typedecl -]
        # Scalar test for each parse mode
        test $testlabel-$parse_mode-0 "type info $parse_mode, $typedecl" -body {
            cffi::type info $typedecl $parse_mode
        } -result [list Size $size Count -1 Alignment $alignment Definition [list $type {*}$expected_typeattrs] BaseSize $size] -match dict {*}$args
    }

    proc testnumericarray {typedecl count parse_mode expected_typeattrs args} {
        variable typeInfo
        variable errorMessages
        set typeattrs [lassign $typedecl type]
        lassign $typeInfo($type) size alignment
        set testlabel type-info-[join $typedecl -]

        set basesize $size
        if {$parse_mode eq "param"} {
            if {[string is integer -strict $count]} {
                set size [expr {$size * $count}]
            } else {
                set size 0
            }
            test $testlabel-array$count-$parse_mode-0 "type info $parse_mode, $typedecl array" -body {
                cffi::type info "$type\[$count\] $typeattrs" $parse_mode
            } -result [list Size $size Count $count Alignment $alignment Definition [list "$type\[$count\]" {*}$expected_typeattrs] BaseSize $basesize] -match dict {*}$args
        } elseif {$parse_mode eq "field"} {
            # Only fixed size array element counts allowed
            if {[string is integer -strict $count]} {
                test $testlabel-array$count-$parse_mode-0 "type info $parse_mode, $typedecl array" -body {
                    cffi::type info "$type\[$count\] $typeattrs" $parse_mode
                } -result [list Size [expr {$count * $size}] Count $count Alignment $alignment Definition [list "$type\[$count\]" {*}$expected_typeattrs] BaseSize $basesize] -match dict {*}$args
            } else {
                test $testlabel-array$count-$parse_mode-0 "type info $parse_mode, $typedecl array" -body {
                    cffi::type info "$type\[$count\] $typeattrs" $parse_mode
                } -result "Invalid value \"$type\[$count\]\". $errorMessages(arraysize)" -returnCodes error {*}$args
            }
        } else {
            # parse mode = return
            test $testlabel-array$count-$parse_mode-0 "type info $parse_mode, $typedecl array" -body {
                cffi::type info "$type\[$count\] $typeattrs" $parse_mode
            } -result "Invalid value \"$type\[$count\]\". $errorMessages(arraysize)" -returnCodes error {*}$args
        }
    }

    ###############################
    # Test categorization:
    #   Data type -> parse mode -> success / fail -> scalar / array
    # Failure tests are further broken down as
    #   invalid attribute, invalid attribute combination, invalid attribute
    #   for type, invalid attribute for parse mode, invalid array size

    ###
    ### Invalid type tests

    test info-type-invalidtype-0 "Invalid type in declaration" -body {
        cffi::type info nosuchtype
    } -result "Invalid value \"nosuchtype\". Invalid base type." -returnCodes error

    test info-type-invalidtype-1 "Empty type declaration" -body {
        cffi::type info [list ]
    } -result "Invalid value \"\". Empty type declaration." -returnCodes error

    test info-type-invalidtype-2 "Empty type in type declaration" -body {
        cffi::type info [list ""]
    } -result "Invalid value \"\". Invalid base type." -returnCodes error


    ###
    ### void type

    foreach type $voidTypes {
        ### Parse mode = param
        test info-type-void-param-0 "void as parameter" -body {
            cffi::type info void param
        } -result "Invalid value \"void\". $errorMessages(typeparsemode)" -returnCodes error

        ### Parse mode = return
        test info-type-void-return-0 "void as return type" -body {
            cffi::type info void return
        } -result [list Size 0 Count -1 Alignment 0 Definition void BaseSize 0]
        test info-type-void-array-return-0 "void array as return type" -body {
            cffi::type info {void[2]} param
        } -result "Invalid value \"void\[2\]\". $errorMessages(unsupportedarraytype)" -returnCodes error
        foreach attr $typeAttrs {
            testinvalidattr "void $attr" return $errorMessages(attrtype)
        }

        ### Parse mode = field
        test info-type-void-field-0 "void as field" -body {
            cffi::type info void field
        } -result "Invalid value \"void\". $errorMessages(typeparsemode)" -returnCodes error
    }

    ### Numeric type tests
    foreach type $numericTypes {
        ###
        ### Parse mode = param

        # Scalar tests - success
        foreach {attr expected} {
            ""      "in"
            "in"    "in"
            "out"   "out byref"
            "inout" "inout byref"
            "byref" "in byref"
            "in byref"       "in byref"
            "byref out"      "out byref"
            "inout byref"    "inout byref"
            "{default 1}"    "in {default 1}"
            "{default 1} in" "in {default 1}"
            "out storeonerror" "out byref storeonerror"
            "out storealways" "out byref storealways"
            "inout storeonerror" "inout byref storeonerror"
            "inout storealways" "inout byref storealways"
            "retval" "out retval byref"
            "retval out" "out retval byref"
        } {
            testnumeric "$type $attr" param $expected
        }
        foreach {attr expected} {
            "in errno" "in"
            "in {onerror handler}" "in"
            "out errno" "out byref"
            "out {onerror handler}" "out byref"
            "inout errno" "inout byref"
            "inout {onerror handler}" "inout byref"
        } {
            if {$type in $intTypes} {
                testnumeric "$type $attr" param $expected
            } else {
                testinvalidattr "$type $attr" param $errorMessages(attrtype)
            }
        }
        foreach {attr expected} {
            "in lasterror" "in"
            "in winerror" "in"
            "out lasterror" "out byref"
            "out winerror" "out byref"
            "inout lasterror" "inout byref"
            "inout winerror" "inout byref"
        } {
            if {$type in $intTypes} {
                testnumeric "$type $attr" param $expected -constraints win
            } else {
                testinvalidattr "$type $attr" param $errorMessages(attrtype) -constraints win
            }
        }

        # enum, bitmask annotations
        foreach {attr expected} {
            "" "in"
            "in" "in"
            "inout" "inout byref"
            "out" "out byref"
        } {
            if {$type in $intTypes} {
                testnumeric "$type $attr {enum E}" param "$expected {enum {I 1}}" -setup {
                    cffi::enum delete *
                    cffi::enum define E {I 1}
                }
                testnumeric "$type $attr {enum {I 1 J 2}}" param "$expected {enum {I 1 J 2}}"
            } else {
                testinvalidattr "$type $attr {enum E}" param $errorMessages(attrtype)
            }
            testinvalidattr "$type $attr enum" param {A type annotation has the wrong number of fields.}
        }

        # bitmask annotations
        foreach {attr expected} {
            "" "in"
            "in" "in"
            "inout" "inout byref"
            "out" "out byref"
        } {
            if {$type in $intTypes} {
                testnumeric "$type $attr bitmask" param "$expected bitmask"
            } else {
                testinvalidattr "$type $attr bitmask" param $errorMessages(attrtype)
            }
        }

        foreach {attr expected} {
            "" "in byref"  "in"  "in byref" "out" "out byref" "inout" "inout byref" "byref" "in byref"
            "in byref" "in byref" "byref out" "out byref" "inout byref" "inout byref"
            "{default 1}"    "in byref {default 1}"
            "{default 1} in" "in byref {default 1}"
            "out storeonerror" "out byref storeonerror"
            "out storealways" "out byref storealways"
            "inout storeonerror" "inout byref storeonerror"
            "inout storealways" "inout byref storealways"
        } {
            # Single element array
            testnumericarray "$type $attr" 1 param $expected
            # Multiple elements
            testnumericarray "$type $attr" 10 param $expected
            # Named size
            testnumericarray "$type $attr" NUMELEMS param $expected
        }

        # enum and bitmask arrays
        foreach {attr expected} {
            "" "in"
            "in" "in"
            "inout" "inout"
        } {
            if {$type in $intTypes} {
                catch {cffi::enum delete E}
                cffi::enum define E {I 1}
                testnumericarray "$type $attr byref {enum E}" 10 param "$expected byref {enum {I 1}}"
                testnumericarray "$type $attr byref bitmask" 10 param "$expected byref bitmask"
            } else {
                # Already tested above for scalars
                #testinvalidattr "$type $attr {enum E}" param $errorMessages(attrtype)
                #testinvalidattr "$type $attr bitmask" param $errorMessages(attrtype)
            }
        }

        # Failure tests - invalid attribute combinations
        foreach attra {in out inout} {
            foreach attrb {in out inout} {
                if {$attra ne $attrb} {
                    testinvalidattr "$type $attra $attrb" param $errorMessages(attrconflict)
                    testinvalidattr "$type\[2\] $attra $attrb" param $errorMessages(attrconflict)
                }
            }
        }
        testinvalidattr "$type in retval" param $errorMessages(attrconflict)
        testinvalidattr "$type in byref retval" param $errorMessages(attrconflict)
        testinvalidattr "$type inout byref retval" param $errorMessages(attrconflict)

        foreach attra {out inout} {
            testinvalidattr "$type $attra {default 0}" param $errorMessages(defaultdisallowed)
        }
        foreach attr {storeonerror storealways} {
            testinvalidattr "$type in $attr" param $errorMessages(store)
        }
        testinvalidattr "$type out storeonerror storealways" param $errorMessages(attrconflict)
        testinvalidattr "$type inout storeonerror storealways" param $errorMessages(attrconflict)

        # Scalar tests - invalid attributes for type
        foreach attr [list {*}$pointerAttrs nullifempty] {
            testinvalidattr "$type $attr" param $errorMessages(attrtype)
            if {$attr eq "nullok"} continue
            testinvalidattr "$type\[2\] $attr" param $errorMessages(attrtype)
        }
        # Scalar tests - attributes invalid for param parse mode
        foreach attr $requirementAttrs {
            if {$type in $intTypes} {
                testinvalidattr "$type $attr" param $errorMessages(attrparsemode)
                testinvalidattr "$type\[2\] $attr" param $errorMessages(attrparsemode)
            }
        }
        if {$type in $intTypes} {
            testinvalidattr "$type structsize" param $errorMessages(attrparsemode)
        } else {
            testinvalidattr "$type structsize" param $errorMessages(attrtype)
        }

        # array invalid count spec failure
        set l {[0] [-2] [] [2x] \[2 [2]x}
        foreach count $l  {
            testinvalidattr "$type$count" param "$errorMessages(arraysize)"
        }

        ### Parse mode = return

        # Scalar tests - success
        testnumeric $type return ""
        if {$type in $intTypes} {
            foreach attr [list byref {*}$errorAttrs] {
                testnumeric "$type $attr" return $attr
            }
            catch {cffi::enum delete E}
            cffi::enum define E {I 1}
            testnumeric "$type {enum E}" return "{enum {I 1}}"
            testnumeric "$type bitmask" return "bitmask"
        }
        if {$type in $intTypes} {
            foreach attr $requirementAttrs {
                testnumeric "$type $attr {onerror foo}" return "$attr {onerror foo}"
            }
        }

        # Array tests - success
        # none

        # Failure tests - invalid attribute combinations
        if {$type in $intTypes} {
            foreach attr $errorAttrs {
                # Repeated attribute - note different combos are all valid
                testinvalidattr "$type $attr $attr" return $errorMessages(attrconflict)
            }
            foreach attr [list {*}$errorHandlerAttrs] {
                foreach attr2 [list {*}$errorHandlerAttrs {{onerror foo}}] {
                    if {$attr ne $attr2} {
                        testinvalidattr "$type $attr $attr2" return $errorMessages(attrconflict)
                    }
                }
            }
        }
        if {$type in $intTypes} {
            testinvalidattr "$type {onerror foo}" return {"onerror" requires an error checking annotation.}
            testinvalidattr "$type onerror" return {A type annotation has the wrong number of fields.}
            testinvalidattr "$type structsize" param $errorMessages(attrparsemode)
        } else {
            testinvalidattr "$type {onerror foo}" return $errorMessages(attrtype)
            testinvalidattr "$type bitmask" return $errorMessages(attrtype)
            testinvalidattr "$type {enum E}" return $errorMessages(attrtype)
            testinvalidattr "$type structsize" param $errorMessages(attrtype)
        }

        # Failure tests - invalid attributes for type
        foreach attr [list {*}$pointerAttrs nullifempty] {
            testinvalidattr "$type $attr" return $errorMessages(attrtype)
        }
        if {$type ni $intTypes} {
            foreach attr $errorAttrs {
                testinvalidattr "$type $attr" return $errorMessages(attrtype)
            }
        }

        # Failure tests - invalid attributes for parse mode
        foreach attr [list {*}$paramDirectionAttrs retval storeonerror storealways] {
           testinvalidattr "$type $attr" return $errorMessages(attrparsemode)
        }
        testinvalidattr [list $type [list default 0]] return $errorMessages(defaultdisallowed)

        # Array tests failures
        test type-info-$type-array-return-0 "Return array type" -body {
            cffi::type info "$type\[2\]" return
        } -result "Invalid value \"$type\[2\]\". $errorMessages(arrayreturn)" -returnCodes error

        foreach count {[0] [-2] [] [2x] \[2 [2]x} {
            testinvalidattr "$type$count" return "$errorMessages(arraysize)"
        }

        ###
        ### Parse mode = field

        # Scalar tests - success
        testnumeric $type field ""
        catch {cffi::enum delete E}
        cffi::enum define E {I 1}
        foreach {attr expected constraints} {
            "lasterror" "" "-constraints win"
            "winerror" "" "-constraints win"
            "errno" "" ""
            "{onerror handler}" "" ""
            "structsize" "structsize" ""
            "bitmask" "bitmask" ""
            "{enum E}" "{enum {I 1}}" ""
        } {
            if {$type in $intTypes} {
                testnumeric "$type $attr" field $expected {*}$constraints
            } else {
                testinvalidattr "$type $attr" field $errorMessages(attrtype) {*}$constraints
            }
        }
        testnumeric "$type {default 42}" field "{default 42}" {*}$constraints

        # Array tests - success
        testnumericarray "$type" 2 field ""

        # Failure tests - invalid attributes for type
        foreach attr [list {*}$pointerAttrs nullifempty] {
            testinvalidattr "$type $attr" field $errorMessages(attrtype)
        }
        if {$type ni $intTypes} {
            foreach attr [list {*}$errorAttrs {onerror foo} bitmask {enum E}] {
                testinvalidattr [list $type $attr] field $errorMessages(attrtype)
            }
        }

        # Failure tests - invalid attributes for parse mode
        foreach attr $invalidFieldAttrs {
            testinvalidattr "$type $attr" field $errorMessages(attrparsemode)
        }

        # Array tests failures
        foreach count {[0] [-2] [] [2x] \[2 [2]x} {
            testinvalidattr "$type$count" field "$errorMessages(arraysize)"
        }
    }

    ###
    ### Pointer type

    proc testpointer {typedecl parse_mode expected_typeattrs {count 0}} {
        variable typeInfo
        if {"lasterror" in $typedecl || "winerror" in $typedecl} {
            set constraints win
        } else {
            set constraints {}
        }
        set typeattrs [lassign $typedecl type]
        if {$count} {
            append type \[$count\]
            set typedecl "$type $typeattrs"
        }
        set testlabel type-info-[join $typedecl -]

        test $testlabel-$parse_mode-0 "type info $parse_mode, $typedecl" -constraints $constraints -body {
            cffi::type info $typedecl $parse_mode
        } -result [list Size [expr {$count ? ($count * $::tcl_platform(pointerSize)) : $::tcl_platform(pointerSize)}] Count [expr {$count == 0 ? -1 : $count}] Alignment $::tcl_platform(pointerSize) Definition [list $type {*}$expected_typeattrs] BaseSize $::tcl_platform(pointerSize)] -match dict
    }

    foreach type {pointer pointer.::TAG} {
        ### Parse mode = param

        set pointer_valid_error_attrs {errno lasterror}

        # Scalar tests - success
        foreach {attr expected} {
            "" "in"  "in"  "in" "out" "out byref" "inout" "inout byref" "byref" "in byref"
            "in byref" "in byref" "byref out" "out byref" "inout byref" "inout byref"

            "unsafe" "in unsafe"  "in unsafe"  "in unsafe" "out unsafe" "out byref unsafe"
            "inout unsafe" "inout byref unsafe" "byref unsafe" "in byref unsafe"
            "in unsafe byref" "in byref unsafe" "unsafe byref out" "out byref unsafe"
            "inout byref unsafe" "inout byref unsafe"

            "counted" "in counted"  "in counted"  "in counted" "out counted" "out byref counted"
            "inout counted" "inout byref counted" "byref counted" "in byref counted"
            "in counted byref" "in byref counted" "counted byref out" "out byref counted"
            "inout byref counted" "inout byref counted"

            "dispose" "in dispose"  "in dispose"  "in dispose" 
            "inout dispose" "inout byref dispose" "byref dispose" "in byref dispose"
            "in dispose byref" "in byref dispose"
            "inout byref dispose" "inout byref dispose"
            "counted dispose" "in counted dispose"  "in dispose counted"  "in counted dispose"
            "{default 0^}" "in {default 0^}"
            "out storeonerror" "out byref storeonerror"
            "out storealways" "out byref storealways"
            "inout storealways" "inout byref storealways"
            "inout storeonerror" "inout byref storeonerror"
            "nullok" "in nullok"
            "nullok out" "out byref nullok"
            "nullok inout" "inout byref nullok"
            "in lasterror" "in"
            "in errno" "in"
            "in {onerror handler}" "in"
            "out lasterror" "out byref"
            "out errno" "out byref"
            "out {onerror handler}" "out byref"
            "inout lasterror" "inout byref"
            "inout errno" "inout byref"
            "inout {onerror handler}" "inout byref"
            "retval" "out retval byref"
            "retval out" "out retval byref"
        } {
            testpointer "$type $attr" param $expected
        }

        # Array tests - success
        testpointer "$type" param "in byref" 2
        testpointer "$type out" param "out byref" 2

        # Failure tests - invalid attribute combinations
        foreach attra {in out inout} {
            foreach attrb {in out inout} {
                if {$attra ne $attrb} {
                    testinvalidattr "$type $attra $attrb" param $errorMessages(attrconflict)
                }
            }
        }
        testinvalidattr "$type in retval" param $errorMessages(attrconflict)
        testinvalidattr "$type in byref retval" param $errorMessages(attrconflict)
        testinvalidattr "$type inout retval" param $errorMessages(attrconflict)
        testinvalidattr "$type inout retval byref" param $errorMessages(attrconflict)

        testinvalidattr "$type dispose out" param $errorMessages(attrparamdir)
        testinvalidattr "$type dispose unsafe" param $errorMessages(attrconflict)
        testinvalidattr "$type counted unsafe" param $errorMessages(attrconflict)
        foreach attra {out inout} {
            testinvalidattr "$type $attra {default 0^}" param $errorMessages(defaultdisallowed)
        }

        # Scalar tests - invalid attributes for type
        foreach attr [list {*}$errorAttrs {*}$requirementAttrs structsize nullifempty bitmask {enum E}] {
            if {$attr ni $pointer_valid_error_attrs} {
                testinvalidattr [list $type $attr] param $errorMessages(attrtype)
            }
        }

        foreach count {[0] [-2] [] [2x] \[2 [2]x} {
            testinvalidattr "$type$count" param "$errorMessages(arraysize)"
        }

        ### Parse mode = return

        # Scalar tests - success
        testpointer $type return ""
        foreach attr [list {*}$pointerAttrs byref] {
            if {$attr ne "dispose"} {
                testpointer "$type $attr" return $attr
            }
        }
        foreach attr $pointer_valid_error_attrs {
            testpointer "$type $attr" return $attr
        }
        testpointer "$type {onerror foo}" return "{onerror foo}"

        # Failure tests - invalid attribute combinations
        testinvalidattr "$type counted unsafe" return $errorMessages(attrconflict)

        # Failure tests - invalid attribute for type
        foreach attr [list {*}$errorAttrs nullifempty structsize bitmask {enum E}] {
            if {$attr ni $pointer_valid_error_attrs} {
                testinvalidattr [list $type $attr] return $errorMessages(attrtype)
            }
        }

        # Failure tests - invalid attribute for parse mode
        foreach attr [list {*}$paramDirectionAttrs retval dispose storeonerror storealways] {
            testinvalidattr "$type $attr" return $errorMessages(attrparsemode)
        }
        testinvalidattr [list $type [list default 0]] return $errorMessages(defaultdisallowed)

        # Array test failures
        # array invalid count spec failure
        testinvalidattr "$type\[2\]" return $errorMessages(arrayreturn)
        testinvalidattr "$type\[NUMELEMS\]" return $errorMessages(arrayreturn)
        foreach count {[0] [-2] [] [2x] \[2 [2]x} {
            testinvalidattr "$type$count" return "$errorMessages(arraysize)"
        }

        ### Parse mode = field
        # Scalar tests - success
        testpointer $type field ""
        foreach attr $pointerAttrs {
            if {$attr ne "dispose"} {
                testpointer "$type $attr" field $attr
            }
        }
        # Error handling attributes should be ignored
        foreach attr [list {*}$pointer_valid_error_attrs [list {onerror handler}]] {
            testpointer "$type $attr" field ""
        }

        # Array tests - success
        # none

        # Failure tests - invalid attribute combinations
        testinvalidattr "$type counted unsafe" field $errorMessages(attrconflict)

        # Failure tests - invalid attribute for type
        foreach attr [list {*}$errorAttrs nullifempty structsize bitmask {enum E}] {
            if {$attr ni $pointer_valid_error_attrs} {
                testinvalidattr [list $type $attr] field $errorMessages(attrtype)
            }
        }

        # Failure tests - invalid attribute for parse mode
        foreach attr $invalidFieldAttrs {
            testinvalidattr "$type $attr" field $errorMessages(attrparsemode)
        }

        # Array test failures
        testinvalidattr "$type\[NUMELEMS\]" field $errorMessages(fieldvararray)
        foreach count {[0] [-2] [] [2x] \[2 [2]x} {
            testinvalidattr "$type$count" field "$errorMessages(arraysize)"
        }
    }


    ###
    # chars
    proc testchars {typedecl count parse_mode expected_typeattrs} {
        variable typeInfo
        set typeattrs [lassign $typedecl type]
        set testlabel type-info-[join $typedecl -]-$count


        set alignment [expr {$type eq "unichars" ? 2 : 1}]
        if {[string is integer -strict $count]} {
            if {$type eq "unichars"} {
                set size [expr {2*$count}]
            } else {
                set size $count
            }
        } else {
            set size 0
        }
        test $testlabel-$parse_mode-0 "type info $parse_mode, $typedecl" -body {
            cffi::type info "$type\[$count\] $typeattrs" $parse_mode
        } -result [list Size $size Count $count Alignment $alignment Definition [list "$type\[$count\]" {*}$expected_typeattrs] BaseSize $alignment] -match dict
    }
    foreach type {chars chars.utf-8 unichars bytes} {
        ### Parse mode = param, return, field
        foreach parse_mode {param return field} {
            # Scalar tests
            # none (always have to be arrays)

            if {$parse_mode eq "param"} {
                # Param mode array tests - success
                foreach {attr expected} {
                    "" "in byref"  "in"  "in byref" "out" "out byref" "inout" "inout byref" "byref" "in byref"
                    "in byref" "in byref" "byref out" "out byref" "inout byref" "inout byref"
                    "{default 0}" "in byref {default 0}"
                    "out storeonerror" "out byref storeonerror"
                    "out storealways" "out byref storealways"
                    "retval" "out retval byref"
                    "out retval" "out retval byref"
                } {
                    testchars "$type $attr" 10 $parse_mode $expected
                    testchars "$type $attr" NUMELEMS $parse_mode $expected
                }
                # Failure tests - invalid attribute combinations
                foreach attra {in out inout} {
                    foreach attrb {in out inout} {
                        if {$attra ne $attrb} {
                            testinvalidattr "$type\[2\] $attra $attrb" $parse_mode $errorMessages(attrconflict)
                        }
                    }
                }
                foreach attr {storealways storeonerror} {
                    testinvalidattr "$type\[2\] $attr" $parse_mode $errorMessages(store)
                }
            } else {
                if {$parse_mode eq "field"} {
                    testchars $type 10 $parse_mode ""
                } else {
                    # parse_mode return
                    testinvalidattr "$type\[2\]" $parse_mode $errorMessages(typeparsemode)
                    testinvalidattr "$type\[NUMELEMS\]" $parse_mode $errorMessages(typeparsemode)
                }
                # field mode array tests - failure
                foreach attr {in out inout retval} {
                    testinvalidattr "$type\[2\] $attr" $parse_mode $errorMessages(attrparsemode)
                    testinvalidattr "$type\[NUMELEMS\] $attr" $parse_mode $errorMessages(attrparsemode)
                }
            }

            # Failure - scalar value
            testinvalidattr $type $parse_mode $errorMessages(scalarchars)

            # Invalid attributes for type
            foreach attr [list {*}$pointerAttrs {*}$errorAttrs nullifempty structsize bitmask {enum E}] {
                if {$attr eq "nullok"} continue; # Skip nullok from pointerAttrs
                testinvalidattr [list $type\[2\] $attr] $parse_mode $errorMessages(attrtype)
            }

            # array invalid count spec failure
            foreach count {[0] [-2] [] [2x] \[2 [2]x} {
                testinvalidattr "$type$count" $parse_mode "$errorMessages(arraysize)"
            }
        }
    }

    test type-info-chars-encoding-error-param-0 "Unknown encoding for chars - param parse mode" -body {
        cffi::type info {chars.xxx[10]} param
    } -result {unknown encoding "xxx"} -returnCodes error
    test type-info-chars-encoding-error-field-0 "Unknown encoding for chars - field parse mode" -body {
        cffi::type info {chars.xxx[10]} field
    } -result {unknown encoding "xxx"} -returnCodes error

    ###
    # strings
    proc teststring {typedecl parse_mode expected_typeattrs args} {
        variable typeInfo
        set typeattrs [lassign $typedecl type]
        set testlabel type-info-[join $typedecl -]

        test $testlabel-$parse_mode-0 "type info $parse_mode, $typedecl" -body {
            cffi::type info $typedecl $parse_mode
        } -result [list Size $::tcl_platform(pointerSize) Count -1 Alignment $::tcl_platform(pointerSize) Definition [list $type {*}$expected_typeattrs] BaseSize $::tcl_platform(pointerSize)] -match dict {*}$args
    }
    proc teststringarray {typedecl count parse_mode expected_typeattrs args} {
        variable typeInfo
        set typeattrs [lassign $typedecl type]
        set testlabel type-info-[join $typedecl -]-count$count

        test $testlabel-$parse_mode-0 "type info $parse_mode, $typedecl \[$count\]" -body {
            cffi::type info $typedecl\[$count\] $parse_mode
        } -result [list Size [expr {2*$::tcl_platform(pointerSize)}] Count $count Alignment $::tcl_platform(pointerSize) Definition [list $type\[$count\] {*}$expected_typeattrs] BaseSize $::tcl_platform(pointerSize)] -match dict {*}$args
    }

    foreach type {string string.utf-8 unistring} {
        ## param mode
        # in parameters valid for all types
        foreach {attr expected} {
            "" "in"
            "in"  "in"
            "{default 0}" "in {default 0}"
            "nullifempty" "in nullifempty"
            "in nullifempty" "in nullifempty"
            "out" "out byref"
            "byref" "in byref"
            "byref in" "in byref"
            "byref out" "out byref"
            "nullok" "in nullok"
            "in nullok" "in nullok"
            "byref nullok" "in byref nullok"
            "out nullok" "out byref nullok"
            "in errno" "in"
            "in {onerror handler}" "in"
            "out errno" "out byref"
            "out {onerror handler}" "out byref"
            "retval" "out retval byref"
            "retval out" "out retval byref"
        } {
            teststring "$type $attr" param $expected
        }
        teststringarray $type 2 param "in byref"

        foreach {attr expected} {
            "in lasterror" "in"
            "out lasterror" "out byref"
        } {
            teststring "$type $attr" param $expected -constraints win
        }

        foreach attr {inout {inout byref}} {
            testinvalidattr "$type $attr" param $errorMessages(attrtype)
        }

        # Failure tests - invalid attribute combinations
        testinvalidattr "$type in retval" param $errorMessages(attrconflict)
        testinvalidattr "$type byref in retval" param $errorMessages(attrconflict)

        ## return mode
        teststring "$type" return ""
        foreach attr {byref errno {{onerror E}}} {
            teststring "$type $attr" return "$attr"
        }
        teststring "$type lasterror" return "lasterror" -constraints win

        foreach attr {in out nullifempty} {
           testinvalidattr "$type $attr" return $errorMessages(attrparsemode)
        }
        foreach attr {inout winerror} {
            testinvalidattr "$type $attr" return $errorMessages(attrtype)
        }

        ## field mode
        teststring "$type" field ""
        teststring "$type nullifempty" field "nullifempty"
        foreach attr {in out retval} {
            testinvalidattr "$type $attr" field $errorMessages(attrparsemode)
        }
        foreach attr {inout} {
            testinvalidattr "$type $attr" field $errorMessages(attrtype)
        }

        ## For all parse modes
        foreach parse_mode {param return field} {
            # Scalar tests - invalid attributes for type
            foreach attr [list {*}$pointerAttrs {*}$requirementAttrs structsize winerror bitmask {{enum E}}] {
                if {$attr ne "nullok"} {
                    testinvalidattr "$type $attr" $parse_mode $errorMessages(attrtype)
                }
            }
            # Array tests - success
            # none - currently arrays not supported

            # Scalar tests - attributes invalid for param parse mode
            # none - no attrs are invalid solely for param mode

            # array invalid count spec failure
            if {$parse_mode eq "return"} {
                testinvalidattr "$type\[NUMELEMS\]" $parse_mode $errorMessages(arrayreturn)
                testinvalidattr "$type\[2\]" $parse_mode $errorMessages(arrayreturn)
            } elseif {$parse_mode eq "field"} {
                testinvalidattr "$type\[NUMELEMS\]" $parse_mode $errorMessages(fieldvararray)
            }
            foreach count {[0] [-2] [] [2x] \[2 [2]x} {
                testinvalidattr "$type$count" $parse_mode "$errorMessages(arraysize)"
            }
        }
    }
    test type-info-string-encoding-error-param-0 "Unknown encoding for string - param parse mode" -body {
        cffi::type info {string.xxx} param
    } -result {unknown encoding "xxx"} -returnCodes error
    test type-info-string-encoding-error-field-0 "Unknown encoding for string - field parse mode" -body {
        cffi::type info {string.xxx} field
    } -result {unknown encoding "xxx"} -returnCodes error

    ###
    # binary

    ## binary - param mode
    foreach {attr expected} {
        "" "in nullifempty"
        "in"  "in nullifempty"
        "{default 0}" "in nullifempty {default 0}"
        "byref in" "in byref nullifempty"
        "byref" "in byref nullifempty"
        "nullifempty in" "in nullifempty"
        "nullifempty" "in nullifempty"
    } {
        teststring "binary $attr" param $expected
    }
    # out and inout params not allowed for binary
    foreach attr {out retval inout {out byref} {inout byref} structsize bitmask {{enum E}}} {
        testinvalidattr "binary $attr" param $errorMessages(attrtype)
    }

    ## binary - field or return mode
    foreach parse_mode {field return} {
        testinvalidattr binary $parse_mode $errorMessages(typeparsemode)
    }

    ###
    # struct
    proc teststruct {typedecl parse_mode expected_typeattrs {count 0} args} {
        variable typeInfo
        variable testStructSize
        set typeattrs [lassign $typedecl type]
        if {$count} {
            append type \[$count\]
            set typedecl "$type $typeattrs"
        }
        set testlabel type-info-[join $typedecl -]
        test $testlabel-$parse_mode-0 "type info $parse_mode, $typedecl" -body {
            cffi::type info $typedecl $parse_mode
        } -result [list Size [expr {$count ? ($count * $testStructSize) : $testStructSize}] Count [expr {$count == 0 ? -1 : $count}] Alignment 8 Definition [list $type {*}$expected_typeattrs] BaseSize $testStructSize] -match dict {*}$args
    }
    foreach parse_mode {param return field} {
        if {$parse_mode eq "param"} {
            testinvalidattr "struct.TestStruct" param $errorMessages(structbyref) -constraints dyncall
            testinvalidattr "struct.TestStruct in" param $errorMessages(structbyref) -constraints dyncall
            # Scalar tests - success
            foreach {attrs expected} {
                "out" "out byref" "inout" "inout byref" "byref" "in byref"
                "in byref" "in byref" "byref out" "out byref" "inout byref" "inout byref"
                "in byref {default {}}" "in byref {default {}}"
                "in byref nullifempty" "in byref nullifempty"
                "in byref nullok" "in byref nullok"
                "retval" "out retval byref"
                "out retval" "out retval byref"
            } {
                teststruct "struct.::TestStruct $attrs" param $expected
            }
            testinvalidattr "struct.TestStruct in nullok" $parse_mode {A type annotation is not valid for this data type without the byref annotation.} -constraints libffi
            testinvalidattr "struct.TestStruct in nullifempty" $parse_mode {Structs cannot have nullifempty attribute when passed as an argument by value.} -constraints libffi
            testinvalidattr "struct.TestStruct in nullifempty" $parse_mode $errorMessages(structbyref) -constraints dyncall
            testinvalidattr "struct.TestStruct in nullok" $parse_mode $errorMessages(structbyref) -constraints dyncall
        } else {
            foreach attr {in out inout} {
                testinvalidattr "struct.TestStruct $attr" $parse_mode $errorMessages(attrparsemode)
            }
            if {$parse_mode eq "return"} {
                testinvalidattr "struct.TestStruct" $parse_mode $errorMessages(typeparsemode) -constraints dyncall
                teststruct "struct.::TestStruct" $parse_mode "" 0 -constraints libffi
                teststruct "struct.::TestStruct byref" $parse_mode "byref" 0
                teststruct "struct.::TestStruct nullok byref" $parse_mode "byref nullok" 0
                testinvalidattr "struct.TestStruct nullok" $parse_mode {A type annotation is not valid for this data type without the byref annotation.} -constraints libffi
            } else {
                teststruct "struct.::TestStruct" $parse_mode ""
                testinvalidattr "struct.TestStruct byref" $parse_mode $errorMessages(attrparsemode)
            }
        }
        # Failure tests - invalid attribute combinations
        foreach attra {in out inout} {
            foreach attrb {in out inout} {
                if {$attra ne $attrb} {
                    testinvalidattr "struct.TestStruct byref $attra $attrb" $parse_mode $errorMessages(attrconflict)
                }
            }
        }
        # Scalar tests - invalid attributes for type
        foreach attr [list {*}$pointerAttrs {*}$errorAttrs structsize bitmask {{enum E}}] {
            if {$attr ne "nullok"} {
                testinvalidattr "struct.TestStruct $attr" $parse_mode $errorMessages(attrtype)
            }
        }
        testinvalidattr "struct.TestStruct nullok" $parse_mode {A type annotation is not valid for this data type without the byref annotation.} -constraints libffi
        testinvalidattr "struct.TestStruct nullok" $parse_mode {*} -constraints dyncall -match glob

        # Array tests
        if {$parse_mode eq "param"} {
            teststruct {struct.::TestStruct byref} param "in byref" 2
            teststruct {struct.::TestStruct out} param "out byref" 2
        }
        if {$parse_mode eq "field"} {
            testinvalidattr "struct.TestStruct\[NUMELEMS\]" $parse_mode $errorMessages(fieldvararray)
        }
        if {$parse_mode eq "return"} {
            testinvalidattr "struct.TestStruct nullifempty" $parse_mode $errorMessages(attrparsemode)
            if {[cffi::pkgconfig get backend] eq "libffi"} {
                set msgindex arrayreturn
            } else {
                set msgindex typeparsemode
            }
            testinvalidattr "struct.TestStruct\[NUMELEMS\]" $parse_mode $errorMessages($msgindex)
            testinvalidattr "struct.TestStruct\[2\]" $parse_mode $errorMessages($msgindex)
        }

        # array invalid count spec failure
        foreach count {[0] [-2] [] [2x] \[2 [2]x} {
            testinvalidattr "struct.TestStruct$count" $parse_mode "$errorMessages(arraysize)"
        }

        test type-info-struct-$parse_mode-unknown-0 "type info struct unknown parse mode = $parse_mode" -body {
           cffi::type info struct.NOSUCHSTRUCT $parse_mode
        } -result "Struct definition \"NOSUCHSTRUCT\" not found or inaccessible. " -returnCodes error
    }

    ###
    # Verify commands work with aliases
    test type-info-alias-0 "type info alias " -setup {
        cffi::alias define type_info_test_alias_0 {int nonzero}
    } -cleanup {
        cffi::alias delete type_info_test_alias_0
    } -body {
        cffi::type info type_info_test_alias_0 return
    } -result {Size 4 Count -1 Alignment 4 Definition {int nonzero} BaseSize 4} -match dict

    test type-info-alias-1 "type info alias array" -setup {
        cffi::alias define type_info_test_alias_1 {int[2]}
    } -cleanup {
        cffi::alias delete type_info_test_alias_1
    } -body {
        cffi::type info type_info_test_alias_1 param
    } -result {Size 8 Count 2 Alignment 4 Definition {{int[2]} in byref} BaseSize 4} -match dict

    test type-size-alias-0 "type size alias array" -setup {
        cffi::alias define type_size_test_alias_0 {int[2]}
    } -cleanup {
        cffi::alias delete type_size_test_alias_0
    } -body {
        cffi::type size type_size_test_alias_0
    } -result 8

    test type-count-alias-0 "type count alias array" -setup {
        cffi::alias define type_count_test_alias_0 {int[2]}
    } -cleanup {
        cffi::alias delete type_count_test_alias_0
    } -body {
        cffi::type count type_count_test_alias_0
    } -result 2

    test type-info-alias-scope-0 "type info alias scoped" -setup {
        cffi::alias delete *
        namespace eval ::ns {cffi::alias delete *}
        namespace eval :: {cffi::alias delete *}
    } -cleanup {
        cffi::alias delete *
    } -body {
        namespace eval :: {cffi::alias define INT int}
        namespace eval ::ns {cffi::alias define INT longlong}
        list \
            [cffi::type info INT] \
            [namespace eval ::ns {cffi::type info INT}]
    } -result {{Size 4 Count -1 Alignment 4 Definition int BaseSize 4} {Size 8 Count -1 Alignment 8 Definition longlong BaseSize 8}}

}



::tcltest::cleanupTests
namespace delete cffi::test
