mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-20 10:20:10 +01:00
util: add a newtype macro
In a valiant approach to introduce some type-safety (after spending time debugging a int vs double confusion) this adds a DECLARE_NEWTYPE() macro that declares a named struct with a single typed value field. This is basically the C version of Rusts "struct Foo(u32)" with a few accessors auto-generated by the macro. C is happy to silently convert between base types but it doesn't do so for structs so this allows us to have some type safety when we accidentally assign two incompatible fields to each other (e.g. an axis value in device units vs a percentage value). Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1171>
This commit is contained in:
parent
000d9282cd
commit
a55dd604e1
2 changed files with 191 additions and 0 deletions
110
src/util-newtype.h
Normal file
110
src/util-newtype.h
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2025 Red Hat, Inc.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the "Software"),
|
||||||
|
* to deal in the Software without restriction, including without limitation
|
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice (including the next
|
||||||
|
* paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
* Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "util-macros.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a C version of the Rust newtypes in the
|
||||||
|
* form struct Foo(u32);
|
||||||
|
*
|
||||||
|
* Use: DECLARE_NEWTYPE(foo, int)
|
||||||
|
*
|
||||||
|
* Defines a single-value struct called foo_t
|
||||||
|
* with the following helper functions:
|
||||||
|
*
|
||||||
|
* - int foo_as_int(foo_t f);
|
||||||
|
* - int foo(foo f);
|
||||||
|
* - foo_t foo_from_int(int);
|
||||||
|
* - foo_t foo_copy(foo_t f);
|
||||||
|
* - foo_t foo_min(foo_t a, foo b);
|
||||||
|
* - foo_t foo_max(foo_t a, foo b);
|
||||||
|
* - foo_t foo_cmp(foo_t a, foo b);
|
||||||
|
* - bool foo_eq(foo_t a, int b);
|
||||||
|
* - bool foo_ne(foo_t a, int b);
|
||||||
|
* - bool foo_le(foo_t a, int b);
|
||||||
|
* - bool foo_lt(foo_t a, int b);
|
||||||
|
* - bool foo_ge(foo_t a, int b);
|
||||||
|
* - bool foo_gt(foo_t a, int b);
|
||||||
|
*
|
||||||
|
* Since all the structs are single-value the provided functions don't
|
||||||
|
* use pointers but pass the struct around as value. The compiler
|
||||||
|
* hopefully optimizes things so this is the same as passing ints around
|
||||||
|
* but having some better type-safety.
|
||||||
|
*
|
||||||
|
* For example, this logic error is no longer possible using newtypes:
|
||||||
|
* ```
|
||||||
|
* double cm_to_inches(int cm) { return cm / 2.54; }
|
||||||
|
*
|
||||||
|
* struct person {
|
||||||
|
* int age;
|
||||||
|
* int height;
|
||||||
|
* };
|
||||||
|
* struct person p = { .age = 20, .height = 180 };
|
||||||
|
* double inches = cm_to_inches(p.age);
|
||||||
|
* ```
|
||||||
|
* With newtypes this would be:
|
||||||
|
* ```
|
||||||
|
* DECLARE_NEWTYPE(year, int);
|
||||||
|
* DECLARE_NEWTYPE(cm, int);
|
||||||
|
* DECLARE_NEWTYPE(in, double);
|
||||||
|
*
|
||||||
|
* in_t cm_to_inches(cm_t cm) { return in_from_double(cm_as_int(cm) / 2.54); }
|
||||||
|
*
|
||||||
|
* struct person {
|
||||||
|
* year_t age;
|
||||||
|
* cm_t height;
|
||||||
|
* };
|
||||||
|
* struct person p = { .age = year_from_int(20), .height = cm_from_int(180) };
|
||||||
|
* in_t inches = cm_to_inches(p.age); // Compiler error!
|
||||||
|
* in_t inches = cm_to_inches(p.height); // Yay
|
||||||
|
* ```
|
||||||
|
* And a side-effect is that the units are documented as part of the type.
|
||||||
|
*/
|
||||||
|
#define DECLARE_NEWTYPE(name_, type_) \
|
||||||
|
typedef struct { \
|
||||||
|
type_ v; \
|
||||||
|
} name_##_t; \
|
||||||
|
\
|
||||||
|
static inline name_##_t name_##_from_##type_(type_ v) { return (name_##_t) { .v = v }; }; \
|
||||||
|
static inline type_ name_##_as_##type_(name_##_t name_) { return name_.v; }; \
|
||||||
|
static inline type_ name_(name_##_t name_) { return name_##_as_##type_(name_); } \
|
||||||
|
static inline name_##_t name_##_copy(name_##_t name_) { return name_##_from_##type_(name_.v); }; \
|
||||||
|
static inline name_##_t name_##_min(name_##_t a, name_##_t b) { \
|
||||||
|
return name_##_from_##type_(min(a.v, b.v)); \
|
||||||
|
}; \
|
||||||
|
static inline name_##_t name_##_max(name_##_t a, name_##_t b) { \
|
||||||
|
return name_##_from_##type_(max(a.v, b.v)); \
|
||||||
|
}; \
|
||||||
|
static inline int name_##_cmp(name_##_t a, name_##_t b) { \
|
||||||
|
return a.v < b.v ? -1 : (a.v > b.v ? 1 : 0); \
|
||||||
|
}; \
|
||||||
|
static inline int name_##_eq(name_##_t a, type_ b) { return a.v == b; }\
|
||||||
|
static inline int name_##_ne(name_##_t a, type_ b) { return a.v != b; }\
|
||||||
|
static inline int name_##_le(name_##_t a, type_ b) { return a.v <= b; }\
|
||||||
|
static inline int name_##_lt(name_##_t a, type_ b) { return a.v < b; }\
|
||||||
|
static inline int name_##_ge(name_##_t a, type_ b) { return a.v >= b; }\
|
||||||
|
static inline int name_##_gt(name_##_t a, type_ b) { return a.v > b; }\
|
||||||
|
struct __useless_struct_to_allow_trailing_semicolon__
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
#include "util-stringbuf.h"
|
#include "util-stringbuf.h"
|
||||||
#include "util-matrix.h"
|
#include "util-matrix.h"
|
||||||
#include "util-input-event.h"
|
#include "util-input-event.h"
|
||||||
|
#include "util-newtype.h"
|
||||||
|
|
||||||
#include "litest.h"
|
#include "litest.h"
|
||||||
|
|
||||||
|
|
@ -1954,6 +1955,84 @@ START_TEST(multivalue_test)
|
||||||
}
|
}
|
||||||
END_TEST
|
END_TEST
|
||||||
|
|
||||||
|
DECLARE_NEWTYPE(newint, int);
|
||||||
|
DECLARE_NEWTYPE(newdouble, double);
|
||||||
|
|
||||||
|
START_TEST(newtype_test)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
newint_t n1 = newint_from_int(1);
|
||||||
|
newint_t n2 = newint_from_int(2);
|
||||||
|
|
||||||
|
litest_assert_int_eq(newint(n1), 1);
|
||||||
|
litest_assert_int_eq(newint_as_int(n1), 1);
|
||||||
|
litest_assert_int_eq(newint(n2), 2);
|
||||||
|
litest_assert_int_eq(newint_as_int(n2), 2);
|
||||||
|
|
||||||
|
litest_assert_int_eq(newint_cmp(n1, n2), -1);
|
||||||
|
litest_assert_int_eq(newint_cmp(n1, n1), 0);
|
||||||
|
litest_assert_int_eq(newint_cmp(n2, n1), 1);
|
||||||
|
|
||||||
|
newint_t copy = newint_copy(n1);
|
||||||
|
litest_assert_int_eq(newint_cmp(n1, copy), 0);
|
||||||
|
|
||||||
|
newint_t min = newint_min(n1, n2);
|
||||||
|
newint_t max = newint_max(n1, n2);
|
||||||
|
litest_assert_int_eq(newint_cmp(min, n1), 0);
|
||||||
|
litest_assert_int_eq(newint_cmp(max, n2), 0);
|
||||||
|
|
||||||
|
litest_assert(newint_gt(n1, 0));
|
||||||
|
litest_assert(newint_eq(n1, 1));
|
||||||
|
litest_assert(newint_ge(n1, 1));
|
||||||
|
litest_assert(newint_le(n1, 1));
|
||||||
|
litest_assert(newint_ne(n1, 2));
|
||||||
|
litest_assert(newint_lt(n1, 2));
|
||||||
|
|
||||||
|
litest_assert(!newint_gt(n1, 1));
|
||||||
|
litest_assert(!newint_eq(n1, 0));
|
||||||
|
litest_assert(!newint_ge(n1, 2));
|
||||||
|
litest_assert(!newint_le(n1, 0));
|
||||||
|
litest_assert(!newint_ne(n1, 1));
|
||||||
|
litest_assert(!newint_lt(n1, 1));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
newdouble_t n1 = newdouble_from_double(1.2);
|
||||||
|
newdouble_t n2 = newdouble_from_double(2.3);
|
||||||
|
|
||||||
|
litest_assert_double_eq(newdouble(n1), 1.2);
|
||||||
|
litest_assert_double_eq(newdouble_as_double(n1), 1.2);
|
||||||
|
litest_assert_double_eq(newdouble(n2), 2.3);
|
||||||
|
litest_assert_double_eq(newdouble_as_double(n2), 2.3);
|
||||||
|
|
||||||
|
litest_assert_int_eq(newdouble_cmp(n1, n2), -1);
|
||||||
|
litest_assert_int_eq(newdouble_cmp(n1, n1), 0);
|
||||||
|
litest_assert_int_eq(newdouble_cmp(n2, n1), 1);
|
||||||
|
|
||||||
|
newdouble_t copy = newdouble_copy(n1);
|
||||||
|
litest_assert_int_eq(newdouble_cmp(n1, copy), 0);
|
||||||
|
|
||||||
|
newdouble_t min = newdouble_min(n1, n2);
|
||||||
|
newdouble_t max = newdouble_max(n1, n2);
|
||||||
|
litest_assert_int_eq(newdouble_cmp(min, n1), 0);
|
||||||
|
litest_assert_int_eq(newdouble_cmp(max, n2), 0);
|
||||||
|
|
||||||
|
litest_assert(newdouble_gt(n1, 0.0));
|
||||||
|
litest_assert(newdouble_eq(n1, 1.2));
|
||||||
|
litest_assert(newdouble_ge(n1, 1.2));
|
||||||
|
litest_assert(newdouble_le(n1, 1.2));
|
||||||
|
litest_assert(newdouble_ne(n1, 2.3));
|
||||||
|
litest_assert(newdouble_lt(n1, 2.3));
|
||||||
|
|
||||||
|
litest_assert(!newdouble_gt(n1, 1.2));
|
||||||
|
litest_assert(!newdouble_eq(n1, 0.0));
|
||||||
|
litest_assert(!newdouble_ge(n1, 2.3));
|
||||||
|
litest_assert(!newdouble_le(n1, 0.0));
|
||||||
|
litest_assert(!newdouble_ne(n1, 1.2));
|
||||||
|
litest_assert(!newdouble_lt(n1, 1.2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END_TEST
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
struct litest_runner *runner = litest_runner_new();
|
struct litest_runner *runner = litest_runner_new();
|
||||||
|
|
@ -2020,6 +2099,8 @@ int main(void)
|
||||||
ADD_TEST(stringbuf_test);
|
ADD_TEST(stringbuf_test);
|
||||||
ADD_TEST(multivalue_test);
|
ADD_TEST(multivalue_test);
|
||||||
|
|
||||||
|
ADD_TEST(newtype_test);
|
||||||
|
|
||||||
enum litest_runner_result result = litest_runner_run_tests(runner);
|
enum litest_runner_result result = litest_runner_run_tests(runner);
|
||||||
litest_runner_destroy(runner);
|
litest_runner_destroy(runner);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue