Skip to content

Commit 3f7551c

Browse files
committed
resolver: Annotate function pointers
1 parent a0d3bf1 commit 3f7551c

File tree

4 files changed

+285
-6
lines changed

4 files changed

+285
-6
lines changed

src/parser.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,8 +1007,13 @@ fn parse_type_reference_type_definition(
10071007
name: Option<String>,
10081008
) -> Option<(DataTypeDeclaration, Option<AstNode>)> {
10091009
let start = lexer.range().start;
1010-
//Subrange
1011-
let referenced_type = lexer.slice_and_advance();
1010+
1011+
let mut referenced_type = lexer.slice_and_advance();
1012+
1013+
// REMOVE THIS BEFORE MERGE, ONLY USED FOR PROTOTYPE ITERATION (there will be no parsing of such expressions from the desugaring POV)
1014+
if lexer.try_consume(KeywordDot) {
1015+
referenced_type = format!("{referenced_type}.{}", lexer.slice_and_advance());
1016+
}
10121017

10131018
let bounds = if lexer.try_consume(KeywordParensOpen) {
10141019
// INT (..) :=

src/resolver.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,20 @@ pub enum StatementAnnotation {
474474
/// The call name of the function iff it differs from the qualified name (generics)
475475
call_name: Option<String>,
476476
},
477+
/// A pointer to a function, mostly needed for polymorphism where function calls are handled indirectly
478+
/// with a virtual table.
479+
FunctionPointer {
480+
/// The return type name of the function pointer
481+
return_type: String,
482+
483+
// XXX: In classical function pointers such information is not neccessary, e.g. in C you would have
484+
// `<return type> (*<function pointer name>)(<comma seperated arg types>)`, however in ST I
485+
// __think__ it might be neccessary because of named arguments? Obviously this limits the use
486+
// case of function pointers because you'd always reference a concrete function rather than some
487+
// generic function such as `DINT(STRING, INT)`.
488+
/// The name of the referenced function, e.g. `MyFb.myMethod` in `POINTER TO MyFb.MyMethod := ADR(...)`
489+
qualified_name: String,
490+
},
477491
/// a reference to a type (e.g. `INT`)
478492
Type {
479493
type_name: String,
@@ -676,6 +690,18 @@ impl StatementAnnotation {
676690
StatementAnnotation::Value { resulting_type: type_name.into() }
677691
}
678692

693+
/// Constructs a new [`StatementAnnotation::FunctionPointer`] with the qualified and return type name
694+
pub fn fnptr<T, U>(qualified_name: T, return_type_name: U) -> Self
695+
where
696+
T: Into<String>,
697+
U: Into<String>,
698+
{
699+
StatementAnnotation::FunctionPointer {
700+
return_type: return_type_name.into(),
701+
qualified_name: qualified_name.into(),
702+
}
703+
}
704+
679705
pub fn create_override(definitions: Vec<MethodDeclarationType>) -> Self {
680706
StatementAnnotation::Override { definitions }
681707
}
@@ -847,11 +873,12 @@ pub trait AnnotationMap {
847873
.get_hint(statement)
848874
.or_else(|| self.get(statement))
849875
.and_then(|it| self.get_type_name_for_annotation(it)),
850-
StatementAnnotation::Program { qualified_name }
851-
| StatementAnnotation::Super { name: qualified_name, .. } => Some(qualified_name.as_str()),
852876
StatementAnnotation::Type { type_name } => Some(type_name),
853-
StatementAnnotation::Function { .. }
854-
| StatementAnnotation::Label { .. }
877+
StatementAnnotation::Program { qualified_name }
878+
| StatementAnnotation::Super { name: qualified_name, .. }
879+
| StatementAnnotation::Function { qualified_name, .. }
880+
| StatementAnnotation::FunctionPointer { qualified_name, .. } => Some(&qualified_name),
881+
StatementAnnotation::Label { .. }
855882
| StatementAnnotation::Override { .. }
856883
| StatementAnnotation::MethodDeclarations { .. }
857884
| StatementAnnotation::Property { .. }
@@ -2020,6 +2047,17 @@ impl<'i> TypeAnnotator<'i> {
20202047
.find_effective_type_by_name(inner_type_name)
20212048
.or(self.annotation_map.new_index.find_effective_type_by_name(inner_type_name))
20222049
{
2050+
// We might be dealing with a function pointer, e.g. `ptr^(...)`
2051+
if let Some(pou) = self.index.find_pou(&inner_type.name) {
2052+
if pou.is_method() {
2053+
let name = pou.get_name();
2054+
let return_type = pou.get_return_type().unwrap();
2055+
2056+
self.annotate(stmt, StatementAnnotation::fnptr(name, return_type));
2057+
return;
2058+
}
2059+
}
2060+
20232061
self.annotate(stmt, StatementAnnotation::value(inner_type.get_name()))
20242062
}
20252063
}
@@ -2059,6 +2097,11 @@ impl<'i> TypeAnnotator<'i> {
20592097
.iter()
20602098
.find_map(|scope| scope.resolve_name(name, qualifier, self.index, ctx, &self.scopes)),
20612099

2100+
AstStatement::ReferenceExpr(_) => {
2101+
self.visit_statement(ctx, reference);
2102+
self.annotation_map.get(reference).cloned()
2103+
}
2104+
20622105
AstStatement::Literal(..) => {
20632106
self.visit_statement_literals(ctx, reference);
20642107
let literal_annotation = self.annotation_map.get(reference).cloned(); // return what we just annotated //TODO not elegant, we need to clone
@@ -2082,12 +2125,15 @@ impl<'i> TypeAnnotator<'i> {
20822125
self.visit_statement(ctx, data.index.as_ref());
20832126
Some(StatementAnnotation::value(get_direct_access_type(&data.access)))
20842127
}
2128+
20852129
AstStatement::HardwareAccess(data, ..) => {
20862130
let name = data.get_mangled_variable_name();
20872131
ctx.resolve_strategy.iter().find_map(|strategy| {
20882132
strategy.resolve_name(&name, qualifier, self.index, ctx, &self.scopes)
20892133
})
20902134
}
2135+
2136+
AstStatement::ParenExpression(expr) => self.resolve_reference_expression(expr, qualifier, ctx),
20912137
_ => None,
20922138
}
20932139
}

src/resolver/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod const_resolver_tests;
2+
mod fnptr;
23
mod lowering;
34
mod resolve_and_lower_init_functions;
45
mod resolve_config_variables;

src/resolver/tests/fnptr.rs

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
use plc_ast::{
2+
ast::{AstStatement, CallStatement, ReferenceAccess, ReferenceExpr},
3+
provider::IdProvider,
4+
};
5+
6+
use crate::{
7+
resolver::{AnnotationMap, TypeAnnotator},
8+
test_utils::tests::index_with_ids,
9+
};
10+
11+
#[test]
12+
fn function_pointer_method_with_no_arguments() {
13+
let ids = IdProvider::default();
14+
let (unit, index) = index_with_ids(
15+
r"
16+
FUNCTION_BLOCK Fb
17+
METHOD echo: DINT
18+
END_METHOD
19+
END_FUNCTION_BLOCK
20+
21+
FUNCTION main
22+
VAR
23+
echoPtr: POINTER TO Fb.echo := ADR(Fb.echo);
24+
END_VAR
25+
26+
echoPtr^();
27+
END_FUNCTION
28+
",
29+
ids.clone(),
30+
);
31+
32+
let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, ids);
33+
{
34+
let node = &unit.implementations.iter().find(|imp| imp.name == "main").unwrap().statements[0];
35+
let AstStatement::CallStatement(CallStatement { operator, .. }) = node.get_stmt() else {
36+
unreachable!();
37+
};
38+
39+
// echoPtr^();
40+
// ^^^^^^^^
41+
insta::assert_debug_snapshot!(annotations.get(operator), @r#"
42+
Some(
43+
FunctionPointer {
44+
return_type: "DINT",
45+
qualified_name: "Fb.echo",
46+
},
47+
)
48+
"#);
49+
50+
// echoPtr^();
51+
// ^^^^^^^^^^
52+
insta::assert_debug_snapshot!(annotations.get(node), @"None");
53+
}
54+
}
55+
56+
#[test]
57+
fn function_pointer_method_with_arguments() {
58+
let ids = IdProvider::default();
59+
let (unit, index) = index_with_ids(
60+
r"
61+
FUNCTION_BLOCK Fb
62+
METHOD echo: DINT
63+
VAR_INPUT
64+
in: DINT;
65+
END_VAR
66+
67+
VAR_OUTPUT
68+
out: DINT;
69+
END_VAR
70+
71+
VAR_IN_OUT
72+
inout: DINT;
73+
END_VAR
74+
END_METHOD
75+
END_FUNCTION_BLOCK
76+
77+
FUNCTION main
78+
VAR
79+
echoPtr: POINTER TO Fb.echo := ADR(Fb.echo);
80+
localIn, localOut, localInOut: DINT;
81+
END_VAR
82+
83+
echoPtr^();
84+
echoPtr^(in := localIn, out => localOut, inout := localInOut);
85+
END_FUNCTION
86+
",
87+
ids.clone(),
88+
);
89+
90+
let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, ids);
91+
let statements = &unit.implementations.iter().find(|imp| imp.name == "main").unwrap().statements;
92+
{
93+
let AstStatement::CallStatement(CallStatement { operator, .. }) = statements[0].get_stmt() else {
94+
unreachable!();
95+
};
96+
97+
// echoPtr^();
98+
// ^^^^^^^^
99+
insta::assert_debug_snapshot!(annotations.get(operator), @r#"
100+
Some(
101+
FunctionPointer {
102+
return_type: "DINT",
103+
qualified_name: "Fb.echo",
104+
},
105+
)
106+
"#);
107+
108+
// echoPtr^();
109+
// ^^^^^^^^^^
110+
insta::assert_debug_snapshot!(annotations.get(&statements[0]), @"None");
111+
}
112+
113+
{
114+
let AstStatement::CallStatement(CallStatement { operator, .. }) = statements[1].get_stmt() else {
115+
unreachable!();
116+
};
117+
118+
// echoPtr^(in := localIn, out => localOut, inout := localInOut);
119+
// ^^^^^^^^
120+
insta::assert_debug_snapshot!(annotations.get(operator), @r#"
121+
Some(
122+
FunctionPointer {
123+
return_type: "DINT",
124+
qualified_name: "Fb.echo",
125+
},
126+
)
127+
"#);
128+
129+
// echoPtr^(in := localIn, out => localOut, inout := localInOut);
130+
// ^^^^^^^^^^^^^
131+
// TODO:
132+
}
133+
}
134+
135+
#[test]
136+
fn void_pointer_casting() {
137+
let id_provider = IdProvider::default();
138+
let (unit, index) = index_with_ids(
139+
r"
140+
VAR_GLOBAL
141+
vtable_FbA_instance: vtable_FbA;
142+
END_VAR
143+
144+
TYPE vtable_FbA: STRUCT
145+
foo: POINTER TO FbA.foo := ADR(FbA.foo);
146+
END_STRUCT END_TYPE
147+
148+
FUNCTION_BLOCK FbA
149+
VAR
150+
__vtable: POINTER TO __VOID;
151+
END_VAR
152+
153+
METHOD foo
154+
END_METHOD
155+
END_FUNCTION_BLOCK
156+
157+
FUNCTION main
158+
VAR
159+
instanceFbA: FbA;
160+
END_VAR
161+
162+
vtable_FbA#(instanceFbA.__vtable);
163+
vtable_FbA#(instanceFbA.__vtable).foo^(instanceFbA);
164+
END_FUNCTION
165+
",
166+
id_provider.clone(),
167+
);
168+
169+
let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider);
170+
{
171+
let node = &unit.implementations.iter().find(|imp| imp.name == "main").unwrap().statements[0];
172+
173+
// vtable_FbA#(instanceFbA.__vtable)
174+
// ^^^^^^^^^^^^^^^^^^^^
175+
let AstStatement::ReferenceExpr(ReferenceExpr { access: ReferenceAccess::Cast(target), .. }) =
176+
node.get_stmt()
177+
else {
178+
unreachable!();
179+
};
180+
181+
insta::assert_debug_snapshot!(annotations.get(target), @r#"
182+
Some(
183+
Variable {
184+
resulting_type: "__FbA___vtable",
185+
qualified_name: "FbA.__vtable",
186+
constant: false,
187+
argument_type: ByVal(
188+
Local,
189+
),
190+
auto_deref: None,
191+
},
192+
)
193+
"#);
194+
insta::assert_debug_snapshot!(annotations.get_hint(target), @"None");
195+
196+
// vtable_FbA#(instanceFbA.__vtable)
197+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
198+
insta::assert_debug_snapshot!(annotations.get(node), @r#"
199+
Some(
200+
Value {
201+
resulting_type: "vtable_FbA",
202+
},
203+
)
204+
"#);
205+
insta::assert_debug_snapshot!(annotations.get_hint(node), @"None");
206+
}
207+
208+
{
209+
let node = &unit.implementations.iter().find(|imp| imp.name == "main").unwrap().statements[1];
210+
211+
// vtable_FbA#(instanceFbA.__vtable).foo^(instanceFbA);
212+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
213+
let AstStatement::CallStatement(call) = node.get_stmt() else {
214+
unreachable!();
215+
};
216+
217+
insta::assert_debug_snapshot!(annotations.get(&call.operator), @r#"
218+
Some(
219+
FunctionPointer {
220+
return_type: "VOID",
221+
qualified_name: "FbA.foo",
222+
},
223+
)
224+
"#);
225+
insta::assert_debug_snapshot!(annotations.get_hint(&call.operator), @"None");
226+
}
227+
}

0 commit comments

Comments
 (0)