mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-12 18:18:43 +08:00
[c] Add check to codegen for setter/getter pairs with inconsistens nullability (one nullable, the other not)
This commit is contained in:
parent
92022e9c76
commit
65b138411c
@ -1,5 +1,6 @@
|
||||
import { isMethodExcluded } from "./exclusions";
|
||||
import { type ClassOrStruct, type Exclusion, type Field, isPrimitive, type Method, type Type, toSnakeCase } from "./types";
|
||||
import type { CClassOrStruct, CMethod } from "./c-types";
|
||||
|
||||
/**
|
||||
* Checks for methods that have both const and non-const versions with different return types.
|
||||
@ -341,6 +342,105 @@ export function checkMethodTypeNameConflicts(classes: ClassOrStruct[], allTypes:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for getter/setter pairs where the return type nullability of the getter
|
||||
* doesn't match the parameter nullability of the setter. This creates inconsistent
|
||||
* APIs in target languages (e.g., Dart) where such mismatches are forbidden.
|
||||
*
|
||||
* @param cTypes - Array of generated C types with their methods
|
||||
*/
|
||||
export function checkGetterSetterNullabilityMismatch(cTypes: CClassOrStruct[]): void {
|
||||
const mismatches: Array<{
|
||||
typeName: string,
|
||||
fieldName: string,
|
||||
getterNullable: boolean,
|
||||
setterNullable: boolean
|
||||
}> = [];
|
||||
|
||||
for (const cType of cTypes) {
|
||||
if (!cType.methods) continue;
|
||||
|
||||
// Group methods by field name (extract from method names like spine_type_get_field, spine_type_set_field)
|
||||
const fieldAccessors = new Map<string, { getter?: CMethod, setter?: CMethod }>();
|
||||
|
||||
for (const method of cType.methods) {
|
||||
// Check if this is a getter method (ends with _get_<field_name> AND has exactly 1 parameter)
|
||||
const getterMatch = method.name.match(/^(.+)_get_(.+)$/);
|
||||
if (getterMatch && method.parameters?.length === 1) {
|
||||
const fieldName = getterMatch[2];
|
||||
if (!fieldAccessors.has(fieldName)) {
|
||||
fieldAccessors.set(fieldName, {});
|
||||
}
|
||||
fieldAccessors.get(fieldName)!.getter = method;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is a setter method (ends with _set_<field_name> AND has exactly 2 parameters)
|
||||
const setterMatch = method.name.match(/^(.+)_set_(.+)$/);
|
||||
if (setterMatch && method.parameters?.length === 2) {
|
||||
const fieldName = setterMatch[2];
|
||||
if (!fieldAccessors.has(fieldName)) {
|
||||
fieldAccessors.set(fieldName, {});
|
||||
}
|
||||
fieldAccessors.get(fieldName)!.setter = method;
|
||||
}
|
||||
}
|
||||
|
||||
// Check each getter/setter pair for nullability mismatches
|
||||
for (const [fieldName, accessors] of fieldAccessors) {
|
||||
const { getter, setter } = accessors;
|
||||
|
||||
// Skip if we don't have both getter and setter
|
||||
if (!getter || !setter) continue;
|
||||
|
||||
// Extract nullability information
|
||||
const getterNullable = getter.returnTypeNullable || false;
|
||||
|
||||
// For setters, find the parameter that's not 'self' (should be the value parameter)
|
||||
const valueParam = setter.parameters?.find(p => p.name !== 'self');
|
||||
if (!valueParam) continue;
|
||||
|
||||
const setterNullable = valueParam.isNullable || false;
|
||||
|
||||
// Check for mismatch
|
||||
if (getterNullable !== setterNullable) {
|
||||
mismatches.push({
|
||||
typeName: cType.name,
|
||||
fieldName,
|
||||
getterNullable,
|
||||
setterNullable
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report mismatches
|
||||
if (mismatches.length > 0) {
|
||||
console.error("\n" + "=".repeat(80));
|
||||
console.error("GETTER/SETTER NULLABILITY MISMATCHES");
|
||||
console.error("=".repeat(80));
|
||||
console.error(`\nFound ${mismatches.length} getter/setter pairs with mismatched nullability:\n`);
|
||||
|
||||
for (const mismatch of mismatches) {
|
||||
const getterType = mismatch.getterNullable ? "nullable" : "non-nullable";
|
||||
const setterType = mismatch.setterNullable ? "nullable" : "non-nullable";
|
||||
console.error(` - ${mismatch.typeName}::${mismatch.fieldName}`);
|
||||
console.error(` Getter returns: ${getterType}`);
|
||||
console.error(` Setter expects: ${setterType}`);
|
||||
}
|
||||
|
||||
console.error("\nThese nullability mismatches cause compilation errors in some target");
|
||||
console.error("languages (e.g., Dart). The getter and setter must have consistent nullability.");
|
||||
console.error("You should either:");
|
||||
console.error(" 1. Ensure the C++ field type has consistent nullability semantics");
|
||||
console.error(" 2. Exclude problematic field getters or setters in exclusions.txt");
|
||||
console.error(" 3. Override nullability analysis for specific field types");
|
||||
console.error("=".repeat(80) + "\n");
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for methods that return non-primitive types by value.
|
||||
* These cannot be wrapped in C without heap allocation.
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import * as path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { CWriter } from './c-writer';
|
||||
import { checkConstNonConstConflicts, checkFieldAccessorConflicts, checkMethodTypeNameConflicts, checkMultiLevelPointers, checkValueReturns } from './checks';
|
||||
import { checkConstNonConstConflicts, checkFieldAccessorConflicts, checkGetterSetterNullabilityMismatch, checkMethodTypeNameConflicts, checkMultiLevelPointers, checkValueReturns } from './checks';
|
||||
import { isTypeExcluded, loadExclusions } from './exclusions';
|
||||
import { generateArrays, generateTypes } from './ir-generator';
|
||||
import { extractTypes } from './type-extractor';
|
||||
@ -71,6 +71,9 @@ export async function generate() {
|
||||
const { cTypes, cEnums } = await generateTypes(types, exclusions, allExtractedTypes);
|
||||
const cArrayTypes = await generateArrays(types, arrayType, exclusions);
|
||||
|
||||
// Check for getter/setter nullability mismatches
|
||||
checkGetterSetterNullabilityMismatch(cTypes);
|
||||
|
||||
// Build interface/pure type information first
|
||||
const isInterface = buildInterfaceMap(allExtractedTypes.filter(t => t.kind !== 'enum') as ClassOrStruct[]);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user