Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ Tests use [libcheck](https://libcheck.github.io/check/). Each test file has its

Every Objectively type consists of exactly three pieces:

1. **Instance struct** (`Foo.h`) — starts with the parent type, then the interface pointer, then instance variables:
1. **Instance struct** (`Foo.h`) — starts with the parent type, then the zero-length interface member, then instance variables:
```c
struct Foo {
Bar bar; // parent (starts-with composition = single inheritance)
FooInterface *interface;
Bar bar; // parent (starts-with composition = single inheritance)
FooInterface *interface[0]; // carries a type, occupies no storage; `$` reads it via typeof
int myField;
};
```
The `interface` member MUST directly follow the parent: it has pointer alignment, so placing it after a smaller field would introduce padding.

2. **Interface struct** (`Foo.h`) — starts with the parent interface, then method function pointers:
```c
Expand All @@ -58,7 +59,7 @@ The `.c` file contains `static` implementations, an `initialize` function that w

### Dispatch macros
```c
$(obj, method, args) // instance method dispatch (obj->interface->method)
$(obj, method, args) // instance method dispatch (classof(obj)->interface->method)
$$(Type, method, args) // class/static method dispatch
super(Type, obj, method, args) // call superclass implementation
alloc(Type) // allocate an instance
Expand Down Expand Up @@ -95,7 +96,6 @@ Class *_Foo(void) {
.name = "Foo",
.superclass = _Bar(),
.instanceSize = sizeof(Foo),
.interfaceOffset = offsetof(Foo, interface),
.interfaceSize = sizeof(FooInterface),
.initialize = initialize,
});
Expand Down
3 changes: 1 addition & 2 deletions .github/copilot/skills/new-type.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ typedef struct FooInterface FooInterface;

struct Foo {
Bar bar; // parent first (starts-with inheritance)
FooInterface *interface;
FooInterface *interface[0]; // zero-length: carries a type for `$`, no storage; MUST follow the parent
// instance variables...
};

Expand Down Expand Up @@ -82,7 +82,6 @@ Class *_Foo(void) {
.name = "Foo",
.superclass = _Bar(),
.instanceSize = sizeof(Foo),
.interfaceOffset = offsetof(Foo, interface),
.interfaceSize = sizeof(FooInterface),
.initialize = initialize,
});
Expand Down
36 changes: 32 additions & 4 deletions Documentation/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ A guide to declaring, implementing, and using your own types with Objectively.

Every Objectively type consists of three components:

**1. The instance struct** — starts with the parent type, then the interface pointer, then instance variables:
**1. The instance struct** — starts with the parent type, then the zero-length interface member, then instance variables:

```c
struct Hello {
Object object; // parent (starts-with = single inheritance)
HelloInterface *interface;
Object object; // parent (starts-with = single inheritance)
HelloInterface *interface[0]; // carries a type, occupies no storage
const char *greeting;
};
```

The `interface` member exists only so that `$` can read its type with `typeof`; it holds nothing. The interface itself lives on the Class and is reached through `Object::clazz`. The member MUST directly follow the parent, where its pointer alignment costs no padding.

**2. The interface struct** — starts with the parent interface, then method function pointers:

```c
Expand Down Expand Up @@ -76,7 +78,6 @@ Class *_Hello(void) {
.name = "Hello",
.superclass = _Object(),
.instanceSize = sizeof(Hello),
.interfaceOffset = offsetof(Hello, interface),
.interfaceSize = sizeof(HelloInterface),
.initialize = initialize,
});
Expand Down Expand Up @@ -139,6 +140,33 @@ To invoke a supertype's method implementation, use the `super` macro.
super(Object, self, dealloc);
```

## Re-classing an instance

An instance carries no interface pointer of its own: `$`, `cast`, and `isKindOfClass` all resolve through `Object::clazz`. Reassigning `clazz` after allocation therefore changes an instance's behavior and its type identity together, immediately. This permits one-off Classes minted purely for behavior, with no struct, header, or archetype of their own:

```c
static bool resizeHandle_captureEvent(Control *self, const SDL_Event *event) {
// ...
return true;
}

static void resizeHandle_initialize(Class *clazz) {
((ControlInterface *) clazz->interface)->captureEvent = resizeHandle_captureEvent;
}

Class *proxy = _initialize(&(const ClassDef) {
.name = "Control(resizeHandle)",
.superclass = classof(resizeHandle),
.instanceSize = classof(resizeHandle)->def.instanceSize,
.interfaceSize = classof(resizeHandle)->def.interfaceSize,
.initialize = resizeHandle_initialize,
});

((Object *) resizeHandle)->clazz = proxy;
```

The new Class inherits every method of the original, since `_initialize` copies the superclass interface before calling `initialize`. The instance struct MUST NOT change: a Class minted this way SHOULD reuse the superclass's `instanceSize` exactly, because the memory has already been allocated.

## Managing memory

Objectively uses reference counting to govern object retention. Newly instantiated Objects have a reference count of 1. To retain a strong reference to an Object, call `retain(obj)`. To relinquish it, call `release(obj)`. Once an Object's reference count reaches 0, it is deallocated. Remember to balance every `retain` with a `release`.
Expand Down
1 change: 0 additions & 1 deletion Examples/Hello.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ Class *_Hello(void) {
.name = "Hello",
.superclass = _Object(),
.instanceSize = sizeof(Hello),
.interfaceOffset = offsetof(Hello, interface),
.interfaceSize = sizeof(HelloInterface),
.initialize = initialize,
});
Expand Down
4 changes: 2 additions & 2 deletions Examples/Hello.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ struct Hello {
Object object;

/**
* @brief The interface.
* @brief The interface type.
*/
HelloInterface *interface;
HelloInterface *interface[0];

/**
* @brief The greeting.
Expand Down
1 change: 0 additions & 1 deletion Sources/Objectively/Array.c
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,6 @@ Class *_Array(void) {
.name = "Array",
.superclass = _Object(),
.instanceSize = sizeof(Array),
.interfaceOffset = offsetof(Array, interface),
.interfaceSize = sizeof(ArrayInterface),
.initialize = initialize,
});
Expand Down
4 changes: 2 additions & 2 deletions Sources/Objectively/Array.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ struct Array {
Object object;

/**
* @brief The interface.
* @brief The interface type.
* @protected
*/
ArrayInterface *interface;
ArrayInterface *interface[0];

/**
* @brief The count of elements.
Expand Down
1 change: 0 additions & 1 deletion Sources/Objectively/Boole.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ Class *_Boole(void) {
.name = "Boole",
.superclass = _Object(),
.instanceSize = sizeof(Boole),
.interfaceOffset = offsetof(Boole, interface),
.interfaceSize = sizeof(BooleInterface),
.initialize = initialize,
.destroy = destroy,
Expand Down
4 changes: 2 additions & 2 deletions Sources/Objectively/Boole.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ struct Boole {
Object object;

/**
* @brief The interface.
* @brief The interface type.
* @protected
*/
BooleInterface *interface;
BooleInterface *interface[0];

/**
* @brief The backing bool.
Expand Down
6 changes: 0 additions & 6 deletions Sources/Objectively/Class.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ Class *_initialize(const ClassDef *def) {
assert(def->name);
assert(def->instanceSize);
assert(def->interfaceSize);
assert(def->interfaceOffset);

Class *clazz = calloc(1, sizeof(Class));
assert(clazz);
Expand Down Expand Up @@ -207,11 +206,6 @@ ident _alloc(Class *clazz) {
object->clazz = clazz;
object->referenceCount = 1;

ident interface = clazz->interface;
do {
*(ident *) (obj + clazz->def.interfaceOffset) = interface;
} while ((clazz = clazz->def.superclass));

return obj;
}

Expand Down
20 changes: 13 additions & 7 deletions Sources/Objectively/Class.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,6 @@ struct ClassDef {
*/
size_t instanceSize;

/**
* @brief The interface offset (required).
*/
ptrdiff_t interfaceOffset;

/**
* @brief The interface size (required).
*/
Expand Down Expand Up @@ -257,11 +252,22 @@ OBJECTIVELY_EXPORT size_t _pageSize;

/**
* @brief Invoke an instance method.
* @details The receiver's static type selects the interface layout: `typeof` of its
* zero-length `interface` member yields e.g. `StringInterface *` without that member
* occupying any storage. The interface itself is resolved through `Object::clazz`, so
* dispatch always follows the receiver's live Class, with every override in place.
* @remarks Instance dispatch, `isKindOfClass`, and `cast` all resolve through the same
* field, `Object::clazz`. There is no per-instance copy of the interface pointer. One
* consequence: reassigning `clazz` after allocation (Objective-C's `object_setClass`) is
* sound - type checks and dispatch see the new Class together, immediately. That makes
* runtime re-classing viable: proxies, spies, KVO-style observation, and Classes minted
* purely for behavior that reuse an existing struct's `instanceSize` with an overridden
* interface, without that struct declaring anything of its own (see issue #23).
*/
#define $(obj, method, ...) \
({ \
typeof(obj) _obj = obj; \
_obj->interface->method(_obj, ## __VA_ARGS__); \
typeof(obj) _obj = (obj); \
((typeof(_obj->interface[0])) classof(_obj)->interface)->method(_obj, ## __VA_ARGS__); \
})
Comment thread
jdolan marked this conversation as resolved.

/// @brief Invoke a Class method.
Expand Down
1 change: 0 additions & 1 deletion Sources/Objectively/Condition.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ Class *_Condition(void) {
.name = "Condition",
.superclass = _Lock(),
.instanceSize = sizeof(Condition),
.interfaceOffset = offsetof(Condition, interface),
.interfaceSize = sizeof(ConditionInterface),
.initialize = initialize,
});
Expand Down
4 changes: 2 additions & 2 deletions Sources/Objectively/Condition.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ struct Condition {
Lock lock;

/**
* @brief The interface.
* @brief The interface type.
* @protected
*/
ConditionInterface *interface;
ConditionInterface *interface[0];

/**
* @brief The backing condition.
Expand Down
1 change: 0 additions & 1 deletion Sources/Objectively/Data.c
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,6 @@ Class *_Data(void) {
.name = "Data",
.superclass = _Object(),
.instanceSize = sizeof(Data),
.interfaceOffset = offsetof(Data, interface),
.interfaceSize = sizeof(DataInterface),
.initialize = initialize,
});
Expand Down
4 changes: 2 additions & 2 deletions Sources/Objectively/Data.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ struct Data {
Object object;

/**
* @brief The interface.
* @brief The interface type.
* @protected
*/
DataInterface *interface;
DataInterface *interface[0];

/**
* @brief The bytes.
Expand Down
1 change: 0 additions & 1 deletion Sources/Objectively/Date.c
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ Class *_Date(void) {
.name = "Date",
.superclass = _Object(),
.instanceSize = sizeof(Date),
.interfaceOffset = offsetof(Date, interface),
.interfaceSize = sizeof(DateInterface),
.initialize = initialize,
});
Expand Down
4 changes: 2 additions & 2 deletions Sources/Objectively/Date.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ struct Date {
Object object;

/**
* @brief The interface.
* @brief The interface type.
* @protected
*/
DateInterface *interface;
DateInterface *interface[0];

/**
* @brief The time.
Expand Down
1 change: 0 additions & 1 deletion Sources/Objectively/DateFormatter.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ Class *_DateFormatter(void) {
.name = "DateFormatter",
.superclass = _Object(),
.instanceSize = sizeof(DateFormatter),
.interfaceOffset = offsetof(DateFormatter, interface),
.interfaceSize = sizeof(DateFormatterInterface),
.initialize = initialize,
});
Expand Down
4 changes: 2 additions & 2 deletions Sources/Objectively/DateFormatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ struct DateFormatter {
Object object;

/**
* @brief The interface.
* @brief The interface type.
* @protected
*/
DateFormatterInterface *interface;
DateFormatterInterface *interface[0];

/**
* @brief The UTF-8 encoded format string.
Expand Down
1 change: 0 additions & 1 deletion Sources/Objectively/Dictionary.c
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,6 @@ Class *_Dictionary(void) {
.name = "Dictionary",
.superclass = _Object(),
.instanceSize = sizeof(Dictionary),
.interfaceOffset = offsetof(Dictionary, interface),
.interfaceSize = sizeof(DictionaryInterface),
.initialize = initialize,
});
Expand Down
4 changes: 2 additions & 2 deletions Sources/Objectively/Dictionary.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ struct Dictionary {
Object object;

/**
* @brief The interface.
* @brief The interface type.
* @protected
*/
DictionaryInterface *interface;
DictionaryInterface *interface[0];

/**
* @brief The internal size (number of bins).
Expand Down
1 change: 0 additions & 1 deletion Sources/Objectively/Error.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,6 @@ Class *_Error(void) {
.name = "Error",
.superclass = _Object(),
.instanceSize = sizeof(Error),
.interfaceOffset = offsetof(Error, interface),
.interfaceSize = sizeof(ErrorInterface),
.initialize = initialize,
});
Expand Down
4 changes: 2 additions & 2 deletions Sources/Objectively/Error.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ struct Error {
Object object;

/**
* @brief The interface.
* @brief The interface type.
* @protected
*/
ErrorInterface *interface;
ErrorInterface *interface[0];

/**
* @brief The error code.
Expand Down
1 change: 0 additions & 1 deletion Sources/Objectively/HashTable.c
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,6 @@ Class *_HashTable(void) {
.name = "HashTable",
.superclass = _Object(),
.instanceSize = sizeof(HashTable),
.interfaceOffset = offsetof(HashTable, interface),
.interfaceSize = sizeof(HashTableInterface),
.initialize = initialize,
});
Expand Down
4 changes: 2 additions & 2 deletions Sources/Objectively/HashTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ struct HashTable {
Object object;

/**
* @brief The interface.
* @brief The interface type.
* @protected
*/
HashTableInterface *interface;
HashTableInterface *interface[0];

/**
* @brief The number of entries.
Expand Down
1 change: 0 additions & 1 deletion Sources/Objectively/IndexPath.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ Class *_IndexPath(void) {
.name = "IndexPath",
.superclass = _Object(),
.instanceSize = sizeof(IndexPath),
.interfaceOffset = offsetof(IndexPath, interface),
.interfaceSize = sizeof(IndexPathInterface),
.initialize = initialize,
});
Expand Down
Loading
Loading