@@ -1000,8 +1000,42 @@ fn resolve_composite_type(
10001000 ( None , None ) => true ,
10011001 } ;
10021002
1003- let representation = if ct. annotations . contains ( "json" ) {
1003+ let is_json = ct. annotations . contains ( "json" ) ;
1004+ let is_computed = ct. annotations . contains ( "computed" ) ;
1005+
1006+ if is_json && is_computed {
1007+ errors. push ( Diagnostic {
1008+ level : Level :: Error ,
1009+ message : format ! ( "Type '{}' cannot use both @json and @computed" , ct. name) ,
1010+ code : Some ( "C000" . to_string ( ) ) ,
1011+ spans : vec ! [ SpanLabel {
1012+ span: ct. span,
1013+ style: SpanStyle :: Primary ,
1014+ label: None ,
1015+ } ] ,
1016+ } ) ;
1017+ }
1018+
1019+ if is_computed && table_annotation. is_some ( ) {
1020+ errors. push ( Diagnostic {
1021+ level : Level :: Error ,
1022+ message : format ! (
1023+ "Type '{}' cannot use @table when marked as @computed" ,
1024+ ct. name
1025+ ) ,
1026+ code : Some ( "C000" . to_string ( ) ) ,
1027+ spans : vec ! [ SpanLabel {
1028+ span: ct. span,
1029+ style: SpanStyle :: Primary ,
1030+ label: None ,
1031+ } ] ,
1032+ } ) ;
1033+ }
1034+
1035+ let representation = if is_json {
10041036 EntityRepresentation :: Json
1037+ } else if is_computed {
1038+ EntityRepresentation :: Computed
10051039 } else if table_managed {
10061040 EntityRepresentation :: Managed
10071041 } else {
@@ -1010,13 +1044,13 @@ fn resolve_composite_type(
10101044
10111045 let access_annotation = ct. annotations . get ( "access" ) ;
10121046
1013- let is_json = representation == EntityRepresentation :: Json ;
1047+ let is_json_like = representation. is_json_like ( ) ;
10141048
1015- if is_json && access_annotation. is_some ( ) {
1049+ if is_json_like && access_annotation. is_some ( ) {
10161050 errors. push ( Diagnostic {
10171051 level : Level :: Error ,
10181052 message : format ! (
1019- "Cannot use @access for type {}. Json types behave like a primitive (and thus have always-allowed access)" ,
1053+ "Cannot use @access for type {}. Json and computed types behave like a primitive (and thus have always-allowed access)" ,
10201054 ct. name
10211055 ) ,
10221056 code : Some ( "C000" . to_string ( ) ) ,
@@ -1040,7 +1074,7 @@ fn resolve_composite_type(
10401074 let join_table_config = join_table_annotation
10411075 . and_then ( |annotation| parse_join_table_annotation ( ct, annotation, errors) ) ;
10421076
1043- let mut access = if is_json {
1077+ let mut access = if is_json_like {
10441078 // As if the user has annotated with `access(true)`
10451079 ResolvedAccess {
10461080 default : Some ( AstExpr :: BooleanLiteral ( true , default_span ( ) ) ) ,
@@ -1054,7 +1088,7 @@ fn resolve_composite_type(
10541088
10551089 let ( resolved_fields, ownership_field_found) = resolve_composite_type_fields (
10561090 ct,
1057- is_json ,
1091+ is_json_like ,
10581092 table_managed,
10591093 module_base_path,
10601094 typechecked_system,
@@ -1120,7 +1154,7 @@ fn resolve_composite_type(
11201154
11211155fn resolve_composite_type_fields (
11221156 ct : & AstModel < Typed > ,
1123- is_json : bool ,
1157+ is_json_like : bool ,
11241158 table_managed : bool ,
11251159 module_base_path : & Path ,
11261160 typechecked_system : & TypecheckedSystem ,
@@ -1138,13 +1172,13 @@ fn resolve_composite_type_fields(
11381172
11391173 let access_annotation = field. annotations . get ( "access" ) ;
11401174
1141- if is_json && access_annotation. is_some ( ) {
1175+ if is_json_like && access_annotation. is_some ( ) {
11421176 errors. push ( Diagnostic {
1143- level : Level :: Error ,
1144- message : format ! (
1145- "Cannot use @access for field '{}' in a type with a '@json' annotation" ,
1146- field. name
1147- ) ,
1177+ level : Level :: Error ,
1178+ message : format ! (
1179+ "Cannot use @access for field '{}' in a type with a '@json' or '@computed ' annotation" ,
1180+ field. name
1181+ ) ,
11481182 code : Some ( "C000" . to_string ( ) ) ,
11491183 spans : vec ! [ SpanLabel {
11501184 span: field. span,
@@ -1944,6 +1978,8 @@ fn compute_column_info(
19441978 _ => field_name. to_snake_case ( ) ,
19451979 } ;
19461980 let enclosing_is_json = enclosing_type. annotations . contains ( "json" ) ;
1981+ let enclosing_is_computed = enclosing_type. annotations . contains ( "computed" ) ;
1982+ let enclosing_is_json_like = enclosing_is_json || enclosing_is_computed;
19471983
19481984 let id_column_names = |field : & AstField < Typed > | -> Result < Vec < String > , Diagnostic > {
19491985 let user_supplied_column_mapping = column_annotation_mapping ( field) ;
@@ -2013,7 +2049,9 @@ fn compute_column_info(
20132049 AstFieldType :: Plain ( ..) => {
20142050 match field_base_type. to_typ ( types) . deref ( types) {
20152051 Type :: Composite ( field_type) => {
2016- if field_type. annotations . contains ( "json" ) {
2052+ if field_type. annotations . contains ( "json" )
2053+ || ( enclosing_is_json_like && enclosing_is_computed)
2054+ {
20172055 return Ok ( ColumnInfo {
20182056 names : vec ! [ compute_column_name( & field. name) ] ,
20192057 self_column : true ,
@@ -2132,9 +2170,9 @@ fn compute_column_info(
21322170 }
21332171 }
21342172 Type :: Set ( typ) => {
2135- if enclosing_is_json
2136- && let Type :: Composite ( field_type) = typ . deref ( types )
2137- && field_type . annotations . contains ( "json" )
2173+ if let Type :: Composite ( field_type ) = typ . deref ( types )
2174+ && ( field_type. annotations . contains ( "json" )
2175+ || ( enclosing_is_json_like && enclosing_is_computed ) )
21382176 {
21392177 return Ok ( ColumnInfo {
21402178 names : vec ! [ compute_column_name( & field. name) ] ,
@@ -2227,17 +2265,29 @@ fn compute_column_info(
22272265 indices,
22282266 cardinality : None ,
22292267 } )
2230- } else if enclosing_is_json
2231- && let Type :: Composite ( field_type) = & underlying_resolved
2232- && field_type. annotations . contains ( "json" )
2233- {
2234- Ok ( ColumnInfo {
2235- names : vec ! [ compute_column_name( & field. name) ] ,
2236- self_column : true ,
2237- unique_constraints,
2238- indices,
2239- cardinality : None ,
2240- } )
2268+ } else if let Type :: Composite ( field_type) = & underlying_resolved {
2269+ if field_type. annotations . contains ( "json" )
2270+ || ( enclosing_is_json_like && enclosing_is_computed)
2271+ {
2272+ Ok ( ColumnInfo {
2273+ names : vec ! [ compute_column_name( & field. name) ] ,
2274+ self_column : true ,
2275+ unique_constraints,
2276+ indices,
2277+ cardinality : None ,
2278+ } )
2279+ } else {
2280+ Err ( Diagnostic {
2281+ level : Level :: Error ,
2282+ message : "Arrays of non-primitives are not supported" . to_string ( ) ,
2283+ code : Some ( "C000" . to_string ( ) ) ,
2284+ spans : vec ! [ SpanLabel {
2285+ span: field. span,
2286+ style: SpanStyle :: Primary ,
2287+ label: None ,
2288+ } ] ,
2289+ } )
2290+ }
22412291 } else {
22422292 Err ( Diagnostic {
22432293 level : Level :: Error ,
@@ -2674,6 +2724,37 @@ mod tests {
26742724 ) ;
26752725 }
26762726
2727+ #[ multiplatform_test]
2728+ fn computed_collection_of_computed_types ( ) {
2729+ let resolved = create_resolved_system_from_src (
2730+ r#"
2731+ @postgres
2732+ module ContentModule {
2733+ @computed
2734+ type TeamAvailablePlaybook {
2735+ tags: Array<TeamAvailablePlaybookTag>
2736+ lessons: Set<TeamAvailablePlaybookLesson>
2737+ }
2738+
2739+ @computed
2740+ type TeamAvailablePlaybookTag {
2741+ label: String
2742+ }
2743+
2744+ @computed
2745+ type TeamAvailablePlaybookLesson {
2746+ id: Int
2747+ }
2748+ }
2749+ "# ,
2750+ ) ;
2751+
2752+ assert ! (
2753+ resolved. is_ok( ) ,
2754+ "Expected @computed collection fields to resolve without errors"
2755+ ) ;
2756+ }
2757+
26772758 #[ test]
26782759 fn with_access ( ) {
26792760 File :: create ( "logger.js" ) . unwrap ( ) ;
0 commit comments