Skip to content

Conversation

@ecito
Copy link
Contributor

@ecito ecito commented Oct 30, 2025

Add support for qualified type names (MemberType)

Description

Fixes a bug where properties with qualified type names were silently excluded from schema generation.

Example:

enum Weather {
  @Schemable
  enum Condition: String {
    case sunny, rainy
  }
}

@Schemable
struct Forecast {
  let condition: Weather.Condition  // Previously skipped, now works
}

The macro's type parser didn't handle .memberType syntax. This adds proper support to generate the correct schema reference (Weather.Condition.schema).

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Additional Notes

  • Added 4 integration tests verifying qualified type names work correctly.
  • Properties with nested types are no longer silently dropped from schemas

Copy link
Owner

@ajevans99 ajevans99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple small comments on tests but looks good otherwise. Thanks!

In the future, would love to add some diagnostics for cases like this so that it isn't a silent failure like it is now. #120

@ecito ecito force-pushed the fix/membertype-support branch from f476318 to 65965ce Compare October 30, 2025 21:20
Properties with qualified type names like Weather.Condition were
previously unsupported, causing them to be silently excluded from
schema generation. This adds handling for .memberType in the type
information parser to properly generate schema references for
nested/qualified type names.
@ecito ecito force-pushed the fix/membertype-support branch from 65965ce to e08052c Compare October 30, 2025 21:22
@ecito
Copy link
Contributor Author

ecito commented Nov 3, 2025

anything else to address in this PR?

@ajevans99 ajevans99 merged commit 803068c into ajevans99:main Nov 3, 2025
8 checks passed
@ajevans99
Copy link
Owner

anything else to address in this PR?

Nope. Lgtm!

ajevans99 pushed a commit that referenced this pull request Nov 5, 2025
## Overview

This PR adds extensive compile-time validation to the `@Schemable` macro
to catch common configuration errors before they result in confusing
compiler errors or invalid JSON schemas. All diagnostics emit clear,
actionable error messages during macro expansion.
#120

There's some limitations in macros that don't allow us to cover the full
spectrum of possible diagnostic messages, as macros can only see the
syntax tree, not the semantic type system.

  What this means:
  - We can't check if a type conforms to a protocol (like Schemable)
  - We can't resolve type aliases
  - We can't check if a custom type actually exists
  - We can't do true type checking

## Problem Statement

Previously, when the generated schema didn't match the memberwise
initializer or when schema options were misconfigured, users would
encounter confusing compiler errors like:
- `cannot convert value of type '(String, Int) -> Person' to expected
argument type '@sendable ((String)) -> Person'`
- `type 'X' has no member 'schema'`
- Silent generation of invalid JSON schemas that fail at runtime

These errors didn't clearly indicate the root cause, making debugging
difficult.

## Solution

Added three new diagnostic systems:

1. **Initializer Diagnostics** - Validates that the generated schema
matches the memberwise initializer
2. **SchemaOptions Diagnostics** - Validates that `@SchemaOptions` and
type-specific option macros are used correctly
3. **Unsupported Type Diagnostics** - Warns when properties have
unsupported types and will be excluded
## Diagnostics Added (11 Total)

### Initializer Diagnostics (4)

#### 1. Property with Default Value (Warning)
Detects when properties have default values that will be excluded from
the synthesized memberwise initializer.

```swift
@Schemable
struct Person {
  let name: String
  let age: Int = 0  // ⚠️ Warning: excluded from init
}
```

**Message**: `Property 'age' has a default value which will be excluded
from the memberwise initializer`

#### 2. Initializer Parameter Order Mismatch (Error)
Detects when an explicit initializer has parameters in a different order
than the schema expects.

```swift
@Schemable
struct Person {
  let name: String
  let age: Int

  init(age: Int, name: String) { ... }  // ❌ Wrong order
}
```

**Message**: `Initializer parameter at position 1 is 'age' but schema
expects 'name'. The schema will generate properties in a different order
than the initializer parameters.`

#### 3. Initializer Parameter Type Mismatch (Error)
Detects when parameter types don't match property types.

```swift
@Schemable
struct Product {
  let price: Double

  init(price: Int) { ... }  // ❌ Wrong type
}
```

**Message**: `Parameter 'price' has type 'Int' but schema expects
'Double'. This type mismatch will cause the generated schema to fail.`

#### 4. No Matching Initializer (Error)
Detects when a type has explicit initializers but none match the schema
signature, especially when using `@ExcludeFromSchema`.

```swift
@Schemable
struct Config {
  let host: String
  let port: Int

  @ExcludeFromSchema
  let internal: Bool

  init(host: String, port: Int, internal: Bool) { ... }  // ❌ Includes excluded property
}
```

**Message**: Detailed message showing expected signature, available
initializers, and excluded properties with suggestions.

### SchemaOptions Diagnostics (6)

#### 5. Type Mismatch for Option Macros (Error)
Detects when type-specific option macros are used on incompatible
property types.

```swift
@Schemable
struct Person {
  @StringOptions(.minLength(5))  // ❌ String options on Int
  let age: Int
}
```

**Message**: `@StringOptions can only be used on String properties, but
'age' has type 'Int'`

**Applies to**:
- `@StringOptions` → must be used on String properties
- `@NumberOptions` → must be used on numeric properties (Int, Double,
etc.)
- `@ArrayOptions` → must be used on Array properties

#### 6. Min Greater Than Max Constraints (Error)
Detects logically impossible constraint combinations.

```swift
@StringOptions(.minLength(10), .maxLength(5))  // ❌ Impossible
@NumberOptions(.minimum(100), .maximum(50))    // ❌ Impossible
@ArrayOptions(.minItems(10), .maxItems(5))     // ❌ Impossible
```

**Message**: `Property 'username' has minLength (10) greater than
maxLength (5). This string length constraint can never be satisfied.`

#### 7. Negative Constraint Values (Error)
Detects invalid negative values for size/length constraints.

```swift
@StringOptions(.minLength(-5))   // ❌ Invalid
@ArrayOptions(.minItems(-1))     // ❌ Invalid
@NumberOptions(.multipleOf(-2))  // ❌ Invalid
```

**Message**: `Property 'text' has minLength with negative value (-5).
This constraint must be non-negative.`

#### 8. ReadOnly and WriteOnly Conflict (Error)
Detects when a property is marked as both read-only and write-only.

```swift
@SchemaOptions(.readOnly(true), .writeOnly(true))  // ❌ Impossible
let value: String
```

**Message**: `Property 'value' cannot be both readOnly and writeOnly`

#### 9. Conflicting Constraint Types (Warning)
Warns when both inclusive and exclusive boundary constraints are
specified.

```swift
@NumberOptions(.minimum(0), .exclusiveMinimum(0))  // ⚠️ Conflicting
@NumberOptions(.maximum(100), .exclusiveMaximum(100))  // ⚠️ Conflicting
```

**Message**: `Property 'value' has both minimum and exclusiveMinimum
specified. Use only one of minimum or exclusiveMinimum.`

#### 10. Duplicate Options (Warning)
Warns when the same option is specified multiple times.

```swift
@StringOptions(.minLength(5), .minLength(10))  // ⚠️ Duplicate
```

**Message**: `Property 'text' has minLength specified 2 times. Only the
last value will be used.`

### Unsupported Type Diagnostics (1)

#### 11. Unsupported Property Type (Warning)
Warns when properties have types that are not supported by the
`@Schemable` macro and will be silently excluded from schema generation.

```swift
@Schemable
struct Handler {
  let name: String
  let callback: () -> Void  // ⚠️ Function type not supported
}
```

**Message**: `Property 'callback' has type '() -> Void' which is not
supported by the @Schemable macro. This property will be excluded from
the generated schema, which may cause the schema to not match the
memberwise initializer.`

**Catches**:
- Function types: `() -> Void`, `(Int) -> String`
- Tuple types: `(Int, Int)`, `(x: Int, y: Int)`
- Metatypes: `Any.Type`, `String.Type`
- Other unsupported Swift types

**Why this matters**: This diagnostic would have made [the MemberType
bug](#119) (where
qualified type names like `Weather.Condition` were silently excluded)
much more obvious by warning about the exclusion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants