Skip to content

Commit

Permalink
Merge pull request #528 from ehaas/type-eql
Browse files Browse the repository at this point in the history
Type: improve type equality checking
  • Loading branch information
Vexu authored Oct 24, 2023
2 parents afc485d + e9f83cb commit d8c26ad
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 59 deletions.
9 changes: 8 additions & 1 deletion src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6342,7 +6342,14 @@ fn typesCompatible(p: *Parser) Error!Result {

try p.expectClosing(l_paren, .r_paren);

const compatible = first.compatible(second, p.comp);
var first_unqual = first.canonicalize(.standard);
first_unqual.qual.@"const" = false;
first_unqual.qual.@"volatile" = false;
var second_unqual = second.canonicalize(.standard);
second_unqual.qual.@"const" = false;
second_unqual.qual.@"volatile" = false;

const compatible = first_unqual.eql(second_unqual, p.comp, true);

var res = Result{
.val = Value.int(@intFromBool(compatible)),
Expand Down
6 changes: 3 additions & 3 deletions src/SymbolStack.zig
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ pub fn defineSymbol(
},
.decl => if (names[i] == name) {
const prev_ty = s.syms.items(.ty)[i];
if (!ty.eql(prev_ty, p.comp, true)) { // TODO adjusted equality check
if (!ty.eql(prev_ty, p.comp, true)) {
try p.errStr(.redefinition_incompatible, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, s.syms.items(.tok)[i]);
}
Expand Down Expand Up @@ -243,15 +243,15 @@ pub fn declareSymbol(
},
.decl => if (names[i] == name) {
const prev_ty = s.syms.items(.ty)[i];
if (!ty.eql(prev_ty, p.comp, true)) { // TODO adjusted equality check
if (!ty.eql(prev_ty, p.comp, true)) {
try p.errStr(.redefinition_incompatible, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, s.syms.items(.tok)[i]);
}
break;
},
.def, .constexpr => if (names[i] == name) {
const prev_ty = s.syms.items(.ty)[i];
if (!ty.eql(prev_ty, p.comp, true)) { // TODO adjusted equality check
if (!ty.eql(prev_ty, p.comp, true)) {
try p.errStr(.redefinition_incompatible, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, s.syms.items(.tok)[i]);
break;
Expand Down
96 changes: 54 additions & 42 deletions src/Type.zig
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,35 @@ pub const Func = struct {
name: StringId,
name_tok: TokenIndex,
};

fn eql(a: *const Func, b: *const Func, a_var_args: bool, b_var_args: bool, comp: *const Compilation) bool {
// return type cannot have qualifiers
if (!a.return_type.eql(b.return_type, comp, false)) return false;

if (a.params.len != b.params.len) {
const a_no_proto = a_var_args and a.params.len == 0 and !comp.langopts.standard.atLeast(.c2x);
const b_no_proto = b_var_args and b.params.len == 0 and !comp.langopts.standard.atLeast(.c2x);
if (a_no_proto or b_no_proto) {
const maybe_has_params = if (a_no_proto) b else a;
for (maybe_has_params.params) |param| {
if (param.ty.undergoesDefaultArgPromotion(comp)) return false;
}
return true;
}
}
if (a_var_args != b_var_args) return false;
// TODO validate this
for (a.params, b.params) |param, b_qual| {
var a_unqual = param.ty;
a_unqual.qual.@"const" = false;
a_unqual.qual.@"volatile" = false;
var b_unqual = b_qual.ty;
b_unqual.qual.@"const" = false;
b_unqual.qual.@"volatile" = false;
if (!a_unqual.eql(b_unqual, comp, true)) return false;
}
return true;
}
};

pub const Array = struct {
Expand Down Expand Up @@ -447,6 +476,22 @@ pub fn isArray(ty: Type) bool {
};
}

/// Whether the type is promoted if used as a variadic argument or as an argument to a function with no prototype
fn undergoesDefaultArgPromotion(ty: Type, comp: *const Compilation) bool {
return switch (ty.specifier) {
.bool => true,
.char, .uchar, .schar => true,
.short, .ushort => true,
.@"enum" => if (comp.langopts.emulate == .clang) ty.data.@"enum".isIncomplete() else false,
.float => true,

.typeof_type => ty.data.sub_type.undergoesDefaultArgPromotion(comp),
.typeof_expr => ty.data.expr.ty.undergoesDefaultArgPromotion(comp),
.attributed => ty.data.attributed.base.undergoesDefaultArgPromotion(comp),
else => false,
};
}

pub fn isScalar(ty: Type) bool {
return ty.isInt() or ty.isScalarNonInt();
}
Expand Down Expand Up @@ -1194,31 +1239,6 @@ pub fn annotationAlignment(comp: *const Compilation, attrs: ?[]const Attribute)
return max_requested;
}

/// Checks type compatibility for __builtin_types_compatible_p
/// Returns true if the unqualified version of `a_param` and `b_param` are the same
/// Ignores top-level qualifiers (e.g. `int` and `const int` are compatible) but `int *` and `const int *` are not
/// Two types that are typedefed are considered compatible if their underlying types are compatible.
/// An enum type is not considered to be compatible with another enum type even if both are compatible with the same integer type;
/// `A[]` and `A[N]` for a type `A` and integer `N` are compatible
pub fn compatible(a_param: Type, b_param: Type, comp: *const Compilation) bool {
var a_unqual = a_param.canonicalize(.standard);
a_unqual.qual.@"const" = false;
a_unqual.qual.@"volatile" = false;
var b_unqual = b_param.canonicalize(.standard);
b_unqual.qual.@"const" = false;
b_unqual.qual.@"volatile" = false;

if (a_unqual.eql(b_unqual, comp, true)) return true;
if (!a_unqual.isArray() or !b_unqual.isArray()) return false;

if (a_unqual.arrayLen() == null or b_unqual.arrayLen() == null) {
// incomplete arrays are compatible with arrays of the same element type
// GCC and clang ignore cv-qualifiers on arrays
return a_unqual.elemType().compatible(b_unqual.elemType(), comp);
}
return false;
}

pub fn eql(a_param: Type, b_param: Type, comp: *const Compilation, check_qualifiers: bool) bool {
const a = a_param.canonicalize(.standard);
const b = b_param.canonicalize(.standard);
Expand Down Expand Up @@ -1251,29 +1271,21 @@ pub fn eql(a_param: Type, b_param: Type, comp: *const Compilation, check_qualifi
.func,
.var_args_func,
.old_style_func,
=> {
// TODO validate this
if (a.data.func.params.len != b.data.func.params.len) return false;
// return type cannot have qualifiers
if (!a.returnType().eql(b.returnType(), comp, false)) return false;
for (a.data.func.params, b.data.func.params) |param, b_qual| {
var a_unqual = param.ty;
a_unqual.qual.@"const" = false;
a_unqual.qual.@"volatile" = false;
var b_unqual = b_qual.ty;
b_unqual.qual.@"const" = false;
b_unqual.qual.@"volatile" = false;
if (!a_unqual.eql(b_unqual, comp, check_qualifiers)) return false;
}
},
=> if (!a.data.func.eql(b.data.func, a.specifier == .var_args_func, b.specifier == .var_args_func, comp)) return false,

.array,
.static_array,
.incomplete_array,
.vector,
=> {
if (!std.meta.eql(a.arrayLen(), b.arrayLen())) return false;
if (!a.elemType().eql(b.elemType(), comp, check_qualifiers)) return false;
const a_len = a.arrayLen();
const b_len = b.arrayLen();
if (a_len == null or b_len == null) {
// At least one array is incomplete; only check child type for equality
} else if (a_len.? != b_len.?) {
return false;
}
if (!a.elemType().eql(b.elemType(), comp, false)) return false;
},
.variable_len_array => if (!a.elemType().eql(b.elemType(), comp, check_qualifiers)) return false,

Expand Down
17 changes: 10 additions & 7 deletions test/cases/invalid types.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ int bar["foo"];
int baz[] = 111111E1111111111111;
int qux[] = baz;

// int g[];
// extern int h[];
// void foo(void) {
// int i[];
// char j[] = "hello";
// }
int g[];
extern int h[];
void foo(void) {
int i[];
char j[] = "hello";
}

#define TESTS_SKIPPED 7
#define EXPECTED_ERRORS \
"invalid types.c:1:6: error: cannot combine with previous 'long' specifier" \
"invalid types.c:2:14: warning: duplicate 'atomic' declaration specifier [-Wduplicate-decl-specifier]" \
Expand All @@ -45,11 +44,15 @@ int qux[] = baz;
"invalid types.c:8:8: note: forward declaration of 'struct Bar'" \
"invalid types.c:9:6: error: array is too large" \
"invalid types.c:11:1: warning: plain '_Complex' requires a type specifier; assuming '_Complex double'" \
"invalid types.c:14:6: note: previous definition is here" \
"invalid types.c:16:15: error: expected identifier or '('" \
"invalid types.c:17:17: error: array has incomplete element type 'struct bar'" \
"invalid types.c:18:15: error: expected identifier or '('" \
"invalid types.c:19:17: error: array has incomplete element type 'struct bar []'" \
"invalid types.c:22:9: error: size of array has non-integer type 'char [4]'" \
"invalid types.c:23:13: error: array initializer must be an initializer list or wide string literal" \
"invalid types.c:24:13: error: array initializer must be an initializer list or wide string literal" \
"invalid types.c:26:5: warning: tentative array definition assumed to have one element" \
"invalid types.c:28:6: error: redefinition of 'foo'" \
"invalid types.c:29:9: error: variable has incomplete type 'int []'" \

26 changes: 23 additions & 3 deletions test/cases/redefinitions.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,24 @@ typedef MyFloat MyInt;
typedef int MyOtherInt;
typedef const int MyOtherInt;

#define TESTS_SKIPPED 1
// int f(int (*)(), double (*)[3]);
// int f(int (*)(char *), double (*)[]);
int f(int (*)(), double (*)[3]);
int f(int (*)(char *), double (*)[]);

double maximum(int n, int m, double a[n][m]);
double maximum(int n, int m, double a[*][*]);
double maximum(int n, int m, double a[ ][*]);
double maximum(int n, int m, double a[ ][m]);

void f3(double (* restrict a)[5]);
void f3(double a[restrict][5]);
void f3(double a[restrict 3][5]);
void f3(double a[restrict static 3][5]);

int f4(int, ...);
int f4(int);

int f5(int (*)(), double (*)[3]);
int f5(int (*)(char), double (*)[]); // not compatible since char undergoes default argument promotion

#define EXPECTED_ERRORS "redefinitions.c:4:5: error: redefinition of 'foo' as different kind of symbol" \
"redefinitions.c:1:5: note: previous definition is here" \
Expand Down Expand Up @@ -105,3 +120,8 @@ typedef const int MyOtherInt;
"redefinitions.c:62:13: note: previous definition is here" \
"redefinitions.c:67:19: error: typedef redefinition with different types ('const int' vs 'int')" \
"redefinitions.c:66:13: note: previous definition is here" \
"redefinitions.c:83:5: error: redefinition of 'f4' with a different type" \
"redefinitions.c:82:5: note: previous definition is here" \
"redefinitions.c:86:5: error: redefinition of 'f5' with a different type" \
"redefinitions.c:85:5: note: previous definition is here" \

5 changes: 2 additions & 3 deletions test/cases/tentative array.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ int arr[];

_Static_assert(sizeof arr,"");

int arr[3]; // TODO should make the error go away
int arr[3]; // TODO should make the warning go away

#define SKIPPED_TESTS 1

#define EXPECTED_ERRORS \
"tentative array.c:1:5: warning: tentative array definition assumed to have one element" \
"tentative array.c:3:16: error: invalid application of 'sizeof' to an incomplete type 'int []'" \
"tentative array.c:5:5: error: redefinition of 'arr' with a different type" \
"tentative array.c:1:5: note: previous definition is here" \

0 comments on commit d8c26ad

Please sign in to comment.