/* Type analysis.
Copyright (C) 2025 Free Software Foundation, Inc.
GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
. */
#include "elna/boot/type_check.h"
#include
#include
namespace elna::boot
{
static char unary_operator_symbol(unary_operator operation)
{
switch (operation)
{
using enum unary_operator;
case reference:
return '@';
case negation:
case bitwise_negation:
case logical_negation:
return '~';
case minus:
return '-';
case plus:
return '+';
}
__builtin_unreachable();
}
static std::string print_binary_operator(const binary_operator operation)
{
switch (operation)
{
using enum binary_operator;
case sum:
return "+";
case subtraction:
return "-";
case multiplication:
return "*";
case division:
return "/";
case remainder:
return "%";
case equals:
return "=";
case not_equals:
return "<>";
case less:
return "<";
case less_equal:
return "<=";
case greater:
return ">";
case greater_equal:
return ">=";
case conjunction:
case logical_conjunction:
case bitwise_conjunction:
return "&";
case disjunction:
case logical_disjunction:
case bitwise_disjunction:
return "or";
case exclusive_disjunction:
case logical_exclusive_disjunction:
case bitwise_exclusive_disjunction:
return "xor";
case shift_left:
return "<<";
case shift_right:
return ">>";
}
__builtin_unreachable();
};
trait_error::trait_error(const source_position position, const std::string& trait_name,
payload_type payload)
: error(position), trait_name(trait_name), payload(std::move(payload))
{
}
std::string trait_error::what() const
{
return std::visit([this](auto&& payload) -> std::string
{
using T = std::decay_t;
if constexpr (std::is_same_v)
{
return "Trait #" + this->trait_name + " expects "
+ std::to_string(payload.expected) + " argument"
+ (payload.expected != 1 ? "s" : "") + ", got "
+ std::to_string(payload.actual);
}
else if constexpr (std::is_same_v)
{
return "The second argument to the #" + this->trait_name
+ " trait must be a field name";
}
else if constexpr (std::is_same_v)
{
return "Type '" + payload.actual.to_string()
+ "' does not support trait '#" + this->trait_name + "'";
}
}, this->payload);
}
type_mismatch_error::type_mismatch_error(const source_position position,
type actual, payload_type payload)
: error(position), actual(std::move(actual)), payload(std::move(payload))
{
}
std::string type_mismatch_error::what() const
{
return std::visit([this](auto&& payload) -> std::string
{
using T = std::decay_t;
if constexpr (std::is_same_v)
{
return "Expected type '" + payload.value.to_string()
+ "', but got '" + this->actual.to_string() + "'";
}
else if constexpr (std::is_same_v)
{
if (!this->actual.empty())
{
return "Procedure '" + payload.identifier
+ "' does not return a value, but return expression has type '"
+ this->actual.to_string() + "'";
}
return "Procedure '" + payload.identifier
+ "' is expected to return, but does not have a return statement";
}
else if constexpr (std::is_same_v)
{
return "Type '" + this->actual.to_string() + "' cannot be used with unary '"
+ unary_operator_symbol(payload.operation) + "'";
}
else if constexpr (std::is_same_v)
{
return "Invalid operands of type '" + this->actual.to_string()
+ "' and '" + payload.right.to_string()
+ "' for operator " + print_binary_operator(payload.operation);
}
else if constexpr (std::is_same_v)
{
return "Type '" + this->actual.to_string()
+ "' cannot be converted to '" + payload.target.to_string() + "'";
}
else if constexpr (std::is_same_v)
{
switch (payload)
{
case kind::record_base:
return "Expected a record type, but got '"
+ this->actual.to_string() + "'";
case kind::for_range:
return "Expected an array or slice type, but got '"
+ this->actual.to_string() + "'";
case kind::condition:
return "Condition must be a boolean expression, but got '"
+ this->actual.to_string() + "'";
case kind::constant_assignment:
return "Cannot assign to a value of type '" + this->actual.to_string()
+ "', because it is constant or contains constant members";
case kind::array_index:
return "Array index must be an integral type, but got '"
+ this->actual.to_string() + "'";
case kind::non_indexable:
return "Indexing is not allowed on type '"
+ this->actual.to_string() + "'";
case kind::dereference_of_non_pointer:
return "Type '" + this->actual.to_string()
+ "' cannot be dereferenced, it is not a pointer";
default:
__builtin_unreachable();
}
}
}, this->payload);
}
cyclic_declaration_error::cyclic_declaration_error(const std::vector& cycle,
const source_position position)
: error(position), cycle(cycle)
{
}
std::string cyclic_declaration_error::what() const
{
auto segment = std::cbegin(this->cycle);
std::string message = "Type declaration forms a cycle: " + *segment;
++segment;
for (; segment != std::cend(this->cycle); ++segment)
{
message += " -> " + *segment;
}
return message;
}
argument_count_error::argument_count_error(std::size_t expected, std::size_t actual,
const source_position position)
: error(position), expected(expected), actual(actual)
{
}
std::string argument_count_error::what() const
{
if (actual > expected)
{
return "Too many arguments, expected " + std::to_string(expected)
+ ", got " + std::to_string(actual);
}
else
{
return "Too few arguments, expected " + std::to_string(expected)
+ ", got " + std::to_string(actual);
}
}
static bool contains_constant_member(const type& checked)
{
auto referent = resolve_aliases(checked);
if (referent.get() != nullptr)
{
return true;
}
else if (auto record = referent.get())
{
for (const auto& [field_name, field_type] : record->fields)
{
if (contains_constant_member(field_type))
{
return true;
}
}
return !record->base.empty()
&& contains_constant_member(resolve_underlying_type(record->base));
}
else if (auto array = referent.get())
{
return contains_constant_member(array->base);
}
return false;
}
bool type_analysis_visitor::is_equality_compatible(const type& left, const type& right)
{
auto resolved_left = resolve_underlying_type(left);
auto resolved_right = resolve_underlying_type(right);
return resolved_left == resolved_right
|| (is_primitive_type(resolved_left, "Pointer") && is_any_pointer_type(resolved_right))
|| (is_any_pointer_type(resolved_left) && is_primitive_type(resolved_right, "Pointer"))
|| (resolved_left.get() && resolved_right.get())
|| (resolved_left.get() && resolved_right.get());
}
bool type_analysis_visitor::check_unresolved_symbol(const std::shared_ptr& alias,
std::vector& alias_path)
{
if (std::ranges::find(alias_path, alias->name) != std::cend(alias_path))
{
return false;
}
alias_path.push_back(alias->name);
if (auto another_alias = alias->referent.get())
{
return check_unresolved_symbol(another_alias, alias_path);
}
return true;
}
void type_analysis_visitor::visit_and_validate_condition(expression& condition)
{
condition.accept(this);
if (!is_primitive_type(condition.type_decoration, "Bool"))
{
add_error(condition.position(),
condition.type_decoration, type_mismatch_error::kind::condition);
}
}
/*
* Checks whether derived has base in its record parent chain.
*
* Base record type must not be null. Derived record type may be null.
*/
static bool is_base_of(const std::shared_ptr& base,
const std::shared_ptr& derived)
{
if (derived != nullptr)
{
if (auto current_record = resolve_underlying_type(derived->base).get())
{
return current_record == base || is_base_of(base, current_record);
}
}
return false;
}
assign_check::verdict assign_check::guard_const_laundering() const
{
if (!is_primitive_type(ctx.aliased_assignee, "Pointer"))
{
return verdict::pass;
}
if (auto ptr = ctx.aliased_assignment.get();
ptr != nullptr && resolve_aliases(ptr->base).get() != nullptr)
{
return verdict::reject;
}
if (auto const_assign = ctx.aliased_assignment.get();
const_assign != nullptr && is_primitive_type(const_assign->unqualified, "Pointer"))
{
return verdict::reject;
}
return verdict::pass;
}
assign_check::verdict assign_check::check_exact_match() const
{
return ctx.resolved_assignee == ctx.resolved_assignment ? verdict::accept : verdict::pass;
}
assign_check::verdict assign_check::check_pointer_hatch() const
{
if (is_primitive_type(ctx.resolved_assignee, "Pointer")
&& is_any_pointer_type(ctx.resolved_assignment))
{
return verdict::accept;
}
if (is_primitive_type(ctx.resolved_assignment, "Pointer")
&& is_any_pointer_type(ctx.resolved_assignee))
{
return verdict::accept;
}
return verdict::pass;
}
assign_check::verdict assign_check::check_pointer_conversion() const
{
auto assignee_ptr = ctx.resolved_assignee.get();
auto assignment_ptr = ctx.resolved_assignment.get();
if (assignee_ptr == nullptr || assignment_ptr == nullptr)
{
return verdict::reject;
}
auto assignee_pointee_const = resolve_aliases(assignee_ptr->base).get();
auto assignment_pointee_const = resolve_aliases(assignment_ptr->base).get();
// Constness can be added at the first indirection level, but not removed.
if (assignee_pointee_const != nullptr
&& assignee_pointee_const->unqualified == assignment_ptr->base)
{
return verdict::accept;
}
if (assignment_pointee_const != nullptr && assignee_pointee_const == nullptr)
{
return verdict::reject;
}
// A pointer to a record can be assigned to a pointer to its base type.
if (auto assignee_record = resolve_underlying_type(assignee_ptr->base).get())
{
return is_base_of(assignee_record,
resolve_underlying_type(assignment_ptr->base).get())
? verdict::accept : verdict::pass;
}
return verdict::pass;
}
assign_check::verdict assign_check::check_slice_conversion() const
{
auto assignee_slice = ctx.resolved_assignee.get();
auto assignment_slice = ctx.resolved_assignment.get();
if (assignee_slice == nullptr || assignment_slice == nullptr)
{
return verdict::pass;
}
auto assignee_element_const = resolve_aliases(assignee_slice->base).get();
auto assignment_element_const = resolve_aliases(assignment_slice->base).get();
// Const can be added to the element type, but not removed.
if (assignee_element_const != nullptr
&& assignee_element_const->unqualified == assignment_slice->base)
{
return verdict::accept;
}
if (assignment_element_const != nullptr && assignee_element_const == nullptr)
{
return verdict::reject;
}
return verdict::pass;
}
bool assign_check::run()
{
for (auto handler : {&assign_check::guard_const_laundering,
&assign_check::check_exact_match,
&assign_check::check_slice_conversion,
&assign_check::check_pointer_hatch,
&assign_check::check_pointer_conversion})
{
switch ((this->*handler)())
{
case verdict::accept: return true;
case verdict::reject: return false;
case verdict::pass:;
}
}
return false;
}
bool type_analysis_visitor::is_assignable_from(const type& assignee, const type& assignment)
{
return assign_check{
resolve_aliases(assignee),
resolve_aliases(assignment),
resolve_underlying_type(assignee),
resolve_underlying_type(assignment)
}.run();
}
type_analysis_visitor::type_analysis_visitor(symbol_bag bag, const target_info& target)
: bag(std::move(bag)), target(target)
{
}
void type_analysis_visitor::visit(procedure_declaration *declaration)
{
this->current_procedure = this->bag.lookup(declaration->identifier.name())->is_procedure();
if (declaration->body.has_value())
{
this->bag.enter(this->current_procedure->scope);
}
walking_visitor::visit(declaration);
if (declaration->body.has_value())
{
if (declaration->body.value().return_expression != nullptr)
{
const expression *return_expr = declaration->body.value().return_expression;
type const return_type = this->current_procedure->symbol.return_type.proper_type;
if (!return_type.empty())
{
if (!is_assignable_from(return_type, return_expr->type_decoration))
{
add_error(return_expr->position(),
return_expr->type_decoration,
type_mismatch_error::expected_type{ return_type });
}
}
else
{
add_error(return_expr->position(),
return_expr->type_decoration,
type_mismatch_error::return_type{ .identifier = declaration->identifier.name() });
}
}
else if (declaration->heading().return_type.proper_type != nullptr)
{
add_error(declaration->position(), type(),
type_mismatch_error::return_type{ .identifier = declaration->identifier.name() });
}
this->bag.leave();
}
this->current_procedure.reset();
}
void type_analysis_visitor::visit(unit *unit)
{
walking_visitor::visit(unit);
}
void type_analysis_visitor::visit(assign_statement *statement)
{
walking_visitor::visit(statement);
if (contains_constant_member(statement->lvalue().type_decoration))
{
add_error(statement->position(), statement->lvalue().type_decoration,
type_mismatch_error::kind::constant_assignment);
}
else if (!is_assignable_from(statement->lvalue().type_decoration, statement->rvalue().type_decoration))
{
add_error(statement->position(),
statement->rvalue().type_decoration,
type_mismatch_error::expected_type{ statement->lvalue().type_decoration });
}
}
void type_analysis_visitor::visit(variable_declaration *declaration)
{
walking_visitor::visit(declaration);
if (declaration->initializer == nullptr || has_errors())
{
return;
}
for (const identifier_definition& variable_identifier : declaration->identifiers)
{
auto variable_symbol = this->bag.lookup(variable_identifier.name())->is_variable();
if (!is_assignable_from(variable_symbol->symbol, declaration->initializer->type_decoration))
{
add_error(declaration->initializer->position(),
declaration->initializer->type_decoration,
type_mismatch_error::expected_type{ variable_symbol->symbol });
}
}
}
void type_analysis_visitor::visit(case_statement *statement)
{
walking_visitor::visit(statement);
type const condition_type = resolve_underlying_type(statement->condition().type_decoration);
for (const switch_case& case_block : statement->cases)
{
for (const expression *case_label : case_block.labels)
{
if (!is_equality_compatible(condition_type, case_label->type_decoration))
{
type_mismatch_error::binary binary_error{
.right = case_label->type_decoration,
.operation = binary_operator::equals
};
add_error(case_label->position(), condition_type, binary_error);
}
}
}
}
void type_analysis_visitor::visit(for_statement *statement)
{
statement->range().accept(this);
auto resolved_range = resolve_underlying_type(statement->range().type_decoration);
if (!get_range_base_type(resolved_range))
{
add_error(statement->range().position(),
statement->range().type_decoration, type_mismatch_error::kind::for_range);
}
this->bag.enter(statement->symbols);
for (auto *body_statement : statement->body)
{
body_statement->accept(this);
}
this->bag.leave();
}
void type_analysis_visitor::visit(repeat_statement *statement)
{
visit_and_validate_condition(statement->condition());
for (auto *body_statement : statement->body)
{
body_statement->accept(this);
}
}
void type_analysis_visitor::visit(while_statement *statement)
{
visit_and_validate_condition(statement->branch().prerequisite());
for (auto *branch_statement : statement->branch().statements)
{
branch_statement->accept(this);
}
for (conditional_statements *branch : statement->branches)
{
visit_and_validate_condition(branch->prerequisite());
for (auto *branch_statement : branch->statements)
{
branch_statement->accept(this);
}
}
}
void type_analysis_visitor::visit(if_statement *statement)
{
visit_and_validate_condition(statement->branch().prerequisite());
for (auto *branch_statement : statement->branch().statements)
{
branch_statement->accept(this);
}
for (conditional_statements *branch : statement->branches)
{
visit_and_validate_condition(branch->prerequisite());
for (auto *branch_statement : branch->statements)
{
branch_statement->accept(this);
}
}
if (statement->alternative != nullptr)
{
for (auto *branch_statement : *statement->alternative)
{
branch_statement->accept(this);
}
}
}
void type_analysis_visitor::visit(type_declaration *declaration)
{
std::vector alias_path;
auto unresolved_type = this->bag.lookup(declaration->identifier.name())->is_type()->symbol.get();
if (!check_unresolved_symbol(unresolved_type, alias_path))
{
add_error(alias_path, declaration->position());
}
else
{
walking_visitor::visit(declaration);
}
}
void type_analysis_visitor::visit(record_type_expression *expression)
{
if (expression->base.has_value())
{
auto const base_symbol = this->bag.lookup(expression->base.value().name());
if (base_symbol == nullptr || base_symbol->is_type() == nullptr)
{
add_error(expression->position(),
type(), type_mismatch_error::kind::record_base);
}
else
{
type const base_type = resolve_underlying_type(base_symbol->is_type()->symbol);
if (base_type.get() == nullptr)
{
add_error(expression->position(),
base_type, type_mismatch_error::kind::record_base);
}
}
}
walking_visitor::visit(expression);
}
void type_analysis_visitor::visit(procedure_call *call)
{
call->callable().accept(this);
if (auto procedure = call->callable().type_decoration.get())
{
std::vector::const_iterator argument_iterator = std::cbegin(call->arguments);
std::vector::const_iterator type_iterator = std::cbegin(procedure->parameters);
while (argument_iterator != std::cend(call->arguments)
&& type_iterator != std::cend(procedure->parameters))
{
(*argument_iterator)->accept(this);
if (!is_assignable_from(*type_iterator, (*argument_iterator)->type_decoration))
{
add_error(
(*argument_iterator)->position(),
(*argument_iterator)->type_decoration,
type_mismatch_error::expected_type{ *type_iterator });
}
++argument_iterator;
++type_iterator;
}
if (call->arguments.size() != procedure->parameters.size())
{
add_error(procedure->parameters.size(),
call->arguments.size(), call->position());
}
}
else if (!call->callable().type_decoration.empty())
{
add_error(call->position(),
call->callable().type_decoration,
type_mismatch_error::expected_type{ type(std::make_shared()) });
}
// else callable is not declared which is already reported.
}
void type_analysis_visitor::visit(record_constructor_expression *expression)
{
auto record = resolve_underlying_type(expression->type_decoration).get();
if (record == nullptr)
{
add_error(
expression->position(), expression->type_decoration,
type_mismatch_error::expected_type{ type(std::make_shared()) });
return;
}
for (const field_initializer& initializer : expression->field_initializers)
{
for (const auto& [field_name, field_type]: record->fields)
{
if (field_name == initializer.name())
{
if (!is_assignable_from(field_type, initializer.value().type_decoration))
{
add_error(
initializer.value().position(), initializer.value().type_decoration, type_mismatch_error::expected_type{field_type});
}
break;
}
}
}
}
void type_analysis_visitor::visit(array_constructor_expression *expression)
{
auto array = resolve_underlying_type(expression->type_decoration).get();
if (array == nullptr)
{
add_error(
expression->position(), expression->type_decoration,
type_mismatch_error::expected_type{ type(std::make_shared(type(), 0)) });
return;
}
if (expression->elements.size() > array->size)
{
add_error(array->size, expression->elements.size(),
expression->position());
return;
}
for (auto *element : expression->elements)
{
if (!is_assignable_from(array->base, element->type_decoration))
{
add_error(
element->position(), element->type_decoration,
type_mismatch_error::expected_type{ array->base });
}
}
}
void type_analysis_visitor::visit(slicing_expression *expression)
{
walking_visitor::visit(expression);
}
void type_analysis_visitor::visit(array_access_expression *expression)
{
walking_visitor::visit(expression);
auto resolved_base = resolve_underlying_type(expression->base().type_decoration);
if (resolved_base.get() == nullptr
&& resolved_base.get() == nullptr)
{
add_error(expression->position(),
expression->base().type_decoration, type_mismatch_error::kind::non_indexable);
}
if (!is_integral_type(resolve_underlying_type(expression->index().type_decoration)))
{
add_error(expression->index().position(),
expression->index().type_decoration, type_mismatch_error::kind::array_index);
}
}
void type_analysis_visitor::visit(unary_expression *expression)
{
walking_visitor::visit(expression);
auto operation = expression->operation();
type const resolved = resolve_underlying_type(expression->operand().type_decoration);
if (operation == unary_operator::plus)
{
if (!is_numeric_type(resolved))
{
add_error(expression->position(),
expression->operand().type_decoration,
type_mismatch_error::unary{ .operation = operation });
}
}
else if (operation == unary_operator::minus)
{
if (!is_primitive_type(resolved, "Int")
&& !is_primitive_type(resolved, "Float"))
{
add_error(expression->position(),
expression->operand().type_decoration,
type_mismatch_error::unary{ .operation = operation });
}
}
else if (operation == unary_operator::negation)
{
if (is_primitive_type(resolved, "Bool"))
{
expression->operation(unary_operator::logical_negation);
}
else if (is_integral_type(resolved))
{
expression->operation(unary_operator::bitwise_negation);
}
else
{
add_error(expression->position(),
expression->operand().type_decoration,
type_mismatch_error::unary{ .operation = operation });
}
}
else if (operation == unary_operator::reference)
{
auto *designator = expression->operand().is_designator();
if (designator == nullptr || designator->is_slicing() != nullptr)
{
add_error(expression->position(),
expression->operand().type_decoration,
type_mismatch_error::unary{ .operation = operation });
}
}
}
void type_analysis_visitor::visit(dereference_expression *expression)
{
walking_visitor::visit(expression);
if (resolve_underlying_type(expression->base().type_decoration).get() == nullptr)
{
add_error(expression->position(),
expression->base().type_decoration,
type_mismatch_error::kind::dereference_of_non_pointer);
}
}
void type_analysis_visitor::visit(cast_expression *expression)
{
walking_visitor::visit(expression);
auto source = resolve_underlying_type(expression->value().type_decoration);
auto target = resolve_underlying_type(expression->type_decoration);
if (source != target // const cast.
&& (!is_scalar_type(source) || !is_scalar_type(target))
&& (source.get() == nullptr || target.get() == nullptr))
{
add_error(expression->position(),
expression->value().type_decoration,
type_mismatch_error::invalid_cast{ expression->type_decoration });
}
}
void type_analysis_visitor::visit(binary_expression *expression)
{
walking_visitor::visit(expression);
auto operation = expression->operation();
type const lhs_resolved = resolve_underlying_type(expression->lhs().type_decoration);
type const rhs_resolved = resolve_underlying_type(expression->rhs().type_decoration);
bool valid = false;
switch (operation)
{
using enum binary_operator;
case sum:
valid = (is_any_pointer_type(lhs_resolved) && is_integral_type(rhs_resolved))
|| (is_integral_type(lhs_resolved) && is_any_pointer_type(rhs_resolved))
|| (is_numeric_type(lhs_resolved) && lhs_resolved == rhs_resolved);
break;
case subtraction:
valid = (is_any_pointer_type(lhs_resolved) && is_integral_type(rhs_resolved))
|| (is_any_pointer_type(lhs_resolved) && is_any_pointer_type(rhs_resolved))
|| (is_numeric_type(lhs_resolved) && lhs_resolved == rhs_resolved);
break;
case division:
case remainder:
case multiplication:
valid = is_numeric_type(lhs_resolved) && lhs_resolved == rhs_resolved;
break;
case less:
case greater:
case less_equal:
case greater_equal:
valid = (is_numeric_type(lhs_resolved) && lhs_resolved == rhs_resolved)
|| (is_any_pointer_type(lhs_resolved) && is_any_pointer_type(rhs_resolved));
break;
case conjunction:
case disjunction:
case exclusive_disjunction:
if (is_primitive_type(lhs_resolved, "Bool") && lhs_resolved == rhs_resolved)
{
switch (operation)
{
case conjunction:
expression->operation(logical_conjunction);
break;
case disjunction:
expression->operation(logical_disjunction);
break;
case exclusive_disjunction:
expression->operation(logical_exclusive_disjunction);
break;
default:
break;
}
valid = true;
}
else if (is_integral_type(lhs_resolved) && lhs_resolved == rhs_resolved)
{
switch (operation)
{
case conjunction:
expression->operation(bitwise_conjunction);
break;
case disjunction:
expression->operation(bitwise_disjunction);
break;
case exclusive_disjunction:
expression->operation(bitwise_exclusive_disjunction);
break;
default:
break;
}
valid = true;
}
break;
case equals:
case not_equals:
valid = is_equality_compatible(lhs_resolved, rhs_resolved);
break;
case shift_left:
case shift_right:
valid = is_integral_type(lhs_resolved)
&& is_primitive_type(rhs_resolved, "Word");
break;
default:
__builtin_unreachable();
}
if (!valid)
{
type_mismatch_error::binary binary_error{
.right = expression->rhs().type_decoration,
.operation = operation
};
add_error(expression->position(), expression->lhs().type_decoration, binary_error);
}
}
void type_analysis_visitor::visit(traits_expression *trait)
{
walking_visitor::visit(trait);
if (trait->name == "size" || trait->name == "alignment")
{
if (trait->arguments.size() != 1)
{
add_error(trait->position(), trait->name.name(),
trait_error::argument_count{ .expected = 1, .actual = trait->arguments.size() });
}
}
else if ((trait->name == "min" || trait->name == "max") && !trait->type_decoration.empty())
{
if (trait->arguments.size() != 1)
{
add_error(trait->position(), trait->name.name(),
trait_error::argument_count{ .expected = 1, .actual = trait->arguments.size() });
}
else
{
const type resolved = resolve_underlying_type(trait->type_decoration);
if (resolved.get() == nullptr
&& !is_primitive_type(resolved, "Float")
&& !is_discrete_type(resolved))
{
add_error(trait->name.position(), trait->name.name(),
trait_error::unsupported_type{ trait->type_decoration });
}
}
}
else if (trait->name == "offset")
{
if (trait->arguments.size() != 2)
{
add_error(trait->position(), trait->name.name(),
trait_error::argument_count{ .expected = 2, .actual = trait->arguments.size() });
}
else if (trait->arguments.at(1)->is_named() == nullptr)
{
add_error(trait->arguments.at(1)->position(), trait->name.name(),
trait_error::offset_not_field_name{});
}
}
}
}