[InterTypes::] Inter Data Types. A primitive notion of data type, below the level of kinds. @h Constructors. Abstractly, an Inter type is a combination of a "constructor" and 0 or more "operand types", the number depending on which constructor is used. If this is 0, type is a "base" constructor. Constructors are identified textually by keywords, such as |int32|, and also by "constructor ID" numbers, such as |INT32_ITCONC|. This one is a base; whereas |list|, for example, is not -- |list of int32| is a valid type, with 1 type operand, but |list| alone is not sufficient to specify a type. @ These are the constructor IDs. Note that changing any of these values would invalidate existing Inter binary files, necessitating a bump of //The Inter Version//. @e UNCHECKED_ITCONC from 1 @e INT32_ITCONC @e INT16_ITCONC @e INT8_ITCONC @e INT2_ITCONC @e REAL_ITCONC @e TEXT_ITCONC @e ENUM_ITCONC @e LIST_ITCONC @e ACTIVITY_ITCONC @e COLUMN_ITCONC @e TABLE_ITCONC @e FUNCTION_ITCONC @e STRUCT_ITCONC @e RELATION_ITCONC @e DESCRIPTION_ITCONC @e RULE_ITCONC @e RULEBOOK_ITCONC @e EQUATED_ITCONC @e VOID_ITCONC @d MIN_INTER_TYPE_CONSTRUCTOR UNCHECKED_ITCONC @d MAX_INTER_TYPE_CONSTRUCTOR VOID_ITCONC = int InterTypes::is_valid_constructor_code(inter_ti constructor) { if ((constructor < MIN_INTER_TYPE_CONSTRUCTOR) || (constructor > MAX_INTER_TYPE_CONSTRUCTOR)) return FALSE; return TRUE; } @ Clearly we need to store some metadata about what these constructor IDs mean, and we do that with a simple lookup array large enough to hold all valid constructor codes as indexes: = typedef struct inter_type_constructor { inter_ti constructor_ID; struct text_stream *constructor_keyword; long long int min_value; long long int max_value; int is_enumerated; int is_base; int arity; } inter_type_constructor; inter_type_constructor inter_type_constructors[MAX_INTER_TYPE_CONSTRUCTOR + 1]; @ That array initially contains undetermined data, of course, so we need to initialise it: = void InterTypes::initialise_constructors(void) { InterTypes::init_con(UNCHECKED_ITCONC, I"unchecked", -2147483648, 2147483647, FALSE, TRUE, 0); InterTypes::init_con(INT32_ITCONC, I"int32", -2147483648, 2147483647, FALSE, TRUE, 0); InterTypes::init_con(INT16_ITCONC, I"int16", -32768, 32767, FALSE, TRUE, 0); InterTypes::init_con(INT8_ITCONC, I"int8", -128, 127, FALSE, TRUE, 0); InterTypes::init_con(INT2_ITCONC, I"int2", 0, 1, FALSE, TRUE, 0); InterTypes::init_con(REAL_ITCONC, I"real", -2147483648, 2147483647, FALSE, TRUE, 0); InterTypes::init_con(TEXT_ITCONC, I"text", -2147483648, 2147483647, FALSE, TRUE, 0); InterTypes::init_con(ENUM_ITCONC, I"enum", 0, 2147483647, TRUE, TRUE, 0); InterTypes::init_con(LIST_ITCONC, I"list", -2147483648, 2147483647, FALSE, FALSE, 1); InterTypes::init_con(ACTIVITY_ITCONC, I"activity", -2147483648, 2147483647, FALSE, FALSE, 1); InterTypes::init_con(COLUMN_ITCONC, I"column", -2147483648, 2147483647, FALSE, FALSE, 1); InterTypes::init_con(TABLE_ITCONC, I"table", -2147483648, 2147483647, FALSE, FALSE, 1); InterTypes::init_con(FUNCTION_ITCONC, I"function", -2147483648, 2147483647, FALSE, FALSE, 2); InterTypes::init_con(STRUCT_ITCONC, I"struct", -2147483648, 2147483647, FALSE, FALSE, 0); InterTypes::init_con(RELATION_ITCONC, I"relation", -2147483648, 2147483647, FALSE, FALSE, 2); InterTypes::init_con(DESCRIPTION_ITCONC, I"description", -2147483648, 2147483647, FALSE, FALSE, 1); InterTypes::init_con(RULE_ITCONC, I"rule", -2147483648, 2147483647, FALSE, FALSE, 2); InterTypes::init_con(RULEBOOK_ITCONC, I"rulebook", -2147483648, 2147483647, FALSE, FALSE, 1); InterTypes::init_con(EQUATED_ITCONC, I"", -2147483648, 2147483647, FALSE, FALSE, 1); InterTypes::init_con(VOID_ITCONC, I"void", 1, 0, FALSE, TRUE, 0); } @ Where: = inter_type_constructor *InterTypes::init_con(inter_ti ID, text_stream *name, int range_from, int range_to, int en, int base, int arity) { if (InterTypes::is_valid_constructor_code(ID) == FALSE) internal_error("constructor ID out of range"); inter_type_constructor *IDT = &(inter_type_constructors[ID]); IDT->constructor_ID = ID; IDT->constructor_keyword = Str::duplicate(name); IDT->min_value = range_from; IDT->max_value = range_to; IDT->is_enumerated = en; IDT->is_base = base; IDT->arity = arity; return IDT; } @ Assuming that has been done, it is safe to call these lookup functions. Note that it's fine for textual lookups to be relatively slow. = inter_type_constructor *InterTypes::constructor_from_ID(inter_ti ID) { if (InterTypes::is_valid_constructor_code(ID)) return &(inter_type_constructors[ID]); return NULL; } inter_type_constructor *InterTypes::constructor_from_name(text_stream *name) { for (inter_ti ID = MIN_INTER_TYPE_CONSTRUCTOR; ID <= MAX_INTER_TYPE_CONSTRUCTOR; ID++) { inter_type_constructor *itc = &(inter_type_constructors[ID]); if (Str::eq(itc->constructor_keyword, name)) return itc; } return NULL; } @h Simple types and type names. We need to represent types very economically in terms of memory. In principle, the set of abstract types is infinite (consider for example |int32|, |list of int32|, |list of list of int32|, ...), so there is no limit to the memory which might be required. We use the following representations, starting with the most concise: (1) A "TID", or type ID, is a single |inter_ti| value, often stored as a field in the bytecode for some instruction. For example, a |VARIABLE_IST| instruction includes a field holding the TID of its variable's type. This can only represent simple descriptions (see below), and you need to know what package a TID came from (i.e., what symbols table was in use there) to unravel it. (2) An //inter_type// is a lightweight structure intended for passing around the functions in this section. It can also only represent simple descriptions -- in fact, TIDs and |inter_type|s can faithfully be converted back and forth -- but has the advantage that you don't need any package context to understand it. Arguably this should be called |inter_simple_type_description|, but this is the one we use most often, so brevity is good. (3) An //inter_semisimple_type_description// is a much larger structure used only when parsing Inter code from text -- so in a regular Inform 7 compilation run, no such structures will ever exist. This is still limited, but to the larger set of semi-simple type descriptions. The following definitions look circular, but are not:[1] (*) A "simple type description" is either a constructor for which all type operands are |unchecked|, such as |int32| or |list of unchecked|, or else a "type name". (*) A "semi-simple type description" is either a constructor for which all type operands are simple, or else a "type name". (*) A "type name" is a name defined with a specific semi-simple type description. [1] Because the hierarchy of definitions of type names must be well-founded. You cannot define |K_apple| to equal |K_pear| and vice versa. Each type name must be defined in terms of a finite number of simple types, once all type name substitution has been performed, and no type name can ever lead back to itself. @ By using type names we can (indirectly) represent any abstract type using any of the representations above. For example, |list of list of int32| is neither simple nor semi-simple, but we can get to it by: (1) Defining |K_list_of_int32| as a type name for |list of int32|, which is semi-simple. (2) Defining |K_list_of_list_of_int32| as a type name for |list of K_list_of_int32|, which is semi-simple. And we now have |K_list_of_list_of_int32|, which is simple since it is a bare type name, and so can be stored in an //inter_type// or a TID. @ So, then, this holds any simple type description: = typedef struct inter_type { inter_ti underlying_constructor; inter_symbol *type_name; } inter_type; @ Since there are two possibilities, there are two functions to construct these: = inter_type InterTypes::from_constructor_code(inter_ti constructor_code) { if (InterTypes::is_valid_constructor_code(constructor_code) == FALSE) internal_error("invalid constructor code"); inter_type type; type.underlying_constructor = constructor_code; type.type_name = NULL; return type; } inter_type InterTypes::from_type_name(inter_symbol *S) { if (S) { inter_type type; type.underlying_constructor = TypenameInstruction::constructor(S); type.type_name = S; return type; } return InterTypes::unchecked(); } @ Reading those back: = inter_symbol *InterTypes::type_name(inter_type type) { return type.type_name; } inter_type_constructor *InterTypes::constructor(inter_type type) { inter_type_constructor *itc = InterTypes::constructor_from_ID(type.underlying_constructor); if (itc == NULL) itc = InterTypes::constructor_from_ID(UNCHECKED_ITCONC); return itc; } inter_ti InterTypes::constructor_code(inter_type type) { return InterTypes::constructor(type)->constructor_ID; } @ In some ways the most useful simple type is |unchecked|. This declares that all type-checking rules are waived for the data being described. A program in which all data is |unchecked| is a program with no type-checking at all. = inter_type InterTypes::unchecked(void) { return InterTypes::from_constructor_code(UNCHECKED_ITCONC); } int InterTypes::is_unchecked(inter_type type) { if (InterTypes::constructor_code(type) == UNCHECKED_ITCONC) return TRUE; return FALSE; } @ Access to the arity and operands depends on whether there's a typename or not: only with a typename can we have an arity other than the default (e.g. a |FUNCTION_ITCONC| with arity 5) or operands other than |unchecked|. = int InterTypes::arity_is_possible(inter_type type, int arity) { inter_type_constructor *itc = InterTypes::constructor(type); if (itc->arity == (int) arity) return TRUE; if (itc->constructor_ID == TABLE_ITCONC) if (arity == 0) return TRUE; if ((itc->constructor_ID == FUNCTION_ITCONC) || (itc->constructor_ID == RULE_ITCONC) || (itc->constructor_ID == STRUCT_ITCONC)) { if (itc->arity <= (int) arity) return TRUE; } return FALSE; } int InterTypes::type_arity(inter_type type) { inter_symbol *type_name = InterTypes::type_name(type); if (type_name) return TypenameInstruction::arity(type_name); return InterTypes::constructor(type)->arity; } inter_type InterTypes::type_operand(inter_type type, int n) { inter_symbol *type_name = InterTypes::type_name(type); if (type_name) return TypenameInstruction::operand_type(InterTypes::type_name(type), n); return InterTypes::unchecked(); } @h Converting inter_type to TID. = inter_type InterTypes::from_TID(inter_symbols_table *T, inter_ti TID) { if (TID >= SYMBOL_BASE_VAL) return InterTypes::from_type_name(InterSymbolsTable::symbol_from_ID(T, TID)); if (InterTypes::is_valid_constructor_code(TID)) return InterTypes::from_constructor_code(TID); return InterTypes::unchecked(); } inter_type InterTypes::from_TID_in_field(inter_tree_node *P, int field) { return InterTypes::from_TID(InterPackage::scope(Inode::get_package(P)), P->W.instruction[field]); } @h Converting TID to inter_type. = inter_ti InterTypes::to_TID(inter_symbols_table *T, inter_type type) { if (type.type_name) return InterSymbolsTable::id_from_symbol_in_table(T, type.type_name); return type.underlying_constructor; } inter_ti InterTypes::to_TID_at(inter_bookmark *IBM, inter_type type) { if (type.type_name) return InterSymbolsTable::id_at_bookmark(IBM, type.type_name); return type.underlying_constructor; } @h Parsing from text. The data structure //inter_semisimple_type_description// exists as a way of holding the results of the function //InterTypes::parse_semisimple// -- see below. It's made convoluted by the remote but theoretical need to handle an arbitrarily large number of type operands. No human user of Inter would ever write a type exceeding about 10 operands, but we want to avoid any maxima in Inter because it's primarily designed as a language for programs to write. @d DEFAULT_SIZE_OF_ISSTD_OPERAND_ARRAY 32 = typedef struct inter_semisimple_type_description { inter_ti constructor_code; int arity; int capacity; inter_ti default_operand_TIDs[DEFAULT_SIZE_OF_ISSTD_OPERAND_ARRAY]; inter_ti *operand_TIDs; } inter_semisimple_type_description; void InterTypes::initialise_isstd(inter_semisimple_type_description *results) { results->constructor_code = UNCHECKED_ITCONC; results->arity = 0; results->capacity = DEFAULT_SIZE_OF_ISSTD_OPERAND_ARRAY; results->operand_TIDs = results->default_operand_TIDs; } void InterTypes::add_operand_to_isstd(inter_semisimple_type_description *results, inter_symbols_table *T, inter_type type) { inter_ti TID = InterTypes::to_TID(T, type); if (results->arity >= results->capacity) { inter_ti *extended = (inter_ti *) Memory::calloc(2*results->capacity, sizeof(inter_ti), INTER_BYTECODE_MREASON); for (int i=0; i<2*results->capacity; i++) if (i < results->capacity) extended[i] = results->operand_TIDs[i]; else extended[i] = 0; @; results->capacity = 2*results->capacity; results->operand_TIDs = extended; } results->operand_TIDs[(results->arity)++] = TID; } @ After a call to //InterTypes::parse_semisimple// the following should be used to dispose of the structure. Of course, almost always it does nothing, but it prevents a memory leak if really large results were returned. = void InterTypes::dispose_of_isstd(inter_semisimple_type_description *results) { results->constructor_code = UNCHECKED_ITCONC; results->arity = 0; @; } @ = if (results->capacity > DEFAULT_SIZE_OF_ISSTD_OPERAND_ARRAY) Memory::I7_array_free(results->operand_TIDs, INTER_BYTECODE_MREASON, results->capacity, sizeof(inter_ti)); @ Here goes, then: = inter_error_message *InterTypes::parse_semisimple(text_stream *text, inter_symbols_table *T, inter_error_location *eloc, inter_semisimple_type_description *results) { results->constructor_code = UNCHECKED_ITCONC; results->arity = 0; inter_error_message *E = NULL; match_results mr = Regexp::create_mr(); @; @; @; @; @; @; @; @; @; @; @; Regexp::dispose_of(&mr); return InterErrors::quoted(I"no such data type", text, eloc); } @ = if (Regexp::match(&mr, text, L"rulebook of (%C+)")) { results->constructor_code = RULEBOOK_ITCONC; inter_type conts_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E); InterTypes::add_operand_to_isstd(results, T, conts_type); Regexp::dispose_of(&mr); return E; } @ = if (Regexp::match(&mr, text, L"list of (%C+)")) { results->constructor_code = LIST_ITCONC; inter_type conts_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E); InterTypes::add_operand_to_isstd(results, T, conts_type); Regexp::dispose_of(&mr); return E; } @ = if (Regexp::match(&mr, text, L"activity on (%C+)")) { results->constructor_code = ACTIVITY_ITCONC; inter_type conts_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E); InterTypes::add_operand_to_isstd(results, T, conts_type); Regexp::dispose_of(&mr); return E; } @ = if (Regexp::match(&mr, text, L"column of (%C+)")) { results->constructor_code = COLUMN_ITCONC; inter_type conts_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E); InterTypes::add_operand_to_isstd(results, T, conts_type); Regexp::dispose_of(&mr); return E; } @ = if (Regexp::match(&mr, text, L"table of (%C+)")) { results->constructor_code = TABLE_ITCONC; inter_type conts_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E); InterTypes::add_operand_to_isstd(results, T, conts_type); Regexp::dispose_of(&mr); return E; } @ = if (Regexp::match(&mr, text, L"description of (%C+)")) { results->constructor_code = DESCRIPTION_ITCONC; inter_type conts_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E); InterTypes::add_operand_to_isstd(results, T, conts_type); Regexp::dispose_of(&mr); return E; } @ = if (Regexp::match(&mr, text, L"relation of (%C+) to (%C+)")) { results->constructor_code = RELATION_ITCONC; inter_type X_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E); InterTypes::add_operand_to_isstd(results, T, X_type); if (E == NULL) { inter_type Y_type = InterTypes::parse_simple(T, eloc, mr.exp[1], &E); InterTypes::add_operand_to_isstd(results, T, Y_type); } else { InterTypes::add_operand_to_isstd(results, T, InterTypes::unchecked()); } Regexp::dispose_of(&mr); return E; } @ = if ((Regexp::match(&mr, text, L"(function) (%c+?) -> (%c+)")) || (Regexp::match(&mr, text, L"(rule) (%c+?) -> (%c+)"))) { if (Str::eq(mr.exp[0], I"function")) results->constructor_code = FUNCTION_ITCONC; else results->constructor_code = RULE_ITCONC; text_stream *from = mr.exp[1]; text_stream *to = mr.exp[2]; inter_error_message *returned_E = NULL; if (Str::eq(from, I"void")) { InterTypes::add_operand_to_isstd(results, T, InterTypes::from_constructor_code(VOID_ITCONC)); } else { match_results mr3 = Regexp::create_mr(); while (Regexp::match(&mr3, from, L" *(%C+) *(%c*)")) { inter_type arg_type = InterTypes::parse_simple(T, eloc, mr3.exp[0], &E); InterTypes::add_operand_to_isstd(results, T, arg_type); if ((E) && (returned_E == NULL)) returned_E = E; Str::copy(from, mr3.exp[1]); } } if (Str::eq(to, I"void")) { InterTypes::add_operand_to_isstd(results, T, InterTypes::from_constructor_code(VOID_ITCONC)); } else { inter_type res_type = InterTypes::parse_simple(T, eloc, to, &E); if ((E) && (returned_E == NULL)) returned_E = E; InterTypes::add_operand_to_isstd(results, T, res_type); } Regexp::dispose_of(&mr); return returned_E; } @ = if (Regexp::match(&mr, text, L"struct (%c+)")) { results->constructor_code = STRUCT_ITCONC; text_stream *elements = mr.exp[0]; inter_error_message *returned_E = NULL; match_results mr3 = Regexp::create_mr(); while (Regexp::match(&mr3, elements, L" *(%C+) *(%c*)")) { inter_type arg_type = InterTypes::parse_simple(T, eloc, mr3.exp[0], &E); if ((E) && (returned_E == NULL)) returned_E = E; Str::copy(elements, mr3.exp[1]); InterTypes::add_operand_to_isstd(results, T, arg_type); } Regexp::dispose_of(&mr); return returned_E; } @ = inter_type_constructor *itc = InterTypes::constructor_from_name(text); if (itc) { results->constructor_code = itc->constructor_ID; if (itc->constructor_ID == VOID_ITCONC) return InterErrors::quoted(I"'void' cannot be used as a type", text, eloc); Regexp::dispose_of(&mr); return NULL; } @ = inter_symbol *typename_s = NULL; if (Str::get_first_char(text) == '/') { typename_s = InterSymbolsTable::wire_to_URL( InterPackage::tree(InterSymbolsTable::package(T)), text, T); if (typename_s == NULL) return InterErrors::quoted(I"no typename at this URL", text, eloc); } else { typename_s = TextualInter::find_symbol_in_table(T, eloc, text, TYPENAME_IST, &E); } if (typename_s) { results->constructor_code = EQUATED_ITCONC; InterTypes::add_operand_to_isstd(results, T, InterTypes::from_type_name(typename_s)); Regexp::dispose_of(&mr); return NULL; } if (E) { Regexp::dispose_of(&mr); return E; } @ Sometimes we want to allow only a simple type, and if so then we can return an //inter_type//, which is less fuss. But we still write this as a wrapper around the full //InterTypes::parse_semisimple// so that it can produce a useful error message in response to a semisimple but not simple piece of syntax. = inter_type InterTypes::parse_simple(inter_symbols_table *T, inter_error_location *eloc, text_stream *text, inter_error_message **E) { if (Str::len(text) > 0) { inter_semisimple_type_description parsed_description; InterTypes::initialise_isstd(&parsed_description); *E = InterTypes::parse_semisimple(text, T, eloc, &parsed_description); if (*E) return InterTypes::unchecked(); if (parsed_description.constructor_code == EQUATED_ITCONC) { inter_type type = InterTypes::from_TID(T, parsed_description.operand_TIDs[0]); InterTypes::dispose_of_isstd(&parsed_description); return type; } int over_complex = FALSE; for (int i=0; iconstructor_keyword); switch (itc->constructor_ID) { case EQUATED_ITCONC: InterTypes::write_type(OUT, InterTypes::type_operand(type, 0)); break; case DESCRIPTION_ITCONC: case COLUMN_ITCONC: case RULEBOOK_ITCONC: case LIST_ITCONC: WRITE(" of "); InterTypes::write_type(OUT, InterTypes::type_operand(type, 0)); break; case ACTIVITY_ITCONC: WRITE(" on "); InterTypes::write_type(OUT, InterTypes::type_operand(type, 0)); break; case RELATION_ITCONC: WRITE(" of "); InterTypes::write_type(OUT, InterTypes::type_operand(type, 0)); WRITE(" to "); InterTypes::write_type(OUT, InterTypes::type_operand(type, 1)); break; case FUNCTION_ITCONC: case RULE_ITCONC: { int arity = InterTypes::type_arity(type); for (int i=0; i "); InterTypes::write_type(OUT, InterTypes::type_operand(type, i)); } break; } case STRUCT_ITCONC: { int arity = InterTypes::type_arity(type); for (int i=0; iis_enumerated) return TRUE; return FALSE; } int InterTypes::literal_is_in_range(long long int N, inter_type type) { inter_type_constructor *itc = InterTypes::constructor(type); if ((N < itc->min_value) || (N > itc->max_value)) return FALSE; return TRUE; } int InterTypes::unsigned_literal_is_in_range(long long int N, inter_type type) { inter_type_constructor *itc = InterTypes::constructor(type); if ((N < 0) || (N > itc->max_value - itc->min_value)) return FALSE; return TRUE; } @ This is what matters: whether we allow a value of type |A| to be used where a value of type |B| is expected. Anything can be used as |unchecked|, and |unchecked| can be used as anything. Otherwise, they must both be base types, or both constructed: and then we split into four cases as to whether they are the same or different. = inter_error_message *InterTypes::can_be_used_as(inter_type A, inter_type B, text_stream *S, inter_error_location *eloc) { inter_type_constructor *A_itc = InterTypes::constructor(A); inter_type_constructor *B_itc = InterTypes::constructor(B); if ((A_itc->constructor_ID == UNCHECKED_ITCONC) || (B_itc->constructor_ID == UNCHECKED_ITCONC)) return NULL; if (A_itc->is_base != B_itc->is_base) @; if (A_itc->is_base) { if (A_itc->constructor_ID != B_itc->constructor_ID) @ else @; } else { if (A_itc->constructor_ID != B_itc->constructor_ID) @ else @; } } @ This expresses that |int2 <= int8 <= int16 <= int32|. @ = switch (A_itc->constructor_ID) { case INT2_ITCONC: if ((B_itc->constructor_ID == INT8_ITCONC) || (B_itc->constructor_ID == INT16_ITCONC) || (B_itc->constructor_ID == INT32_ITCONC)) return NULL; break; case INT8_ITCONC: if ((B_itc->constructor_ID == INT16_ITCONC) || (B_itc->constructor_ID == INT32_ITCONC)) return NULL; break; case INT16_ITCONC: if (B_itc->constructor_ID == INT32_ITCONC) return NULL; break; } @; @ Enumerated types, if named, can be declared as subtypes of each other. This is used by Inform to make the enumerated type for "vehicle" a subtype of the enumerated type for "thing", for example. @ = if (A_itc->constructor_ID == ENUM_ITCONC) { inter_symbol *typenameB_s = B.type_name; inter_symbol *typenameA_s = A.type_name; if ((typenameB_s) && (typenameA_s) && (TypenameInstruction::is_a(typenameA_s, typenameB_s) == FALSE)) @; } return NULL; @ Different proper constructors never match. @ = @; @ If the same proper constructor is used, the question is then whether the operands match. For example, |list of int2| can be used as |list of int32| but not vice versa: that's a covariant operand. But |function int2 -> text| cannot be used as |function int32 -> text|, it's the other way around: that is an example of contravariance. In the simple type system of Inter, only function arguments are contravariant. @ = inter_error_message *operand_E = NULL; switch (A_itc->constructor_ID) { case STRUCT_ITCONC: case FUNCTION_ITCONC: { inter_symbol *typename_A = A.type_name; inter_symbol *typename_B = B.type_name; if ((typename_A) && (typename_B)) { if (InterTypes::type_arity(A) != InterTypes::type_arity(B)) @; int arity = InterTypes::type_arity(A); for (int i=0; iconstructor_ID == FUNCTION_ITCONC) && (i; } } break; } default: for (int i=0; iarity; i++) { operand_E = InterTypes::can_be_used_as(InterTypes::type_operand(A, i), InterTypes::type_operand(B, i), S, eloc); if (operand_E) @; } break; } return NULL; @ = text_stream *err = Str::new(); WRITE_TO(err, "value '%S' has type ", S); InterTypes::write_type(err, A); WRITE_TO(err, " which is not a "); InterTypes::write_type(err, B); return InterErrors::plain(err, eloc); @h The type of a defined symbol. Note that a typename can be used as a value, and that if so, its type is |unchecked|. = inter_type InterTypes::of_symbol(inter_symbol *symb) { if (symb == NULL) return InterTypes::unchecked(); inter_tree_node *D = InterSymbol::definition(symb); if (D == NULL) return InterTypes::unchecked(); if (InterSymbol::defined_elsewhere(symb)) return InterTypes::unchecked(); inter_construct *IC = NULL; if (InterInstruction::get_construct(D, &IC)) return InterTypes::unchecked(); if (IC->TID_field >= 0) return InterTypes::from_TID_in_field(D, IC->TID_field); return InterTypes::unchecked(); } int InterTypes::expresses_value(inter_symbol *symb) { inter_tree_node *D = InterSymbol::definition(symb); if (D) { inter_construct *IC = NULL; if (InterInstruction::get_construct(D, &IC)) return FALSE; if (IC->construct_ID == TYPENAME_IST) return TRUE; if (IC->TID_field >= 0) return TRUE; } return FALSE; }