Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

library;

import 'package:analyzer/dart/element/element.dart';
import 'package:build_test/build_test.dart';
import 'package:fory/src/codegen/analyze/impl/annotation/uint_annotation_analyzer.dart';
import 'package:fory/src/codegen/entity/location_mark.dart';
import 'package:fory/src/const/obj_type.dart';
import 'package:test/test.dart';

void main() {
group('UintAnnotationAnalyzer integration tests', () {
const analyzer = UintAnnotationAnalyzer();

Future<UintAnnotationResult> analyzeField(String source, String fieldName) async {
final library = await resolveSource(source, (resolver) => resolver.findLibraryByName('test_lib'));
final classElement = library!.topLevelElements.firstWhere((e) => e is ClassElement) as ClassElement;
final fieldElement = classElement.fields.firstWhere((f) => f.name == fieldName);

final locationMark = LocationMark.fieldLevel(
'test_lib',
'TestClass',
fieldName,
);

return analyzer.analyze(fieldElement.metadata, locationMark);
}

const String commonSource = '''
library test_lib;
import 'package:fory/fory.dart';

class TestClass {
@Uint8Type()
int f8;

@Uint16Type()
int f16;

@Uint32Type()
int f32;

@Uint32Type(encoding: UintEncoding.varint)
int f32v;

@Uint64Type()
int f64;

@Uint64Type(encoding: UintEncoding.varint)
int f64v;

@Uint64Type(encoding: UintEncoding.tagged)
int f64t;

int fNone;

@Uint8Type()
@Uint16Type()
int fDup;
}
''';

test('analyzes @Uint8Type correctly', () async {
final result = await analyzeField(commonSource, 'f8');
expect(result.hasAnnotation, isTrue);
expect(result.objType, equals(ObjType.UINT8));
});

test('analyzes @Uint16Type correctly', () async {
final result = await analyzeField(commonSource, 'f16');
expect(result.hasAnnotation, isTrue);
expect(result.objType, equals(ObjType.UINT16));
});

test('analyzes @Uint32Type (default) correctly', () async {
final result = await analyzeField(commonSource, 'f32');
expect(result.hasAnnotation, isTrue);
expect(result.objType, equals(ObjType.UINT32));
});

test('analyzes @Uint32Type (varint) correctly', () async {
final result = await analyzeField(commonSource, 'f32v');
expect(result.hasAnnotation, isTrue);
expect(result.objType, equals(ObjType.VAR_UINT32));
});

test('analyzes @Uint64Type (default) correctly', () async {
final result = await analyzeField(commonSource, 'f64');
expect(result.hasAnnotation, isTrue);
expect(result.objType, equals(ObjType.UINT64));
});

test('analyzes @Uint64Type (varint) correctly', () async {
final result = await analyzeField(commonSource, 'f64v');
expect(result.hasAnnotation, isTrue);
expect(result.objType, equals(ObjType.VAR_UINT64));
});

test('analyzes @Uint64Type (tagged) correctly', () async {
final result = await analyzeField(commonSource, 'f64t');
expect(result.hasAnnotation, isTrue);
expect(result.objType, equals(ObjType.TAGGED_UINT64));
});

test('returns none for unannotated field', () async {
final result = await analyzeField(commonSource, 'fNone');
expect(result.hasAnnotation, isFalse);
expect(result.objType, isNull);
});

test('throws exception for duplicated uint annotations', () async {
expect(
() => analyzeField(commonSource, 'fDup'),
throwsA(anything), // Should throw DuplicatedAnnotationException
);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class AnalysisTypeIdentifier{
static int get dartCoreLibId => objectType.element.library.id;


static final List<int?> _ids = [null,null,null,null];
static final List<int?> _ids = [null,null,null,null,null,null,null,null];
static final List<Type3StringKey> _keys = [
Type3StringKey(
'ForyClass',
Expand All @@ -57,6 +57,26 @@ class AnalysisTypeIdentifier{
'package',
'fory/src/annotation/fory_enum.dart',
),
Type3StringKey(
'Uint8Type',
'package',
'fory/src/annotation/uint_types.dart',
),
Type3StringKey(
'Uint16Type',
'package',
'fory/src/annotation/uint_types.dart',
),
Type3StringKey(
'Uint32Type',
'package',
'fory/src/annotation/uint_types.dart',
),
Type3StringKey(
'Uint64Type',
'package',
'fory/src/annotation/uint_types.dart',
),
];

static bool _check(ClassElement element, int index){
Expand Down Expand Up @@ -100,4 +120,20 @@ class AnalysisTypeIdentifier{
_ids[0] = id;
}

static bool isUint8Type(ClassElement element){
return _check(element, 4);
}

static bool isUint16Type(ClassElement element){
return _check(element, 5);
}

static bool isUint32Type(ClassElement element){
return _check(element, 6);
}

static bool isUint64Type(ClassElement element){
return _check(element, 7);
}

}
2 changes: 2 additions & 0 deletions dart/packages/fory/lib/src/codegen/analyze/analyzer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import 'package:fory/src/codegen/analyze/impl/annotation/class_annotation_analyzer.dart';
import 'package:fory/src/codegen/analyze/impl/annotation/enum_annotation_analyzer.dart';
import 'package:fory/src/codegen/analyze/impl/annotation/key_annotation_analyzer.dart';
import 'package:fory/src/codegen/analyze/impl/annotation/uint_annotation_analyzer.dart';
import 'package:fory/src/codegen/analyze/impl/constructor/constructor_analyzer.dart';
import 'package:fory/src/codegen/analyze/impl/field/access_info_analyzer.dart';
import 'package:fory/src/codegen/analyze/impl/field/field_analyzer_impl.dart';
Expand All @@ -44,6 +45,7 @@ abstract class Analyzer {
static const ClassAnnotationAnalyzer classAnnotationAnalyzer = ClassAnnotationAnalyzer();
static const KeyAnnotationAnalyzer keyAnnotationAnalyzer = KeyAnnotationAnalyzer();
static const EnumAnnotationAnalyzer enumAnnotationAnalyzer = EnumAnnotationAnalyzer();
static const UintAnnotationAnalyzer uintAnnotationAnalyzer = UintAnnotationAnalyzer();

// Enum analyzers
static const EnumAnalyzer enumAnalyzer = EnumAnalyzerImpl();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:fory/src/codegen/analyze/analysis_type_identifier.dart';
import 'package:fory/src/codegen/analyze/annotation/location_level_ensure.dart';
import 'package:fory/src/codegen/const/location_level.dart';
import 'package:fory/src/codegen/entity/location_mark.dart';
import 'package:fory/src/codegen/exception/annotation_exception.dart';
import 'package:fory/src/const/obj_type.dart';

/// Result of uint annotation analysis
class UintAnnotationResult {
final ObjType? objType;
final bool hasAnnotation;

const UintAnnotationResult(this.objType, this.hasAnnotation);

static const none = UintAnnotationResult(null, false);
}

class UintAnnotationAnalyzer {
const UintAnnotationAnalyzer();

/// Analyzes uint type annotations on a field.
/// Returns the appropriate ObjType if a uint annotation is found, null otherwise.
UintAnnotationResult analyze(
List<ElementAnnotation> metadata,
@LocationEnsure(LocationLevel.fieldLevel) LocationMark locationMark,
) {
assert(locationMark.ensureFieldLevel);

DartObject? uintAnno;
String? annotationType;

for (ElementAnnotation annoElement in metadata) {
final anno = annoElement.computeConstantValue()!;
final annoClsElement = anno.type!.element as ClassElement;

if (AnalysisTypeIdentifier.isUint8Type(annoClsElement)) {
if (uintAnno != null) {
throw DuplicatedAnnotationException(
'Uint type annotation',
locationMark.fieldName!,
locationMark.clsLocation,
);
}
uintAnno = anno;
annotationType = 'Uint8Type';
} else if (AnalysisTypeIdentifier.isUint16Type(annoClsElement)) {
if (uintAnno != null) {
throw DuplicatedAnnotationException(
'Uint type annotation',
locationMark.fieldName!,
locationMark.clsLocation,
);
}
uintAnno = anno;
annotationType = 'Uint16Type';
} else if (AnalysisTypeIdentifier.isUint32Type(annoClsElement)) {
if (uintAnno != null) {
throw DuplicatedAnnotationException(
'Uint type annotation',
locationMark.fieldName!,
locationMark.clsLocation,
);
}
uintAnno = anno;
annotationType = 'Uint32Type';
} else if (AnalysisTypeIdentifier.isUint64Type(annoClsElement)) {
if (uintAnno != null) {
throw DuplicatedAnnotationException(
'Uint type annotation',
locationMark.fieldName!,
locationMark.clsLocation,
);
}
uintAnno = anno;
annotationType = 'Uint64Type';
}
}

if (uintAnno == null) {
return UintAnnotationResult.none;
}

// Determine the ObjType based on the annotation
ObjType objType;
switch (annotationType) {
case 'Uint8Type':
objType = ObjType.UINT8;
break;
case 'Uint16Type':
objType = ObjType.UINT16;
break;
case 'Uint32Type':
// Check encoding parameter
final encodingField = uintAnno.getField('encoding');
if (encodingField != null && !encodingField.isNull) {
final encodingValue = encodingField.getField('_name')?.toStringValue();
if (encodingValue == 'varint') {
objType = ObjType.VAR_UINT32;
} else {
objType = ObjType.UINT32;
}
} else {
objType = ObjType.UINT32;
}
break;
case 'Uint64Type':
// Check encoding parameter
final encodingField = uintAnno.getField('encoding');
if (encodingField != null && !encodingField.isNull) {
final encodingValue = encodingField.getField('_name')?.toStringValue();
if (encodingValue == 'varint') {
objType = ObjType.VAR_UINT64;
} else if (encodingValue == 'tagged') {
objType = ObjType.TAGGED_UINT64;
} else {
objType = ObjType.UINT64;
}
} else {
objType = ObjType.UINT64;
}
break;
default:
return UintAnnotationResult.none;
}

return UintAnnotationResult(objType, true);
}
}
Loading
Loading