Type

This chapter describes the glua language type system

Basic Types

  • Including nil, string, boolean, number, integer, function, table, Map, Array, Stream.

  • nil: Represent empty data.

  • string: Represent string, surrounded by ‘”“’, ‘’‘’ or ‘[[]]’. For example, “hello world”, ‘hello’, [[hello world]]. String surrounded by “[[]]” is cross-line string.

  • boolean: Represent true or false.

  • integer: Integer type, like 123,45.Support 64bit integers.Range from -9223372036854775808 to 9223372036854775807.

  • number: Float type, represent decimal.The implementation is 64bit double type.Range from -2^1024 to 2^1024 . The accuracy 15 decimal. When compiling, integer type can be implicitly converted to number type while number type can not be implicitly converted to integer type.

  • function: Function type.Function can be used as a variable value, parameter and return value.Support closure.Similar to other types of values, but can be called.

  • Map<T>: Hash table type, T represents the hash table’s value type.The hash table is a key-value pair. The type of the key is string. The value type of each key-value pair is of type T. The Map <T> type is a subtype of the table type

    The Map <T> type can be manipulated through functions in the table module. Also can be accessed through the brackets subscript or point plus attribute name. For example, a[‘name’], a.name etc.

  • Array<T>: Array type, T represents the value type of the array. The type of each item in the list is represented by T. Array <T> is a subtype of the table type.

    The Array<T> type can be manipulated through functions in the table module. Also can be accessed through the brackets. For example a[1] etc.

  • Stream Binary byte stream type, representing a binary bit stream.

For example

let a = {}                    -- This is an empty Hash table. The type of the variable is automatically derived as Map <object>.
a['name'] = 'glua'          -- Modify the key-value pairs in the hash table <a>.
let b = { age: 2 }               -- The variable type of <b> is Map <int>.
let c={ ['name']='China', age=5000, address: 'China' }            -- The variable type of <c> is Map <object>. Because the value type has many different types, the value type automatically derived as Map <object>.
let d = [ 1, 2, 3 ]              --  Variable d is a list of three values and its type is Array <int>.
  • record: Custom data structure during compile time, similar to the struct structure in C language,Which can customize the property.When compilation is completed and in the run-time, record type is equivalent to the table type.

For example

type Person = {
    name: string,
    age: int default 24,
    age2: int = 24  -- The default value of record can be used either default or can be used to distinguish.
}
  • table: The parent type of Map <T> ,Array <T> and record.The other three types can be implicitly converted or explicitly converted to a table type by the totable function.You can also implicitly convert a table type to a concrete subtype. And map type can be converted to record type.Other subtypes can not be directly converted to each other.
  • union: Indicates that during the compilation period the viraible may be one of several different types. For example, let a : int | string | number Indicates that it is an int, string or number type.
  • literal type: The Literal Type type is an enumeration type.You can use multiple strings, numbers, boolean or nil as each of the enumerated types.

The enumeration type assignment must be compatible with the enumeration type or enumerated type of literal value, or the compile error.

For example

type Gender = "male" | "female"
var gender: Gender
gender = "male"    -- Correct
gender = "Chinese" -- Error: Compile error.
  • object: Compile type, parent type for all types.The variable declared as an object can be assigned to any type of value.Accessing the object type is allowed during compilation.

For example

var a: object = 1
a = "hello"
pprint(a.name)  -- Compile passed, runtime failed.

Static Type Checking System

  • The compile period will have variable type, function call, operator use, function declaration, record structure and other static type derivation and analysis, Incorrect use of the compile time for the type will cause a error.
  • For variables, will analyze the types of variables in different code locations, and then analyze whether errors are used in different locations, such as whether the type is used correctly, or whether it is used after initialization
  • For the function definition, the parameter type and return type are analyzed, and static type analysis is performed in the function body
  • For function calls, will analyze the current context, whether the type of function matches the type of argument used
  • For the record type, the type of each property field is analyzed
  • Type declaration of variables and function arguments,and the place that constructor function used,will check the existence of type,compatibility and so on

Global Variable and Local Variable

  • You are not allowed to create new global variable, or make changes to the _ENV and _G global variable. However, create a new local variable is allowed, The syntax goes like local a = 123; local a,b = “name”, ‘age’. If you need to create a local function, it could be like ‘local function abc() return “abc”; end;’
  • Local variable declarations can declare variable types at the same time, Such as local a: string = “hello”
  • The value of local variable at compile time should be consistent with the compilation period type declaration of local variable, Such as local a = 1; a = “hi” it will compile error.
  • For security reasons, each function in a smart contract cannot have more than 128 local variables, and the symbol length of each local variable cannot exceed 128 characters(The parameters of the function, including self, are also included in the number of local variables)
  • The names of variables, parameter names, and symbol for the event name are not allowed to use the names of keywords, especially notice such as do/end/then are easily misused as keywords of variable name/parameter name/event name
  • Local variables can be declared with local, var, and let
  • The keyword of var and local are equivalent to be used to declare variable local variables, and variable local variables can be used and modified in the following visibility scope code
  • The keyword of let used to declare the immutable local variables, local variable with the statement can only be initialized values, can’t modify the value, but it only variables itself immutable, the value of the variable pointing to the table or record type if is still the same can change the content of the table

Such as:

var a = 'hello'
local b = 'hello'
let c = 'hello'
var d: object = 'hello'
a = 'glua'  -- correct
a = 123  -- Error, type can't be change
b = 'glua'  -- correct
b = 123 -- Error, type can't be change
c = 'glua' -- Error
d = 123  -- correct, d variable's object type of the compile time type, which can be assigned value by numeric value

Type Declaration

When declaring variables or the parameters of a function, you can declare variable types at the same time, and if the type of function parameters is not given, then the default is object type.

for example:

let a: string = "123"
var b: G1                          -- G1 is one record type
let b: int | string | Person       -- Person is one record type, means that b is an int or a string or a Person type, which is the union type
let c ?: int                       -- The variable c that represents the declaration is the type int or nil, the equivalent of int | nil

let function add(a: number, b: number, c ?: Array<number>)
    return a + b
end

You can also declare a function type when declaring a type, and the signature type syntax of the function is (ArgTypeName1, … ) => RetTypeName

for example:

let a: (int, int, int) => string

The variable type or function parameter type also can be set as Function,which means The type of variable/parameter is a function. Any value of any function type can be accepted, regardless of parameter number, parameter type and return value type.

for example:

let function add(a: number, b: number)
    return a + b
end
let add2: Function = add
let r = add2('123')    -- the compile type of this code is fine,but will cause a type error in runtime

Custom Type of ‘record’

  • The ‘record’ type is the syntax of the c-language struct,can customize the structure with a number of attribute fields.Each attribute field has its own type,different properties can have different types, The type of the attribute can also be another ‘record’ type,can nest using the ‘record’ type。

    for example:

    type Address = {
        province: string,
        city: string
    }
    
    type Person = {
        name: string,
        age: int default 24,
        age2: int = 24,  -- The default value of the 'record' attribute can be distinguished either by default or =
        address: Address,
        parent_names: Array<string>
    }
    
  • You can use ‘table’ to initiate and assign ‘record’, the variable or returning value from a function of the record type can also be assigned to a table type variable, with no additional type conversion, and the property and attribute values of the record correspond to the key value of the table.

for example:

type Person = {
    name: string,
    age: int default 24
}

type Address = {
    province: string,
    city: string
}

var p1: Person = {name='glua'} -- The value of the Map<T> value to the value of the record type is assigned, and the automatic type conversion is done
var p2: Address = p1           -- Compile  error, p1 is the 'Person' type variable, cannot assign the value to the 'Address' type variable
var p3: table = p1             -- Correct, the variable or value of the 'record' type can be assigned to the table type variable
  • The ‘record’ type is just a type declaration for the variable, but it is actually a ‘table’ type at runtime。 If record is created with the constructor (the name of the constructor and the name of the type),can use a value of table as a parameter initialization,properties that have no assignment will use the default value of the attribute.
  If you use table type values such as {} to type conversion assignment to record type,so constructor calls are not involved.The default value of the ‘record’ type is not working,
the storage initialization of the contract is initialized directly by blockchain.The default attribute value of the record type does not work on it.

for example:

type Person = {
    name: string,
    age: int default 24,
    age2: int = 24  -- The default value of the record attribute can be distinguished either by default or =
}
let p1 = Person()                        -- The value of p1 {name: nil, age: 24, age2: 24}
let p2 = Person({name='glua'})           -- The value of p2 {name: 'glua', age: 24, age2: 24}
let p3 = Person({name='glua', age=100})  -- The value of p3 {name: 'glua', age: 100, age2: 24}
let p4: Person = {}                      -- The value of p4 {}
  • The ‘record’ definition can have several generic parameters,Generic parameters can be used to define attribute types in the type of record.A record type with a generic parameter, need to instantiate a specific type to declare a variable or a function parameter type.

for example:

type Person<A> = {  -- A is the generic parameter of the Person generic type, which is used to represent the type variable of the unknown type
    name: string,
    address: A      -- Declare the address property to A type, which indicates that the type of address attribute is determined based on the type of A in Person's generic instantiation
}
let p = Person<string>()  -- In this case, you replace the Person generic type A variable with A string type to produce A new type Person<string >, and then call this new type of constructor
  • The definition of the member function of record,cann’t use function <varname>.<funcname> (…) … The ‘end’ of the grammar,can only use function <varname>:<funcname> (…) … The ‘end’ of the grammar

for example:

type Person = {
    name: string,
    age: int
}
var p1 = {}
function p1.sayHi1(self)    -- right
end
function p1:sayHi2()    -- right
end

var p2 = Person()
function p2.sayHi1(self)    -- The compiler error, p2 is the record type, and you cannot define the member function in varname.funcname
end
function p2:sayHi2()        -- right
end

p1:sayHi1()                 -- right
p1:sayHi2()                 -- right
p2:sayHi2()                 -- right
  • The syntax of a record without a specific description, grammatical agreement to Map<object>,can also use varname.propertyName and varname[‘propertyName’],varname[“propertyName”} to read and modify the properties of the record type variable
  • the definition grammer of record is :
type RecordName = { PropName: PropTypeName, … }

or

type RecordName <GenericType1, … > = { PropName: PropTypeName, … }

for example:

type Person = {
    name: string,
    age: int,
    mobile ?: string default ''                  --  This means that the attribute mobile type is string | nil, and the default value is an empty string
}
type G1<T1, T2, T3> = { a: T1, b: T2, c: T3, count: int }
  • Record can be used to define new record for other names, while you can have new generic parameters (optional)

Syntax is as follows:

type RecordName = RecordNameExisted < Type1, … >

or

type RecordName = RecordNameExisted

or

type RecordName <GenericType1, … > = RecordNameExisted < Type1, … >

or

type RecordName <GenericType1, … > = RecordNameExisted

for example:

type G2<T> = G1<int, T, string>
type G3 = G1<string> -- compiler error, G1 requires 3 type variables
type G4 = string
type G5 = G4
  • The record type automatically produces a function of the same name as a constructor after the definition,can optionally accept a Map<T> type parameter, the parameter Map<T> has attributes that override the default values of the record type.The merged Map<T> as the record type as the constructor’s returning value, the constructor of calling type can also omit the parentheses directly to pass a map literal literal as a parameter

for example:

type Person = { name: string, age: int default 100 }
let p1 = Person()                        -- The value of p1 is {name: nil, age: 100}
let p2 = Person({name: "glua"})           -- The value of p2 is {name: "glua", age: 100}

let p3 = Array<Person> (  [ {name: "glua"}, {name: "China", age: 10000} ] )   -- The brackets cannot be omitted here, because the parameter is the array literal is not the map literal
let p4 = Array<Person> { name: "hello", age: 100 }

Literal Type

  • The type of Literal Type is an enumerated Type that can be used as each of the enumerated types of string or number or Boolean or nil literals,

When assigning values to an enumeration type, you must use a compatible enumeration type or the literal value of an enumerated type, otherwise the compile period is wrong.

  • The runtime enumeration type shows the type that is specific to the type of the specific value, which is to say, the enumeration type is the compile time type, and the runtime does not have an enumeration type
  • The enumeration type has no constructor, which is different from the record type
  • grammar
type EnumTypeName = LiteralValue1 | LiteralValue2 ...

LiteralValue1 can use LiteralString | LiteralNumber | LiteralInteger | LiteralBool | nil in any of the literal quantities
  • Such as
type Gender = "male" | "female"
var gender: Gender
gender = "male" -- Correct
gender = "Chinese" -- Error: Compile error

function a1(p: true)
end

function a2(p: 'Chinese')
end

let a3: string = gender -- Variables of the enumerated type can be explicitly degraded to the types of enumerated values

-- The type of Literal Type can also be spliced
type Cat = "whiteCat" | "blackCat"
type Dog = "husky" | "corgi"
type Pets = Cat | Dog
-- Equivalent to type Pets = "whiteCat" | "blackCat" | "husky" | "corgi"

type Falsy = "" | 0 | false | nil

Generic type

  • Support in the record type definition using generic to define, in the type of attribute as a type to use.
  • The record type with the generic type needs to be instantiated with the actual type before it can be used to declare the variable type
  • When you define a new record type, you support generic and support multiple generic parameters

grammar

type RecordName <GenericName1, … > = { PropName: TypeName, … }

Such as:

type G1<T1, T2, T3> = {   -- Here T1, T2, T3 is a generic type variable, where the definition of a generic type G1, the specific use of specific types can be used to replace T1, T2, T3, to produce a new type
    id: string,
    a: T1,
    b: T2,
    c: T3
}
  • Generic type partial instantiation and type redefining
  • Record type with a generic, may not use all of the generic type variables, only the replacement of some of the variables to generate a new type of generic type

grammar

type RecordName {<GenericName1, … > } = RecordNameExisted { <TypeName1, … >  }

Such as:

type G2<T> = G1<int, T, string>        -- Define a new generic G2 with type variable T, which is a new type of substitution for three types of variables in the G1 generic, using int, T, and string, respectively
type G3 = G2<string>                   -- Define a new type of G3, which is a new type that is generated by string instead of a type variable in the G2 generic
  • Generic instantiation refers to replacing a generic variable in a generic type with a specific type, such as G2 <string> that is the instantiation of the G2 generic
  • The generic instantiation can be used directly in the type declaration of the variable/function parameter or directly in the constructor

Such as:

type G2<T> = { name: string, info: T }
let a1: G2<string> = { name: 'glua', info: 'hello' }
let a2 = G2<int>({ name: 'glua', info: 123 })

Map type

Map<T> type is a built-in primitive type,on behalf of hash table.

The literal grammar is {} on behalf of empty Map<T>,also can use like { key1 = value1, key2 = value2, key3: value3, [‘key4’] = value4, ‘key5’: value5, … } to initialize the value of a table.

Map<T> can modify and query the value of one of these indexes by using the dot operator or the middle brackets,for example:

let a = { name='glua', age=1 }      -- Because 'glua' is not the same type as 1,a is automatically known as Map<object> type
let a1 = { name: 'glua', age: 1 }   -- So here a1 and a are the equivalent of initializing the Map
let a2 = { name: 'glua', address: 'China' }   -- Because the types of 'glua' and 'China' are string, the type of a2 is automatically derived as Map<string>
let a3 = {name: 'glua', 'age': 1 }   -- This is the same thing as a, a1
a['abc'] = 'China'    -- Insert/modify index 'ABC' for a
a.abc = 'China'    -- The effect is the same as a[' ABC '] = 'China'
let b1 = a.name     -- The value assigned to read 'name' index of a is assigned to the new variable b1
let b2 = a['name']    -- like let b2 = a.name

The CRUD of the Map<T> type can be manipulated using the built-in module table module

For example:

var a = {name='glua'}
let b1 = a.name  -- Gets the value of the key mapping in hashtable of a
let b2 = a['name']  -- Get another way to get the value of the 'name' in hashtable of a
a.name = 'China'  -- Modify or increase the 'name' in hashtable of a to 'China'
a['name'] = 'China'  -- Another way to modify or increase the 'name' in hashtable of a to 'China'
var k: string, v: string = '', ''
for k, v in pairs(a) do
    pprint(k, v)  -- Traversal hashtable of a, k and v are key and value of each item of hashtable of a
end

Type Array

  • Glua array type is a dynamic array and array length is vairable.

  • The array type is a data structure that can store several values of the same type. The type declaration syntax is Array <T>, where T can be replaced by a specific type.

  • The array syntax is:

    [ value1, value2, … ]
    

For example:

let a: Array<int> = [ 1, 2, 3 ]     -- Note: '>' and '=' symbols can not be written together.
let b: int = a[1]
  • Array type and array literals support nested and protected table literals.

For example:

type Person = { name: string, age: int default 0 }

let p1 = Person({name='p1'})
let p2 = Person({name='p2', age=24})
let persons: Array<Array<Person> > = [ [p1, p2], [p2, p3], [p3] ]       -- Note: Symbol '>' can not be written together otherwise it will be recognized as '>>'
  • Using the table module in build-in modules, we can change the value of the Array<T>.

For example:

var a = [1,2,3]
table.append(a, 4)  -- Add value to array.
a[3] = 100  -- Modify the 3rd element of the array.
let array_length1 = table.length(a)  -- Get the length of array.
let array_length2 = #a  -- Another method to get the length of array.
table.remove(a, 2)  -- Delete the 2nd element of the array.(1-based index)

var k: int, v: int = 0, 0
for k, v in pairs(a) do
    pprint(k, v)  -- Go through array a.  k and v are the indexes and values for each of the items, starting with 1.
end

table,record,Map<T>,Array<T> conversion between types

  • Table is the parent type of the three types, The Map <? The > type can be used to initialize the record type

It is acceptable when the type between two sides are exactly same.

==================     ==============================================  ======================
    the left value type            the right value type                     whether to accept

    table                          record                                   true
    table                          Map<T>                                   true
    table                          Array<T>                                 true
    record                         table                                    true
    record                         Map<T>                                   true
    record                         Array<T>                                 false
    record                         the record of other types                false
    Map<T1>                        table                                    true
    Map<T1>                        record                                   false
    Map<T1>                        empty Map<object>                             true
    Map<T1>                        Map<T2 where T2 extends T1>              true
    Map<T1>                        Map<T2 where T1 extends T2>              false
    Map<T1>                        Array<?>                                 false
    Array<T1>                      table                                    true
    Array<T1>                      empty Map<object>                            true
    Array<T1>                      record                                   false
    Array<T1>                      Array<T2 where T2 extends T1>            true
    Array<T1>                      Array<T2 where T1 extends T2>            false
    Array<T1>                      Map<?>                                   false
==================     ==============================================  ======================

Built-in record type

  • Contract<S> type ,can be used to declare the type of contract variable,replace the storage type with the current contract

The built-in Contract type code is implemented as follows:

type Contract<S> = {
    id: string,
    name: string,
    storage: S
}

Example:

type Storage = {
    author_name: string,
    age: int
}
let M: Contract<Storage> = {}
function M:init()
    self.storage.age = 100
    self.storage.author_name = 'Achain'
    -- the self.id and self.name, self.storage.author_name is string type ,self.storage.age is integer

end
return M
  • Stream type ,can be used as byte stream type ,is a built-in record type,But the Stream type object accessing the member function can only be used the colon other than the dot notation

The built-in Stream’s type signature is as follows:

type Stream = {
    pos: () => int,  -- Get the current location in the byte stream
    eof: () => bool, -- Determine if the byte stream is ending
    current: () => int,  -- Get the current byte (the conversion int type), and if it has been read that cannot read the current byte,return -1
    next: () => bool,    -- If the byte stream is not at the end of the line, the pos goes in one step and returns true, otherwise false
    reset_pos: () => nil,   -- Reset the current read location of the byte stream to the starting position
    size: () => int,        -- Get the length of the byte stream
    push: (int) => nil,     -- Take the last byte of the argument (in a c-style char type), and add to the byte stream
    push_string: (string) => nil  -- each byte of the parameter string will be added to a byte stream
}

Example:

let a1 = Stream()
a1:push(123)    -- for Stream type ,You can't use the method of accessing member functions on the point number of a1. Push (a1, 123)
a1:push_string('abc')
let s1 = a1:size()
pprint("a1 size is ", s1)
pprint('a1 is ', tostring(a1), tojsonstring(a1))
pprint('a1 pos is', a1:pos())

var c = a1:pos()

var s = ''
while c < a1:size() do
    s = s .. tostring(a1:current())
    pprint("a1[", c, "]=", a1:current())  -- Should be output in turn a1[0]=123   a1[1]=97    a1[2]=98      a1[3]=99
    c = c + 1
    a1:next()
end