Skip to content

Conversation

@hemanta212
Copy link
Contributor

@hemanta212 hemanta212 commented Jan 7, 2026

Problem

When a schema field has required: false (the default), the generated Go struct uses non-pointer types. This causes issues when the DSL specifies null as an expected value:

test "non-existent user returns null" {
    $userId: 999
    u.name: null   // expects null from database
}

Generated struct:

type getUserResult struct {
    Name string  // non-pointer - cant be nil
}

Generated mock tries to return nil for a string field - compile error.

Solution

Pass Required metadata through ReturnInfo from the analyzer layer, letting codegen (signature.go) decide pointer wrapping. Type inference stays clean (returns string), while Required bool travels separately for codegen to use with proper type inspection via isNilableType().

Changes

  • dialect.go: Add Required bool field to ReturnInfo
  • dialects/cypher/type_inference.go: Add FieldInfo struct and inferFieldInfo() to extract Required from property access
  • dialects/cypher/analyzer.go: Set ret.Required in extractProjectionItem()
  • language/go/signature.go: Use ret.Required + isNilableType() helper to wrap nullable primitives in pointers
  • language/go/mock.go: Emit ptr[T any] helper, wrap non-nil pointer values with ptr()
  • Tests for Required propagation, nullable return inference, and pointer mock generation

@rlch rlch marked this pull request as draft January 9, 2026 03:24
@rlch
Copy link
Owner

rlch commented Jan 9, 2026

What is the inference logic for required? Simply whether the field has a pointer? Let's say a field has **string, does that become *string in the result struct or are types propagated exactly? Needs a bit more elaboration

@hemanta212
Copy link
Contributor Author

  • I infer types based on whether the required field in the schema is populated, regardless of what type is set to (I don’t infer from type).

  • If required is false, I ensure the field is nilable. If the type is already a pointer, slice, or map (which are nilable in Go), I don’t wrap it in a pointer.

  • This information is passed cleanly through the stack; the language/Go layer sees the type and required flag and performs the inference at code generation time.

When schema fields have required: false, generated Go structs now use
pointer types (e.g., *string instead of string). This allows mocks to
return nil for null values without compile errors.

Changes:
- signature.go: add lookupField() returning full Field with Required info
- signature.go: inferReturnType() wraps type in * when !field.Required
- mock.go: emit ptr[T any] helper for pointer value construction
- mock.go: findOutputValue() uses ptr() wrapper for non-nil pointer values

Fixes nullable return type codegen bug where bio: null in test cases
caused 'cannot use nil as string value' compile errors.
…ndling

- Add Required bool field to scaf.ReturnInfo in dialect.go
- Add FieldInfo struct and LookupFieldInfo() in type_inference.go
- Add inferFieldInfo() to extract Required from property access expressions
- extractProjectionItem() now sets ret.Required from schema field lookup
- signature.go uses ret.Required with proper isNilableType() helper
- Pointer wrapping for nullable fields happens in codegen layer
- Add TestTypeInference_RequiredField to verify Required propagation
@hemanta212 hemanta212 force-pushed the nullable-return-types branch from ab10fab to 2b1c816 Compare January 12, 2026 02:24
Replace complex AST traversal (inferFieldInfo, getPostfixExpr, FieldInfo,
LookupFieldInfo) with simple lookupFieldFromExpression() that:
- Parses 'variable.property' pattern from expression string
- Rejects complex expressions (operators, predicates, indexing)
- Looks up binding to get model, then field's Required from schema

This addresses reviewer feedback about inferFieldInfo being too permissive
for predicate expressions like 'u.bio IS NULL'.
- Move TestTypeInference_RequiredField to analyzer_test.go (tests analyzer behavior)
- Add test case for complex expressions defaulting to required
- Remove redundant needsPointerHelpers assignment in findOutputValue
- Remove goLiteralWithType wrapper, call goLiteral directly
- Remove unused baseType variable
@hemanta212 hemanta212 marked this pull request as ready for review January 12, 2026 04:25
Copy link
Owner

@rlch rlch left a comment

Choose a reason for hiding this comment

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

Remove fallback and ensure tests pass after. Otherwise LGTM

Also can you submit another PR for CI on PRs?

The fallback scanned all models matching by field name only, which could
return wrong field type/Required for ambiguous names like 'id' or 'name'.

Now relies solely on analyzer's ReturnInfo which has proper model context.
@hemanta212 hemanta212 requested a review from rlch January 12, 2026 06:36
@rlch rlch merged commit b14e058 into rlch:main Jan 12, 2026
1 check passed
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