relm_gen_widget/
lib.rs

1/*
2 * Copyright (c) 2017-2018 Boucher, Antoni <bouanto@zoho.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy of
5 * this software and associated documentation files (the "Software"), to deal in
6 * the Software without restriction, including without limitation the rights to
7 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 * the Software, and to permit persons to whom the Software is furnished to do so,
9 * subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 */
21
22/*
23 * TODO: automatically add the model() method with a () return type when it is not found?
24 * FIXME: Doing model.text.push_str() will not cause a set_text() to be added.
25 * TODO: think about conditions and loops (widget-list).
26 */
27
28#![recursion_limit="128"]
29
30#[macro_use]
31extern crate lazy_static;
32extern crate proc_macro;
33extern crate proc_macro2;
34#[macro_use]
35extern crate quote;
36#[macro_use]
37extern crate syn;
38
39mod adder;
40mod gen;
41mod parser;
42mod transformer;
43mod walker;
44
45use std::collections::{HashMap, HashSet};
46
47use proc_macro2::{Span, TokenStream};
48use quote::ToTokens;
49use syn::{
50    ArgCaptured,
51    Generics,
52    Ident,
53    ImplItem,
54    ImplItemMethod,
55    ItemImpl,
56    Macro,
57    MethodSig,
58    Path,
59    ReturnType,
60    TypePath,
61    parse,
62};
63use syn::FnArg::{self, Captured};
64use syn::fold::Fold;
65use syn::ImplItem::{Const, Existential, Method, Verbatim};
66use syn::Item::{self, Impl};
67use syn::parse::Result;
68use syn::spanned::Spanned;
69use syn::Type;
70use syn::visit::Visit;
71
72use adder::{Adder, Message, Property};
73use gen::gen;
74pub use gen::gen_where_clause;
75use parser::EitherWidget::{Gtk, Relm};
76use parser::{Widget, parse_widget};
77use walker::ModelVariableVisitor;
78
79const MODEL_IDENT: &str = "__relm_model";
80
81type MsgModelMap = HashMap<Ident, HashSet<Message>>;
82type PropertyModelMap = HashMap<Ident, HashSet<Property>>;
83
84#[derive(Debug)]
85pub struct Driver {
86    data_method: Option<ImplItem>,
87    generic_types: Option<Generics>,
88    model_type: Option<ImplItem>,
89    model_param_type: Option<ImplItem>,
90    msg_model_map: Option<MsgModelMap>,
91    msg_type: Option<ImplItem>,
92    other_methods: Vec<ImplItem>,
93    properties_model_map: Option<PropertyModelMap>,
94    root_method: Option<ImplItem>,
95    root_type: Option<ImplItem>,
96    root_widget: Option<Ident>,
97    root_widget_expr: Option<TokenStream>,
98    root_widget_type: Option<TokenStream>,
99    update_method: Option<ImplItem>,
100    view_macro: Option<Macro>,
101    widget_model_type: Option<Type>,
102    widget_msg_type: Option<Type>,
103    widget_parent_id: Option<String>,
104    widgets: HashMap<Ident, TokenStream>, // Map widget ident to widget type.
105}
106
107struct View {
108    container_impl: TokenStream,
109    item: ImplItem,
110    msg_model_map: MsgModelMap,
111    properties_model_map: PropertyModelMap,
112    relm_widgets: HashMap<Ident, Path>,
113    widget: Widget,
114}
115
116impl Driver {
117    fn new() -> Self {
118        Driver {
119            data_method: None,
120            generic_types: None,
121            model_type: None,
122            model_param_type: None,
123            msg_model_map: None,
124            msg_type: None,
125            other_methods: vec![],
126            properties_model_map: None,
127            root_method: None,
128            root_type: None,
129            root_widget: None,
130            root_widget_expr: None,
131            root_widget_type: None,
132            update_method: None,
133            view_macro: None,
134            widget_model_type: None,
135            widget_msg_type: None,
136            widget_parent_id: None,
137            widgets: HashMap::new(),
138        }
139    }
140
141    fn add_set_property_to_method(&self, func: &mut ImplItem) {
142        if let Method(ImplItemMethod { ref mut block, .. }) = *func {
143            let msg_map = self.msg_model_map.as_ref().expect("update method");
144            let property_map = self.properties_model_map.as_ref().expect("update method");
145            let mut adder = Adder::new(property_map, msg_map);
146            *block = adder.fold_block(block.clone());
147        }
148    }
149
150    fn add_widgets(&mut self, widget: &Widget, map: &PropertyModelMap) {
151        // Only add widgets that are needed by the update() function.
152        let mut to_add = false;
153        for values in map.values() {
154            for value in values {
155                if value.widget_name == widget.name {
156                    to_add = true;
157                }
158            }
159        }
160        if to_add {
161            let widget_type = &widget.typ;
162            let typ = quote! {
163                #widget_type
164            };
165            self.widgets.insert(widget.name.clone(), typ);
166        }
167        for child in &widget.children {
168            self.add_widgets(child, map);
169        }
170    }
171
172    fn create_struct(&self, typ: &Type, relm_widgets: &HashMap<Ident, Path>, generics: &Generics) -> TokenStream {
173        let where_clause = gen_where_clause(generics);
174        let widgets = self.widgets.iter().filter(|&(ident, _)| !relm_widgets.contains_key(ident))
175            .map(|(ident, tokens)| (ident.clone(), tokens));
176        let (idents, types): (Vec<Ident>, Vec<_>) = widgets.unzip();
177        let idents = &idents;
178        let types = &types;
179        let relm_idents = relm_widgets.keys();
180        let relm_types = relm_widgets.values();
181        let widget_model_type = self.widget_model_type.as_ref().expect("missing model method");
182        let widgets = {
183            let relm_idents = relm_widgets.keys();
184            let relm_types = relm_widgets.values();
185            let name = Ident::new(&format!("__{}Widgets", get_name(&typ)), Span::call_site());
186            quote! {
187                pub struct #name {
188                    #(pub #relm_idents: #relm_types,)*
189                }
190            }
191        };
192        quote_spanned! { typ.span() =>
193            #[allow(dead_code, missing_docs)]
194            pub struct #typ #where_clause {
195                #(#idents: #types,)*
196                #(#relm_idents: #relm_types,)*
197                model: #widget_model_type,
198            }
199
200            #widgets
201        }
202    }
203
204    fn gen_widget(&mut self, input: TokenStream) -> TokenStream {
205        let mut ast: Item = parse(input.into()).expect("parse_item() in gen_widget()");
206        if let Impl(ItemImpl { attrs, defaultness, unsafety, impl_token, generics, trait_, self_ty, items, brace_token }
207                    ) = ast
208        {
209            self.generic_types = Some(generics.clone());
210            let name = get_name(&self_ty);
211            let mut new_items = vec![];
212            let mut update_items = vec![];
213            for item in items {
214                let mut i = item.clone();
215                match item {
216                    Const(..) => panic!("Unexpected const item"),
217                    Existential(..) => panic!("Unexpected existential item"),
218                    ImplItem::Macro(mac) => self.view_macro = Some(mac.mac),
219                    Method(ImplItemMethod { sig, .. }) => {
220                        match sig.ident.to_string().as_ref() {
221                            "parent_id" => self.data_method = Some(i),
222                            "root" => self.root_method = Some(i),
223                            "model" => {
224                                self.widget_model_type = Some(get_return_type(sig));
225                                add_model_param(&mut i, &mut self.model_param_type);
226                                update_items.push(i);
227                            },
228                            "subscriptions" => update_items.push(i),
229                            "init_view" | "on_add" => new_items.push(i),
230                            "update" => {
231                                self.widget_msg_type = Some(get_second_param_type(&sig));
232                                self.update_method = Some(i)
233                            },
234                            _ => self.other_methods.push(i),
235                        }
236                    },
237                    ImplItem::Type(typ) => {
238                        match typ.ident.to_string().as_ref() {
239                            "Root" => self.root_type = Some(i),
240                            "Model" => self.model_type = Some(i),
241                            "ModelParam" => self.model_param_type = Some(i),
242                            "Msg" => self.msg_type = Some(i),
243                            _ => panic!("Unexpected type item {:?}", typ.ident),
244                        }
245                    },
246                    Verbatim(..) => panic!("Unexpected verbatim item"),
247                }
248            }
249            let view =
250                match self.get_view(&name, &self_ty) {
251                    Ok(view) => view,
252                    Err(error) => return error.to_compile_error(),
253                };
254            if let Some(on_add) = gen_set_child_prop_calls(&view.widget) {
255                new_items.push(on_add);
256            }
257            self.msg_model_map = Some(view.msg_model_map);
258            self.properties_model_map = Some(view.properties_model_map);
259            new_items.push(view.item);
260            self.widgets.insert(self.root_widget.clone().expect("root widget"),
261            self.root_widget_type.clone().expect("root widget type"));
262            let widget_struct = self.create_struct(&self_ty, &view.relm_widgets, &generics);
263            new_items.push(self.get_root_type());
264            if let Some(data_method) = self.get_data_method() {
265                new_items.push(data_method);
266            }
267            new_items.push(self.get_root());
268            let other_methods = self.get_other_methods(&self_ty, &generics);
269            let update_impl = self.update_impl(&self_ty, &generics, update_items);
270            let widget_test_impl = self.widget_test_impl(&self_ty, &generics, &view.relm_widgets);
271            let item = Impl(ItemImpl { attrs, defaultness, unsafety, generics, impl_token, trait_, self_ty, brace_token,
272                items: new_items });
273            ast = item;
274            let container_impl = view.container_impl;
275            quote! {
276                #widget_struct
277                #ast
278                #container_impl
279                #update_impl
280                #widget_test_impl
281
282                #other_methods
283            }
284        }
285        else {
286            panic!("Expected impl");
287        }
288    }
289
290    fn get_data_method(&mut self) -> Option<ImplItem> {
291        self.data_method.take().or_else(|| {
292            if let Some(ref parent_id) = self.widget_parent_id {
293                Some(block_to_impl_item(quote! {
294                    fn parent_id() -> Option<&'static str> {
295                        Some(#parent_id)
296                    }
297                }))
298            }
299            else {
300                None
301            }
302        })
303    }
304
305    fn get_model_param_type(&mut self) -> ImplItem {
306        self.model_param_type.take().unwrap_or_else(|| {
307            block_to_impl_item(quote! {
308                type ModelParam = ();
309            })
310        })
311    }
312
313    fn get_model_type(&mut self) -> ImplItem {
314        self.model_type.take().unwrap_or_else(|| {
315            let widget_model_type = self.widget_model_type.take().expect("missing model method");
316            block_to_impl_item(quote! {
317                type Model = #widget_model_type;
318            })
319        })
320    }
321
322    fn get_msg_type(&mut self) -> ImplItem {
323        self.msg_type.take().unwrap_or_else(|| {
324            let widget_msg_type = self.widget_msg_type.take().expect("missing update method");
325            block_to_impl_item(quote! {
326                type Msg = #widget_msg_type;
327            })
328        })
329    }
330
331    fn get_other_methods(&mut self, typ: &Type, generics: &Generics) -> TokenStream {
332        let mut other_methods: Vec<_> = self.other_methods.drain(..).collect();
333        let where_clause = gen_where_clause(generics);
334        for method in &mut other_methods {
335            self.add_set_property_to_method(method);
336        }
337        quote! {
338            impl #generics #typ #where_clause {
339                #(#other_methods)*
340            }
341        }
342    }
343
344    fn get_root(&mut self) -> ImplItem {
345        self.root_method.take().unwrap_or_else(|| {
346            let root_widget_expr = self.root_widget_expr.take().expect("root widget expr");
347            block_to_impl_item(quote! {
348                fn root(&self) -> Self::Root {
349                    self.#root_widget_expr.clone()
350                }
351            })
352        })
353    }
354
355    fn get_root_type(&mut self) -> ImplItem {
356        self.root_type.take().unwrap_or_else(|| {
357            let root_widget_type = self.root_widget_type.take().expect("root widget type");
358            block_to_impl_item(quote! {
359                type Root = #root_widget_type;
360            })
361        })
362    }
363
364    /*
365     * TODO: Create a control flow graph for each variable of the model.
366     * Add the set_property() calls in every leaf of every graphs.
367     */
368    fn get_update(&mut self) -> ImplItem {
369        let mut func = self.update_method.take().expect("update method");
370        self.add_set_property_to_method(&mut func);
371        // TODO: consider gtk::main_quit() as return.
372        func
373    }
374
375    fn get_view(&mut self, name: &Ident, typ: &Type) -> Result<View> {
376        // This method should probably just be replaced with `impl_view` and
377        // `view_validation_before_impl` should be put inside `impl_view`
378        self.view_validation_before_impl();
379        self.impl_view(name, typ)
380    }
381
382    fn view_validation_before_impl(&mut self) {
383        /*
384        // This is what comes immediately after `view!` e.g. `{ ... }`
385        let macro_token_tree: Vec<_> = self.view_macro.as_ref().expect("`view!` macro not yet set").tts
386            .clone()
387            .into_iter()
388            .collect();
389        // Panic if the macro is declared as anything other than `view! { ... }` or equivalent
390        if macro_token_tree.len() != 1 {
391            panic!("Invalid `view!` syntax, must be `view! { ... }`, `view! ( ... )`, or `view! [ ... ]`");
392        }
393        // Reach inside the brackets and bind the contents (the top level items) of `view!`
394        let top_level_items: Vec<_> = match macro_token_tree[0].kind {
395            TokenNode::Group(_, ref tts) => tts.clone().into_iter().collect(),
396            _ => panic!("Contents of `view!` should be a comma-delimitted series of items")
397        };
398        if let Some(index) = top_level_items.iter().position(|item|
399            match item.kind {
400                TokenNode::Op(',', _) => true,
401                _ => false,
402            })
403        {
404            // Find a comma (meaning more than one top level item) and panic unless it's just a trailing comma
405            if index != top_level_items.len() - 1 {
406                panic!("There may only be one top-level item in `view!`");
407            }
408        } else if top_level_items.len() == 0 {
409            // Panic if `view!` is empty e.g. `view! {}`
410            panic!("`view!` macro is empty, must contain one top-level item");
411        }
412        let macro_name_segments = &self.view_macro.as_ref().expect("`view!` macro not yet set").path.segments;
413        let last_segment = &macro_name_segments[macro_name_segments.len() - 1];
414        if (macro_name_segments.len() != 1) || (last_segment.ident.as_ref() != "view") {
415            let joined_path = macro_name_segments.iter()
416                .map(|seg| seg.ident.as_ref())
417                .collect::<Vec<&str>>()
418                .join("::");
419            panic!("Expected `view!` macro, found `{}` instead", joined_path);
420        }
421        */
422    }
423
424    fn impl_view(&mut self, name: &Ident, typ: &Type) -> Result<View> {
425        let tts = self.view_macro.take().expect("view_macro in impl_view()").tts;
426        let mut widget = parse_widget(tts)?;
427        if let Gtk(ref mut widget) = widget.widget {
428            widget.relm_name = Some(typ.clone());
429        }
430        self.widget_parent_id = widget.parent_id.clone();
431        let mut msg_model_map = HashMap::new();
432        let mut properties_model_map = HashMap::new();
433        get_properties_model_map(&widget, &mut properties_model_map);
434        get_msg_model_map(&widget, &mut msg_model_map);
435        self.add_widgets(&widget, &properties_model_map);
436        let (view, relm_widgets, container_impl) = gen(name, &widget, self);
437        let model_ident = Ident::new(MODEL_IDENT, Span::call_site()); // TODO: maybe need to set Span here.
438        let code = quote_spanned! { name.span() =>
439            #[allow(unused_variables)] // Necessary to avoid warnings in case the parameters are unused.
440            fn view(relm: &::relm::Relm<Self>, #model_ident: Self::Model) -> Self {
441                #view
442            }
443        };
444        let item = block_to_impl_item(code);
445        Ok(View {
446            container_impl,
447            item,
448            msg_model_map,
449            properties_model_map,
450            relm_widgets,
451            widget,
452        })
453    }
454
455    fn update_impl(&mut self, typ: &Type, generics: &Generics, items: Vec<ImplItem>) -> TokenStream {
456        let where_clause = gen_where_clause(generics);
457
458        let msg = self.get_msg_type();
459        let model_param = self.get_model_param_type();
460        let update = self.get_update();
461        let model = self.get_model_type();
462        quote_spanned! { typ.span() =>
463            impl #generics ::relm::Update for #typ #where_clause {
464                #msg
465                #model
466                #model_param
467                #update
468                #(#items)*
469            }
470        }
471    }
472
473    fn widget_test_impl(&self, typ: &Type, generics: &Generics, relm_widgets: &HashMap<Ident, Path>) -> TokenStream {
474        let name = Ident::new(&format!("__{}Widgets", get_name(&typ)), Span::call_site());
475        let where_clause = gen_where_clause(generics);
476        let mut relm_idents = quote! { };
477        for token in relm_widgets.keys().map(|ident| ident.clone().into_token_stream()) {
478            relm_idents = quote_spanned! { typ.span() =>
479                #relm_idents
480                #token: self.#token.clone(),
481            };
482        }
483        quote_spanned! { typ.span() =>
484            impl #generics ::relm::WidgetTest for #typ #where_clause {
485                type Widgets = #name;
486
487                fn get_widgets(&self) -> #name {
488                    #name {
489                        #relm_idents
490                    }
491                }
492            }
493        }
494    }
495}
496
497pub fn gen_widget(input: TokenStream) -> TokenStream {
498    let mut driver = Driver::new();
499    driver.gen_widget(input)
500}
501
502fn add_model_param(model_fn: &mut ImplItem, model_param_type: &mut Option<ImplItem>) {
503    let span = model_fn.span();
504    if let Method(ImplItemMethod { ref mut sig, .. }) = *model_fn {
505        let len = sig.decl.inputs.len();
506        if len == 0 || len == 1 {
507            let type_tokens = quote_spanned! { span =>
508                &::relm::Relm<Self>
509            };
510            let ty: Type = parse(type_tokens.into()).expect("Relm type");
511            let input: FnArg = parse(quote! { _: #ty }.into()).expect("wild arg");
512            sig.decl.inputs.insert(0, input);
513            if len == 0 {
514                let input: FnArg = parse(quote! { _: () }.into()).expect("wild arg");
515                sig.decl.inputs.push(input);
516            }
517        }
518        if let Some(&Captured(ArgCaptured { ref ty, .. })) = sig.decl.inputs.iter().nth(1) {
519            *model_param_type = Some(block_to_impl_item(quote! {
520                type ModelParam = #ty;
521            }));
522        }
523    }
524}
525
526fn block_to_impl_item(tokens: TokenStream) -> ImplItem {
527    let implementation = quote! {
528        impl Test {
529            #tokens
530        }
531    };
532    let implementation: Item = parse(implementation.into()).expect("parse_item in block_to_impl_item");
533    match implementation {
534        Impl(ItemImpl { items, .. }) => items[0].clone(),
535        _ => unreachable!(),
536    }
537}
538
539fn get_name(typ: &Type) -> Ident {
540    if let Type::Path(TypePath { ref path, .. }) = *typ {
541        let mut parts = vec![];
542        for segment in &path.segments {
543            parts.push(segment.ident.to_string());
544        }
545        Ident::new(&parts.join("::"), typ.span())
546    }
547    else {
548        panic!("Expected Path")
549    }
550}
551
552macro_rules! get_map {
553    ($widget:expr, $map:expr, $is_relm:expr) => {{
554        for (name, expr) in &$widget.properties {
555            let mut visitor = ModelVariableVisitor::new();
556            visitor.visit_expr(&expr);
557            let model_variables = visitor.idents;
558            for var in model_variables {
559                let set = $map.entry(var).or_insert_with(HashSet::new);
560                set.insert(Property {
561                    expr: expr.clone(),
562                    is_relm_widget: $is_relm,
563                    name: name.clone(),
564                    widget_name: $widget.name.clone(),
565                });
566            }
567        }
568        for child in &$widget.children {
569            get_properties_model_map(child, $map);
570        }
571    }};
572}
573
574fn get_msg_model_map(widget: &Widget, map: &mut MsgModelMap) {
575    match widget.widget {
576        Gtk(_) => {
577            for child in &widget.children {
578                get_msg_model_map(child, map);
579            }
580        },
581        Relm(ref relm_widget) => {
582            for (name, expr) in &relm_widget.messages {
583                let mut visitor = ModelVariableVisitor::new();
584                visitor.visit_expr(&expr);
585                let model_variables = visitor.idents;
586                for var in model_variables {
587                    let set = map.entry(var).or_insert_with(HashSet::new);
588                    set.insert(Message {
589                        expr: expr.clone(),
590                        name: name.clone(),
591                        widget_name: widget.name.clone(),
592                    });
593                }
594            }
595            for child in &widget.children {
596                get_msg_model_map(child, map);
597            }
598        },
599    }
600}
601
602/*
603 * The map maps model variable name to a vector of tuples (widget name, property name).
604 */
605fn get_properties_model_map(widget: &Widget, map: &mut PropertyModelMap) {
606    match widget.widget {
607        Gtk(_) => get_map!(widget, map, false),
608        Relm(_) => get_map!(widget, map, true),
609    }
610}
611
612fn get_return_type(sig: MethodSig) -> Type {
613    if let ReturnType::Type(_, ty) = sig.decl.output {
614        *ty
615    }
616    else {
617        Type::Tuple(syn::TypeTuple {
618            paren_token: syn::token::Paren::default(),
619            elems: syn::punctuated::Punctuated::new()
620        })
621    }
622}
623
624fn get_second_param_type(sig: &MethodSig) -> Type {
625    if let Captured(ArgCaptured { ref ty, .. }) = sig.decl.inputs[1] {
626        ty.clone()
627    }
628    else {
629        panic!("Unexpected `(unknown)`, expecting Captured Type"/*, sig.decl.inputs[1]*/); // TODO
630    }
631}
632
633fn gen_set_child_prop_calls(widget: &Widget) -> Option<ImplItem> {
634    let mut tokens = quote! {};
635    let widget_name = &widget.name;
636    for (&(ref ident, ref key), value) in &widget.child_properties {
637        let property_func = Ident::new(&format!("set_{}_{}", ident, key), key.span());
638        tokens = quote_spanned! { widget_name.span() =>
639            #tokens
640            parent.#property_func(&self.#widget_name, #value);
641        };
642    }
643    if !widget.child_properties.is_empty() {
644        Some(block_to_impl_item(quote_spanned! { widget_name.span() =>
645            fn on_add<W: ::gtk::IsA<::gtk::Widget> + ::gtk::IsA<::gtk::Object>>(&self, parent: W) {
646                let parent: gtk::Box = ::gtk::Cast::downcast(::gtk::Cast::upcast::<::gtk::Widget>(parent))
647                    .expect("the parent of a widget with child properties must be a gtk::Box");
648                #tokens
649            }
650        }))
651    }
652    else {
653        None
654    }
655}
656
657#[cfg(test)]
658mod tests {
659    use super::*;
660    use syn::parse_expr;
661    use syn::{ExprKind};
662
663    #[test]
664    #[should_panic(expected = "Expected `view!` macro, found `foo` instead")]
665    fn incorrect_view_macro_name() {
666        let macro_text = "foo! {
667            gtk::Window {}
668        }";
669        let parsed_expr: ExprKind = parse_expr(macro_text)
670                                        .expect("incorrect_view_macro_name > parse_expr failed").node;
671        let mac = match parsed_expr {
672            ExprKind::Mac(mac) => mac,
673            _ => panic!("Expected ExprKind::Mac(mac), found {:#?}", parsed_expr),
674        };
675        let mut driver = Driver::new();
676        driver.view_macro = Some(mac);
677        driver.view_validation_before_impl();
678    }
679
680    #[test]
681    #[should_panic(expected = "`view!` macro is empty, must contain one top-level item")]
682    fn empty_view_macro() {
683        let macro_text = "view! {
684        }";
685        let parsed_expr: ExprKind = parse_expr(macro_text)
686                                        .expect("empty_view_macro > parse_expr failed").node;
687        let mac = match parsed_expr {
688            ExprKind::Mac(mac) => mac,
689            _ => panic!("Expected ExprKind::Mac(mac), found {:#?}", parsed_expr),
690        };
691        let mut driver = Driver::new();
692        driver.view_macro = Some(mac);
693        driver.view_validation_before_impl();
694    }
695
696    #[test]
697    #[should_panic(expected = "There may only be one top-level item in `view!`")]
698    fn multiple_top_level_items() {
699        let macro_text = "view! {
700            gtk::Window {},
701            gtk::Window {}
702        }";
703        let parsed_expr: ExprKind = parse_expr(macro_text)
704                                        .expect("multiple_top_level_items > parse_expr failed").node;
705        let mac = match parsed_expr {
706            ExprKind::Mac(mac) => mac,
707            _ => panic!("Expected ExprKind::Mac(mac), found {:#?}", parsed_expr),
708        };
709        let mut driver = Driver::new();
710        driver.view_macro = Some(mac);
711        driver.view_validation_before_impl();
712    }
713}